Skip to content

Commit

Permalink
Merge pull request #11 from cerc-io/ian/v5_dev
Browse files Browse the repository at this point in the history
create database abstraction to make it easier to use different drivers
  • Loading branch information
i-norden authored Mar 16, 2023
2 parents 9f53f99 + 73bb6cf commit c7d05bb
Show file tree
Hide file tree
Showing 11 changed files with 606 additions and 157 deletions.
27 changes: 27 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package ipld_eth_statedb

import (
"context"
"fmt"
"time"

"github.com/jackc/pgx/v4/pgxpool"
"github.com/jmoiron/sqlx"

_ "github.com/lib/pq"
)

type Config struct {
Expand All @@ -19,6 +23,20 @@ type Config struct {
MinConns int
MaxConnLifetime time.Duration
MaxConnIdleTime time.Duration
MaxIdle int
}

// DbConnectionString constructs and returns the connection string from the config (for sqlx driver)
func (c Config) DbConnectionString() string {
if len(c.Username) > 0 && len(c.Password) > 0 {
return fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
c.Username, c.Password, c.Hostname, c.Port, c.DatabaseName)
}
if len(c.Username) > 0 && len(c.Password) == 0 {
return fmt.Sprintf("postgresql://%s@%s:%d/%s?sslmode=disable",
c.Username, c.Hostname, c.Port, c.DatabaseName)
}
return fmt.Sprintf("postgresql://%s:%d/%s?sslmode=disable", c.Hostname, c.Port, c.DatabaseName)
}

// NewPGXPool returns a new pgx conn pool
Expand All @@ -30,6 +48,15 @@ func NewPGXPool(ctx context.Context, config Config) (*pgxpool.Pool, error) {
return pgxpool.ConnectConfig(ctx, pgConf)
}

// NewSQLXPool returns a new sqlx conn pool
func NewSQLXPool(ctx context.Context, config Config) (*sqlx.DB, error) {
db, err := sqlx.ConnectContext(ctx, "postgres", config.DbConnectionString())
if err != nil {
return nil, err
}
return db, nil
}

// makePGXConfig creates a pgxpool.Config from the provided Config
func makePGXConfig(config Config) (*pgxpool.Config, error) {
conf, err := pgxpool.ParseConfig("")
Expand Down
139 changes: 16 additions & 123 deletions database.go
Original file line number Diff line number Diff line change
@@ -1,135 +1,28 @@
package ipld_eth_statedb

import (
"context"
"errors"
"fmt"
"math/big"
var _ Database = &DB{}

"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/statediff/indexer/ipld"
lru "github.com/hashicorp/golang-lru"
"github.com/jackc/pgx/v4/pgxpool"

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")
)

// 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 {
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)
}

var _ Database = &stateDatabase{}

type stateDatabase struct {
pgdb *pgxpool.Pool
codeSizeCache *lru.Cache
codeCache *fastcache.Cache
}

// 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{
pgdb: pgDb,
codeSizeCache: csc,
codeCache: fastcache.New(codeCacheSize),
}, nil
}

// 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)
// NewPostgresDB returns a postgres.DB using the provided driver
func NewPostgresDB(driver Driver) *DB {
return &DB{driver}
}

// ContractCode satisfies Database, it returns the contract code for a given codehash
func (sd *stateDatabase) ContractCode(codeHash common.Hash) ([]byte, error) {
if code := sd.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
return code, nil
}
c, err := util.Keccak256ToCid(ipld.RawBinary, codeHash.Bytes())
if err != nil {
return nil, fmt.Errorf("cannot derive CID from provided codehash: %s", err.Error())
}
code := make([]byte, 0)
if err := sd.pgdb.QueryRow(context.Background(), GetContractCodePgStr, c).Scan(&code); err != nil {
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
// DB implements sql.Database using a configured driver and Postgres statement syntax
type DB struct {
Driver
}

// ContractCodeSize satisfies Database, it returns the length of the code for a provided codehash
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(codeHash)
return len(code), err
// GetContractCodeStmt satisfies the Statements interface
func (db *DB) GetContractCodeStmt() string {
return GetContractCodePgStr
}

// 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) {
res := StateAccountResult{}
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 {
return nil, err
}
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: common.HexToHash(res.CodeHash).Bytes(),
}, nil
// GetStateAccountStmt satisfies the Statements interface
func (db *DB) GetStateAccountStmt() string {
return GetStateAccount
}

// 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) {
res := StorageSlotResult{}
err := sd.pgdb.QueryRow(context.Background(), GetStorageSlot,
addressHash.Hex(), slotHash.Hex(), blockHash.Hex()).
Scan(&res.Value, &res.Removed, &res.StateLeafRemoved)
if err != nil {
return nil, err
}
if res.Removed || res.StateLeafRemoved {
// TODO: check expected behavior for deleted/non existing accounts
return nil, nil
}
return res.Value, nil
// GetStorageSlotStmt satisfies the Statements interface
func (db *DB) GetStorageSlotStmt() string {
return GetStorageSlot
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ require (
github.com/ethereum/go-ethereum v1.10.26
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/ipfs/go-cid v0.2.0
github.com/jackc/pgconn v1.14.0
github.com/jackc/pgx/v4 v4.18.1
github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.10.6
github.com/multiformats/go-multihash v0.1.0
github.com/stretchr/testify v1.8.1
)
Expand All @@ -25,7 +28,6 @@ require (
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-ipld-format v0.4.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
Expand Down
Loading

0 comments on commit c7d05bb

Please sign in to comment.