ipld-eth-statedb/database.go

136 lines
4.3 KiB
Go
Raw Permalink Normal View History

package ipld_eth_statedb
import (
"context"
"errors"
2023-03-01 19:54:54 +00:00
"fmt"
2023-02-28 17:43:07 +00:00
"math/big"
"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
2023-02-28 17:34:21 +00:00
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/statediff/indexer/ipld"
lru "github.com/hashicorp/golang-lru"
2023-03-01 19:54:54 +00:00
"github.com/jackc/pgx/v4/pgxpool"
2023-03-05 07:33:51 +00:00
util "github.com/cerc-io/ipld-eth-statedb/internal"
)
const (
// Number of codehash->size associations to keep.
codeSizeCacheSize = 100000
// Cache size granted for caching clean code.
codeCacheSize = 64 * 1024 * 1024
)
var (
// not found error
errNotFound = errors.New("not found")
)
2023-02-28 17:34:21 +00:00
// Database interface is a union of the subset of the geth state.Database interface required
// to support the vm.StateDB implementation as well as methods specific to this Postgres based implementation
type Database interface {
2023-03-06 17:46:07 +00:00
ContractCode(codeHash common.Hash) ([]byte, error)
ContractCodeSize(codeHash common.Hash) (int, error)
StateAccount(addressHash, blockHash common.Hash) (*types.StateAccount, error)
StorageValue(addressHash, slotHash, blockHash common.Hash) ([]byte, error)
}
2023-02-28 17:34:21 +00:00
var _ Database = &stateDatabase{}
type stateDatabase struct {
2023-02-28 17:34:21 +00:00
pgdb *pgxpool.Pool
codeSizeCache *lru.Cache
codeCache *fastcache.Cache
}
2023-03-05 07:33:51 +00:00
// NewStateDatabaseWithPool returns a new Database implementation using the provided postgres connection pool
func NewStateDatabaseWithPool(pgDb *pgxpool.Pool) (*stateDatabase, error) {
csc, _ := lru.New(codeSizeCacheSize)
return &stateDatabase{
2023-03-01 19:54:54 +00:00
pgdb: pgDb,
codeSizeCache: csc,
codeCache: fastcache.New(codeCacheSize),
}, nil
}
2023-03-05 07:33:51 +00:00
// NewStateDatabase returns a new Database implementation using the passed parameters
func NewStateDatabase(ctx context.Context, conf Config) (*stateDatabase, error) {
pgDb, err := NewPGXPool(ctx, conf)
if err != nil {
return nil, err
}
return NewStateDatabaseWithPool(pgDb)
}
// ContractCode satisfies Database, it returns the contract code for a given codehash
2023-03-06 17:46:07 +00:00
func (sd *stateDatabase) ContractCode(codeHash common.Hash) ([]byte, error) {
if code := sd.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
return code, nil
}
2023-03-05 07:33:51 +00:00
c, err := util.Keccak256ToCid(ipld.RawBinary, codeHash.Bytes())
2023-03-01 19:54:54 +00:00
if err != nil {
return nil, fmt.Errorf("cannot derive CID from provided codehash: %s", err.Error())
}
code := make([]byte, 0)
2023-03-01 19:54:54 +00:00
if err := sd.pgdb.QueryRow(context.Background(), GetContractCodePgStr, c).Scan(&code); err != nil {
2023-03-05 07:33:51 +00:00
return nil, err
}
if len(code) > 0 {
sd.codeCache.Set(codeHash.Bytes(), code)
sd.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
return nil, errNotFound
}
// ContractCodeSize satisfies Database, it returns the length of the code for a provided codehash
2023-03-06 17:46:07 +00:00
func (sd *stateDatabase) ContractCodeSize(codeHash common.Hash) (int, error) {
if cached, ok := sd.codeSizeCache.Get(codeHash); ok {
return cached.(int), nil
}
2023-03-06 17:46:07 +00:00
code, err := sd.ContractCode(codeHash)
return len(code), err
}
// StateAccount satisfies Database, it returns the types.StateAccount for a provided address and block hash
func (sd *stateDatabase) StateAccount(addressHash, blockHash common.Hash) (*types.StateAccount, error) {
2023-02-28 17:43:07 +00:00
res := StateAccountResult{}
2023-03-05 07:45:53 +00:00
err := sd.pgdb.QueryRow(context.Background(), GetStateAccount, addressHash.Hex(), blockHash.Hex()).
Scan(&res.Balance, &res.Nonce, &res.CodeHash, &res.StorageRoot, &res.Removed)
if err != nil {
2023-03-05 07:33:51 +00:00
return nil, err
2023-02-28 17:43:07 +00:00
}
if res.Removed {
// TODO: check expected behavior for deleted/non existing accounts
return nil, nil
}
bal := new(big.Int)
bal.SetString(res.Balance, 10)
return &types.StateAccount{
Nonce: res.Nonce,
Balance: bal,
Root: common.HexToHash(res.StorageRoot),
2023-03-05 07:45:53 +00:00
CodeHash: common.HexToHash(res.CodeHash).Bytes(),
2023-02-28 17:43:07 +00:00
}, nil
}
2023-03-05 07:33:51 +00:00
// StorageValue satisfies Database, it returns the RLP-encoded storage value for the provided address, slot,
// and block hash
func (sd *stateDatabase) StorageValue(addressHash, slotHash, blockHash common.Hash) ([]byte, error) {
2023-02-28 17:45:11 +00:00
res := StorageSlotResult{}
2023-03-05 07:45:53 +00:00
err := sd.pgdb.QueryRow(context.Background(), GetStorageSlot,
addressHash.Hex(), slotHash.Hex(), blockHash.Hex()).
2023-03-08 02:40:28 +00:00
Scan(&res.Value, &res.Removed, &res.StateLeafRemoved)
if err != nil {
2023-03-05 07:33:51 +00:00
return nil, err
2023-02-28 17:45:11 +00:00
}
2023-03-08 02:40:28 +00:00
if res.Removed || res.StateLeafRemoved {
2023-02-28 17:45:11 +00:00
// TODO: check expected behavior for deleted/non existing accounts
return nil, nil
}
return res.Value, nil
}