ipld-eth-statedb/direct_by_leaf/statedb_test.go
Roy Crihfield 761d60acdf Geth 1.13 (Deneb/Cancun) update (#5)
The Geth `core/state` and `trie` packages underwent a big refactor between `v1.11.6` and `1.13.14`.
This code, which was adapted from those, needed corresponding updates. To do this I applied the diff patches from Geth directly where possible and in some places had to clone new parts of the Geth code and adapt them.

In order to make this process as straightforward as possible in the future, I've attempted to minimize the number of changes vs. Geth and added some documentation in the `trie_by_cid` package.

Reviewed-on: #5
2024-05-29 10:00:12 +00:00

462 lines
15 KiB
Go

package state_test
import (
"context"
"math/big"
"testing"
"github.com/holiman/uint256"
"github.com/lib/pq"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/require"
"github.com/cerc-io/plugeth-statediff/indexer/database/sql/postgres"
"github.com/cerc-io/plugeth-statediff/indexer/ipld"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
state "github.com/cerc-io/ipld-eth-statedb/direct_by_leaf"
util "github.com/cerc-io/ipld-eth-statedb/internal"
"github.com/cerc-io/ipld-eth-statedb/sql"
)
var (
testCtx = context.Background()
teardownStatements = []string{
`TRUNCATE eth.header_cids`,
`TRUNCATE eth.state_cids`,
`TRUNCATE eth.storage_cids`,
`TRUNCATE ipld.blocks`,
}
// Fixture data
// block one: contract account and slot are created
// block two: slot is emptied
// block three: slot has a new value added
// block four: non-canonical block with non-canonical slot value is added to the database
// block five: entire contract is destructed; another non-canonical block is created but it doesn't include an update for our slot
// but it links back to the other non-canonical header (this is to test the ability to resolve canonicity by comparing how many
// children reference back to a header)
// block six: canonical block only, no relevant state changes (check that we still return emptied result at heights where it wasn't emptied)
BlockNumber = big.NewInt(1337)
Header = types.Header{Number: BlockNumber}
BlockHash = Header.Hash()
BlockHash2 = crypto.Keccak256Hash([]byte("I am a random hash"))
BlockNumber2 = BlockNumber.Uint64() + 1
BlockHash3 = crypto.Keccak256Hash([]byte("I am another random hash"))
BlockNumber3 = BlockNumber.Uint64() + 2
BlockHash4 = crypto.Keccak256Hash([]byte("I am"))
BlockNumber4 = BlockNumber.Uint64() + 3
BlockHash5 = crypto.Keccak256Hash([]byte("I"))
BlockNumber5 = BlockNumber.Uint64() + 4
BlockHash6 = crypto.Keccak256Hash([]byte("am"))
BlockNumber6 = BlockNumber.Uint64() + 5
BlockParentHash = common.HexToHash("0123456701234567012345670123456701234567012345670123456701234567")
NonCanonicalHash4 = crypto.Keccak256Hash([]byte("I am a random non canonical hash"))
NonCanonicalHash5 = crypto.Keccak256Hash([]byte("I am also a random non canonical hash"))
AccountPK, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
AccountAddress = crypto.PubkeyToAddress(AccountPK.PublicKey) //0x703c4b2bD70c169f5717101CaeE543299Fc946C7
AccountLeafKey = crypto.Keccak256Hash(AccountAddress[:])
AccountCode = []byte{0, 1, 2, 3, 4, 5, 6, 7}
AccountCodeHash = crypto.Keccak256Hash(AccountCode)
Account = types.StateAccount{
Nonce: uint64(0),
Balance: uint256.NewInt(1000),
CodeHash: AccountCodeHash.Bytes(),
Root: common.Hash{},
}
StorageSlot = common.HexToHash("0")
StorageLeafKey = crypto.Keccak256Hash(StorageSlot[:])
StoredValue = crypto.Keccak256Hash([]byte{1, 2, 3, 4, 5})
StoragePartialPath = []byte{0, 1, 0, 2, 0, 4}
// Encoded data
accountRLP, _ = rlp.EncodeToBytes(&Account)
accountAndLeafRLP, _ = rlp.EncodeToBytes(&[]interface{}{AccountLeafKey, accountRLP})
AccountCID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, accountAndLeafRLP, multihash.KECCAK_256)
AccountCodeCID, _ = util.Keccak256ToCid(ipld.RawBinary, AccountCodeHash[:])
StoredValueRLP, _ = rlp.EncodeToBytes(StoredValue)
StoredValueRLP2, _ = rlp.EncodeToBytes("something")
NonCanonStoredValueRLP, _ = rlp.EncodeToBytes("something else")
StorageRLP, _ = rlp.EncodeToBytes(&[]interface{}{StoragePartialPath, StoredValueRLP})
StorageCID, _ = ipld.RawdataToCid(ipld.MEthStorageTrie, StorageRLP, multihash.KECCAK_256)
RemovedNodeStateCID = "baglacgzayxjemamg64rtzet6pwznzrydydsqbnstzkbcoo337lmaixmfurya"
RemovedNodeStorageCID = "bagmacgzayxjemamg64rtzet6pwznzrydydsqbnstzkbcoo337lmaixmfurya"
)
func TestPGXSuite(t *testing.T) {
testConfig, err := postgres.TestConfig.WithEnv()
require.NoError(t, err)
pool, err := postgres.ConnectPGX(testCtx, testConfig)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
tx, err := pool.Begin(testCtx)
require.NoError(t, err)
for _, stm := range teardownStatements {
_, err = tx.Exec(testCtx, stm)
require.NoErrorf(t, err, "Exec(`%s`)", stm)
}
require.NoError(t, tx.Commit(testCtx))
})
database := sql.NewPGXDriverFromPool(context.Background(), pool)
insertSuiteData(t, database)
db := state.NewDatabase(database)
require.NoError(t, err)
testSuite(t, db)
}
func TestSQLXSuite(t *testing.T) {
testConfig, err := postgres.TestConfig.WithEnv()
require.NoError(t, err)
pool, err := postgres.ConnectSQLX(testCtx, testConfig)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
tx, err := pool.Begin()
require.NoError(t, err)
for _, stm := range teardownStatements {
_, err = tx.Exec(stm)
require.NoErrorf(t, err, "Exec(`%s`)", stm)
}
require.NoError(t, tx.Commit())
})
database := sql.NewSQLXDriverFromPool(context.Background(), pool)
insertSuiteData(t, database)
db := state.NewDatabase(database)
require.NoError(t, err)
testSuite(t, db)
}
func insertSuiteData(t *testing.T, database sql.Database) {
require.NoError(t, insertHeaderCID(database, BlockHash.String(), BlockParentHash.String(), BlockNumber.Uint64(), true))
require.NoError(t, insertHeaderCID(database, BlockHash2.String(), BlockHash.String(), BlockNumber2, true))
require.NoError(t, insertHeaderCID(database, BlockHash3.String(), BlockHash2.String(), BlockNumber3, true))
require.NoError(t, insertHeaderCID(database, BlockHash4.String(), BlockHash3.String(), BlockNumber4, true))
require.NoError(t, insertHeaderCID(database, NonCanonicalHash4.String(), BlockHash3.String(), BlockNumber4, false))
require.NoError(t, insertHeaderCID(database, BlockHash5.String(), BlockHash4.String(), BlockNumber5, true))
require.NoError(t, insertHeaderCID(database, NonCanonicalHash5.String(), NonCanonicalHash4.String(), BlockNumber5, false))
require.NoError(t, insertHeaderCID(database, BlockHash6.String(), BlockHash5.String(), BlockNumber6, true))
require.NoError(t, insertStateCID(database, stateModel{
BlockNumber: BlockNumber.Uint64(),
BlockHash: BlockHash.String(),
LeafKey: AccountLeafKey.String(),
CID: AccountCID.String(),
Diff: true,
Balance: Account.Balance.Uint64(),
Nonce: Account.Nonce,
CodeHash: AccountCodeHash.String(),
StorageRoot: Account.Root.String(),
Removed: false,
}))
require.NoError(t, insertStateCID(database, stateModel{
BlockNumber: BlockNumber4,
BlockHash: NonCanonicalHash4.String(),
LeafKey: AccountLeafKey.String(),
CID: AccountCID.String(),
Diff: true,
Balance: big.NewInt(123).Uint64(),
Nonce: Account.Nonce,
CodeHash: AccountCodeHash.String(),
StorageRoot: Account.Root.String(),
Removed: false,
}))
require.NoError(t, insertStateCID(database, stateModel{
BlockNumber: BlockNumber5,
BlockHash: BlockHash5.String(),
LeafKey: AccountLeafKey.String(),
CID: RemovedNodeStateCID,
Diff: true,
Removed: true,
}))
require.NoError(t, insertStorageCID(database, storageModel{
BlockNumber: BlockNumber.Uint64(),
BlockHash: BlockHash.String(),
LeafKey: AccountLeafKey.String(),
StorageLeafKey: StorageLeafKey.String(),
StorageCID: StorageCID.String(),
Diff: true,
Value: StoredValueRLP,
Removed: false,
}))
require.NoError(t, insertStorageCID(database, storageModel{
BlockNumber: BlockNumber2,
BlockHash: BlockHash2.String(),
LeafKey: AccountLeafKey.String(),
StorageLeafKey: StorageLeafKey.String(),
StorageCID: RemovedNodeStorageCID,
Diff: true,
Value: []byte{},
Removed: true,
}))
require.NoError(t, insertStorageCID(database, storageModel{
BlockNumber: BlockNumber3,
BlockHash: BlockHash3.String(),
LeafKey: AccountLeafKey.String(),
StorageLeafKey: StorageLeafKey.String(),
StorageCID: StorageCID.String(),
Diff: true,
Value: StoredValueRLP2,
Removed: false,
}))
require.NoError(t, insertStorageCID(database, storageModel{
BlockNumber: BlockNumber4,
BlockHash: NonCanonicalHash4.String(),
LeafKey: AccountLeafKey.String(),
StorageLeafKey: StorageLeafKey.String(),
StorageCID: StorageCID.String(),
Diff: true,
Value: NonCanonStoredValueRLP,
Removed: false,
}))
require.NoError(t, insertContractCode(database))
}
func testSuite(t *testing.T, db state.Database) {
t.Run("Database", func(t *testing.T) {
size, err := db.ContractCodeSize(AccountCodeHash)
require.NoError(t, err)
require.Equal(t, len(AccountCode), size)
code, err := db.ContractCode(AccountCodeHash)
require.NoError(t, err)
require.Equal(t, AccountCode, code)
acct, err := db.StateAccount(AccountLeafKey, BlockHash)
require.NoError(t, err)
require.Equal(t, &Account, acct)
acct2, err := db.StateAccount(AccountLeafKey, BlockHash2)
require.NoError(t, err)
require.Equal(t, &Account, acct2)
acct3, err := db.StateAccount(AccountLeafKey, BlockHash3)
require.NoError(t, err)
require.Equal(t, &Account, acct3)
// check that we don't get the non-canonical account
acct4, err := db.StateAccount(AccountLeafKey, BlockHash4)
require.NoError(t, err)
require.Equal(t, &Account, acct4)
acct5, err := db.StateAccount(AccountLeafKey, BlockHash5)
require.NoError(t, err)
require.Nil(t, acct5)
acct6, err := db.StateAccount(AccountLeafKey, BlockHash6)
require.NoError(t, err)
require.Nil(t, acct6)
val, err := db.StorageValue(AccountLeafKey, StorageLeafKey, BlockHash)
require.NoError(t, err)
require.Equal(t, StoredValueRLP, val)
val2, err := db.StorageValue(AccountLeafKey, StorageLeafKey, BlockHash2)
require.NoError(t, err)
require.Nil(t, val2)
val3, err := db.StorageValue(AccountLeafKey, StorageLeafKey, BlockHash3)
require.NoError(t, err)
require.Equal(t, StoredValueRLP2, val3)
// this checks that we don't get the non-canonical result
val4, err := db.StorageValue(AccountLeafKey, StorageLeafKey, BlockHash4)
require.NoError(t, err)
require.Equal(t, StoredValueRLP2, val4)
// this checks that when the entire account was deleted, we return nil result for storage slot
val5, err := db.StorageValue(AccountLeafKey, StorageLeafKey, BlockHash5)
require.NoError(t, err)
require.Nil(t, val5)
val6, err := db.StorageValue(AccountLeafKey, StorageLeafKey, BlockHash6)
require.NoError(t, err)
require.Nil(t, val6)
})
t.Run("StateDB", func(t *testing.T) {
sdb, err := state.New(BlockHash, db)
require.NoError(t, err)
checkAccountUnchanged := func() {
require.Equal(t, Account.Balance, sdb.GetBalance(AccountAddress))
require.Equal(t, Account.Nonce, sdb.GetNonce(AccountAddress))
require.Equal(t, StoredValue, sdb.GetState(AccountAddress, StorageSlot))
require.Equal(t, AccountCodeHash, sdb.GetCodeHash(AccountAddress))
require.Equal(t, AccountCode, sdb.GetCode(AccountAddress))
require.Equal(t, len(AccountCode), sdb.GetCodeSize(AccountAddress))
}
require.True(t, sdb.Exist(AccountAddress))
checkAccountUnchanged()
id := sdb.Snapshot()
newStorage := crypto.Keccak256Hash([]byte{5, 4, 3, 2, 1})
newCode := []byte{1, 3, 3, 7}
sdb.SetBalance(AccountAddress, uint256.NewInt(300))
sdb.AddBalance(AccountAddress, uint256.NewInt(200))
sdb.SubBalance(AccountAddress, uint256.NewInt(100))
sdb.SetNonce(AccountAddress, 42)
sdb.SetState(AccountAddress, StorageSlot, newStorage)
sdb.SetCode(AccountAddress, newCode)
require.Equal(t, uint256.NewInt(400), sdb.GetBalance(AccountAddress))
require.Equal(t, uint64(42), sdb.GetNonce(AccountAddress))
require.Equal(t, newStorage, sdb.GetState(AccountAddress, StorageSlot))
require.Equal(t, newCode, sdb.GetCode(AccountAddress))
sdb.AddSlotToAccessList(AccountAddress, StorageSlot)
require.True(t, sdb.AddressInAccessList(AccountAddress))
hasAddr, hasSlot := sdb.SlotInAccessList(AccountAddress, StorageSlot)
require.True(t, hasAddr)
require.True(t, hasSlot)
sdb.RevertToSnapshot(id)
checkAccountUnchanged()
require.False(t, sdb.AddressInAccessList(AccountAddress))
hasAddr, hasSlot = sdb.SlotInAccessList(AccountAddress, StorageSlot)
require.False(t, hasAddr)
require.False(t, hasSlot)
})
}
func insertHeaderCID(db sql.Database, blockHash, parentHash string, blockNumber uint64, canon bool) error {
cid, err := util.Keccak256ToCid(ipld.MEthHeader, common.HexToHash(blockHash).Bytes())
if err != nil {
return err
}
sql := `INSERT INTO eth.header_cids (
block_number,
block_hash,
parent_hash,
cid,
td,
node_ids,
reward,
state_root,
tx_root,
receipt_root,
uncles_hash,
bloom,
timestamp,
coinbase,
canonical
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`
_, err = db.Exec(testCtx, sql,
blockNumber,
blockHash,
parentHash,
cid.String(),
0, pq.StringArray([]string{}), 0,
Header.Root.String(),
Header.TxHash.String(),
Header.ReceiptHash.String(),
Header.UncleHash.String(),
[]byte{},
Header.Time,
Header.Coinbase.String(),
canon,
)
return err
}
type stateModel struct {
BlockNumber uint64
BlockHash string
LeafKey string
CID string
Diff bool
Balance uint64
Nonce uint64
CodeHash string
StorageRoot string
Removed bool
}
func insertStateCID(db sql.Database, cidModel stateModel) error {
sql := `INSERT INTO eth.state_cids (
block_number,
header_id,
state_leaf_key,
cid,
diff,
balance,
nonce,
code_hash,
storage_root,
removed
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
_, err := db.Exec(testCtx, sql,
cidModel.BlockNumber,
cidModel.BlockHash,
cidModel.LeafKey,
cidModel.CID,
cidModel.Diff,
cidModel.Balance,
cidModel.Nonce,
cidModel.CodeHash,
cidModel.StorageRoot,
cidModel.Removed,
)
return err
}
type storageModel struct {
BlockNumber uint64
BlockHash string
LeafKey string
StorageLeafKey string
StorageCID string
Diff bool
Value []byte
Removed bool
}
func insertStorageCID(db sql.Database, cidModel storageModel) error {
sql := `INSERT INTO eth.storage_cids (
block_number,
header_id,
state_leaf_key,
storage_leaf_key,
cid,
diff,
val,
removed
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`
_, err := db.Exec(testCtx, sql,
cidModel.BlockNumber,
cidModel.BlockHash,
cidModel.LeafKey,
cidModel.StorageLeafKey,
cidModel.StorageCID,
cidModel.Diff,
cidModel.Value,
cidModel.Removed,
)
return err
}
func insertContractCode(db sql.Database) error {
sql := `INSERT INTO ipld.blocks (block_number, key, data) VALUES ($1, $2, $3)`
_, err := db.Exec(testCtx, sql, BlockNumber.Uint64(), AccountCodeCID.String(), AccountCode)
return err
}