2023-02-23 22:50:30 +00:00
|
|
|
package ipld_eth_statedb
|
|
|
|
|
|
|
|
import (
|
2023-02-27 22:17:24 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
2023-02-28 17:43:07 +00:00
|
|
|
"math/big"
|
2023-02-27 22:17:24 +00:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/fastcache"
|
2023-02-23 22:50:30 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2023-02-28 17:34:21 +00:00
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
2023-02-27 22:17:24 +00:00
|
|
|
"github.com/ethereum/go-ethereum/statediff/indexer/ipld"
|
|
|
|
lru "github.com/hashicorp/golang-lru"
|
2023-02-23 22:50:30 +00:00
|
|
|
"github.com/jackc/pgx/pgxpool"
|
|
|
|
)
|
|
|
|
|
2023-02-27 22:17:24 +00:00
|
|
|
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
|
2023-02-23 22:50:30 +00:00
|
|
|
type Database interface {
|
|
|
|
ContractCode(addrHash common.Hash, codeHash common.Hash) ([]byte, error)
|
|
|
|
ContractCodeSize(addrHash common.Hash, codeHash common.Hash) (int, error)
|
2023-02-28 18:07:03 +00:00
|
|
|
StateAccount(addressHash, blockHash common.Hash) (*types.StateAccount, error)
|
|
|
|
StorageValue(addressHash, slotHash, blockHash common.Hash) ([]byte, error)
|
2023-02-23 22:50:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 17:34:21 +00:00
|
|
|
var _ Database = &stateDatabase{}
|
|
|
|
|
2023-02-27 22:17:24 +00:00
|
|
|
type stateDatabase struct {
|
2023-02-28 17:34:21 +00:00
|
|
|
pgdb *pgxpool.Pool
|
2023-02-27 22:17:24 +00:00
|
|
|
codeSizeCache *lru.Cache
|
|
|
|
codeCache *fastcache.Cache
|
2023-02-23 22:50:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 18:07:03 +00:00
|
|
|
// NewStateDatabase returns a new Database implementation using the provided postgres connection pool
|
|
|
|
func NewStateDatabase(pgdb *pgxpool.Pool) (*stateDatabase, error) {
|
2023-02-27 22:17:24 +00:00
|
|
|
csc, _ := lru.New(codeSizeCacheSize)
|
|
|
|
return &stateDatabase{
|
2023-02-28 17:34:21 +00:00
|
|
|
pgdb: pgdb,
|
2023-02-27 22:17:24 +00:00
|
|
|
codeSizeCache: csc,
|
|
|
|
codeCache: fastcache.New(codeCacheSize),
|
|
|
|
}, nil
|
|
|
|
}
|
2023-02-27 21:51:04 +00:00
|
|
|
|
2023-02-28 18:07:03 +00:00
|
|
|
// ContractCode satisfies Database, it returns the contract code for a give codehash
|
2023-02-27 22:17:24 +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
|
|
|
|
}
|
|
|
|
cid := ipld.Keccak256ToCid(ipld.RawBinary, codeHash.Bytes())
|
|
|
|
code := make([]byte, 0)
|
|
|
|
if err := sd.pgdb.QueryRow(context.Background(), GetContractCodePgStr, cid).Scan(&code); err != nil {
|
|
|
|
return nil, errNotFound
|
|
|
|
}
|
|
|
|
if len(code) > 0 {
|
|
|
|
sd.codeCache.Set(codeHash.Bytes(), code)
|
|
|
|
sd.codeSizeCache.Add(codeHash, len(code))
|
|
|
|
return code, nil
|
|
|
|
}
|
|
|
|
return nil, errNotFound
|
2023-02-23 22:50:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 18:07:03 +00:00
|
|
|
// ContractCodeSize satisfies Database, it returns the length of the code for a provided codehash
|
2023-02-27 22:17:24 +00:00
|
|
|
func (sd *stateDatabase) ContractCodeSize(_, codeHash common.Hash) (int, error) {
|
|
|
|
if cached, ok := sd.codeSizeCache.Get(codeHash); ok {
|
|
|
|
return cached.(int), nil
|
|
|
|
}
|
|
|
|
code, err := sd.ContractCode(common.Hash{}, codeHash)
|
|
|
|
return len(code), err
|
2023-02-23 22:50:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 18:07:03 +00:00
|
|
|
// 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-02-28 18:07:03 +00:00
|
|
|
err := sd.pgdb.QueryRow(context.Background(), GetStateAccount, addressHash.Hex(), blockHash.Hex()).Scan(&res)
|
|
|
|
if err != nil {
|
2023-02-28 17:43:07 +00:00
|
|
|
return nil, errNotFound
|
|
|
|
}
|
|
|
|
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),
|
|
|
|
CodeHash: res.CodeHash,
|
|
|
|
}, nil
|
2023-02-23 22:50:30 +00:00
|
|
|
}
|
|
|
|
|
2023-02-28 18:07:03 +00:00
|
|
|
// StorageValue satisfies Database, it returns the 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-02-28 18:07:03 +00:00
|
|
|
err := sd.pgdb.QueryRow(context.Background(), GetStorageSlot, addressHash.Hex(), slotHash.Hex(), blockHash.Hex()).Scan(&res)
|
|
|
|
if err != nil {
|
2023-02-28 17:45:11 +00:00
|
|
|
return nil, errNotFound
|
|
|
|
}
|
|
|
|
if res.Removed {
|
|
|
|
// TODO: check expected behavior for deleted/non existing accounts
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return res.Value, nil
|
2023-02-23 22:50:30 +00:00
|
|
|
}
|