Skip to content

Commit

Permalink
feat(eip-4895): withdrawals data storage
Browse files Browse the repository at this point in the history
Store the withdrawals (max. 16 per block) on the bodyForStorage.
  • Loading branch information
revitteth committed Dec 15, 2022
1 parent cf6173d commit d7d9249
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ tests/testdata/*

go.work
go.work.sum
erigon-lib/

/goerli

Expand Down
7 changes: 6 additions & 1 deletion core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,11 @@ func (g *Genesis) ToBlock() (*types.Block, *state.IntraBlockState, error) {
}
}

var withdrawals []*types.Withdrawal
if g.Config != nil && (g.Config.IsShanghai(g.Timestamp)) {
withdrawals = []*types.Withdrawal{}
}

var root common.Hash
var statedb *state.IntraBlockState
wg := sync.WaitGroup{}
Expand Down Expand Up @@ -431,7 +436,7 @@ func (g *Genesis) ToBlock() (*types.Block, *state.IntraBlockState, error) {

head.Root = root

return types.NewBlock(head, nil, nil, nil, nil), statedb, nil
return types.NewBlock(head, nil, nil, nil, withdrawals), statedb, nil
}

func (g *Genesis) WriteGenesisState(tx kv.RwTx) (*types.Block, *state.IntraBlockState, error) {
Expand Down
18 changes: 11 additions & 7 deletions core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,8 @@ func ReadBodyForStorageByKey(db kv.Getter, k []byte) (*types.BodyForStorage, err
return nil, nil
}
bodyForStorage := new(types.BodyForStorage)
if err := rlp.DecodeBytes(bodyRlp, bodyForStorage); err != nil {
s := rlp.NewStream(bytes.NewReader(bodyRlp), 0)
if err = bodyForStorage.DecodeRLP(s); err != nil {
return nil, err
}

Expand All @@ -624,6 +625,7 @@ func ReadBody(db kv.Getter, hash common.Hash, number uint64) (*types.Body, uint6
}
body := new(types.Body)
body.Uncles = bodyForStorage.Uncles
body.Withdrawals = bodyForStorage.Withdrawals

if bodyForStorage.TxAmount < 2 {
panic(fmt.Sprintf("block body hash too few txs amount: %d, %d", number, bodyForStorage.TxAmount))
Expand Down Expand Up @@ -660,9 +662,10 @@ func WriteRawBody(db kv.RwTx, hash common.Hash, number uint64, body *types.RawBo
return false, 0, err
}
data := types.BodyForStorage{
BaseTxId: baseTxId,
TxAmount: uint32(len(body.Transactions)) + 2,
Uncles: body.Uncles,
BaseTxId: baseTxId,
TxAmount: uint32(len(body.Transactions)) + 2,
Uncles: body.Uncles,
Withdrawals: body.Withdrawals,
}
if err = WriteBodyForStorage(db, hash, number, &data); err != nil {
return false, 0, fmt.Errorf("WriteBodyForStorage: %w", err)
Expand All @@ -682,9 +685,10 @@ func WriteBody(db kv.RwTx, hash common.Hash, number uint64, body *types.Body) er
return err
}
data := types.BodyForStorage{
BaseTxId: baseTxId,
TxAmount: uint32(len(body.Transactions)) + 2,
Uncles: body.Uncles,
BaseTxId: baseTxId,
TxAmount: uint32(len(body.Transactions)) + 2,
Uncles: body.Uncles,
Withdrawals: body.Withdrawals,
}
if err := WriteBodyForStorage(db, hash, number, &data); err != nil {
return fmt.Errorf("failed to write body: %w", err)
Expand Down
180 changes: 180 additions & 0 deletions core/rawdb/accessors_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"math/big"
"testing"

"github.com/holiman/uint256"
"github.com/ledgerwatch/erigon-lib/kv/memdb"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/u256"
Expand Down Expand Up @@ -434,6 +435,185 @@ func TestBlockReceiptStorage(t *testing.T) {
}
}

// Tests block storage and retrieval operations with withdrawals.
func TestBlockWithdrawalsStorage(t *testing.T) {
_, tx := memdb.NewTestTx(t)
require := require.New(t)

// create fake withdrawals
w := types.Withdrawal{
Index: uint64(15),
Validator: uint64(5500),
Address: common.Address{0: 0xff},
Amount: *uint256.NewInt(1000),
}

w2 := types.Withdrawal{
Index: uint64(16),
Validator: uint64(5501),
Address: common.Address{0: 0xff},
Amount: *uint256.NewInt(1001),
}

withdrawals := make([]*types.Withdrawal, 0)
withdrawals = append(withdrawals, &w)
withdrawals = append(withdrawals, &w2)

// Create a test block to move around the database and make sure it's really new
block := types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
Extra: []byte("test block"),
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
ReceiptHash: types.EmptyRootHash,
})
if entry := ReadBlock(tx, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Non existent block returned: %v", entry)
}
if entry := ReadHeader(tx, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Non existent header returned: %v", entry)
}
if entry := ReadCanonicalBodyWithTransactions(tx, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Non existent body returned: %v", entry)
}

// Write withdrawals to block
wBlock := types.NewBlockFromStorage(block.Hash(), block.Header(), block.Transactions(), block.Uncles(), withdrawals)

if err := WriteBody(tx, wBlock.Hash(), wBlock.NumberU64(), wBlock.Body()); err != nil {
t.Fatalf("Could not write body: %v", err)
}

// Write and verify the block in the database
err := WriteBlock(tx, wBlock)
if err != nil {
t.Fatalf("Could not write block: %v", err)
}
if entry := ReadBlock(tx, block.Hash(), block.NumberU64()); entry == nil {
t.Fatalf("Stored block not found")
} else if entry.Hash() != block.Hash() {
t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block)
}
if entry := ReadHeader(tx, block.Hash(), block.NumberU64()); entry == nil {
t.Fatalf("Stored header not found")
} else if entry.Hash() != block.Hash() {
t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, block.Header())
}
if err := TruncateBlocks(context.Background(), tx, 2); err != nil {
t.Fatal(err)
}
entry := ReadCanonicalBodyWithTransactions(tx, block.Hash(), block.NumberU64())
if entry == nil {
t.Fatalf("Stored body not found")
} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(block.Transactions()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body())
}

// Verify withdrawals on block
readWithdrawals := entry.Withdrawals
if len(readWithdrawals) != len(withdrawals) {
t.Fatalf("Retrieved withdrawals mismatch: have %v, want %v", readWithdrawals, withdrawals)
}

rw := readWithdrawals[0]
rw2 := readWithdrawals[1]

require.NotNil(rw)
require.Equal(uint64(15), rw.Index)
require.Equal(uint64(5500), rw.Validator)
require.Equal(common.Address{0: 0xff}, rw.Address)
require.Equal(*uint256.NewInt(1000), rw.Amount)

require.NotNil(rw2)
require.Equal(uint64(16), rw2.Index)
require.Equal(uint64(5501), rw2.Validator)
require.Equal(common.Address{0: 0xff}, rw2.Address)
require.Equal(*uint256.NewInt(1001), rw2.Amount)

// Delete the block and verify the execution
if err := TruncateBlocks(context.Background(), tx, block.NumberU64()); err != nil {
t.Fatal(err)
}
//if err := DeleteBlock(tx, block.Hash(), block.NumberU64()); err != nil {
// t.Fatalf("Could not delete block: %v", err)
//}
if entry := ReadBlock(tx, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Deleted block returned: %v", entry)
}
if entry := ReadHeader(tx, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Deleted header returned: %v", entry)
}
if entry := ReadCanonicalBodyWithTransactions(tx, block.Hash(), block.NumberU64()); entry != nil {
t.Fatalf("Deleted body returned: %v", entry)
}

// write again and delete it as old one
if err := WriteBlock(tx, block); err != nil {
t.Fatalf("Could not write block: %v", err)
}
if _, _, err := DeleteAncientBlocks(tx, 0, 1); err != nil {
t.Fatal(err)
}
}

// Tests pre-shanghai body to make sure withdrawals doesn't panic
func TestPreShanghaiBodyNoPanicOnWithdrawals(t *testing.T) {
require := require.New(t)

const bodyRlp = "f902bef8bef85d0101019471562b71999873db5b286df957af199ec94617f701801ca023f4aad9a71341d2990012a732366c3bc8a4ce9ff54c05546a9487445ac67692a0290d3a1411c2a675a4c12c98af60e34ea4d689f0ddfe0250a9e09c0819dfe3bff85d0201029471562b71999873db5b286df957af199ec94617f701801ca0f824d7edc241758aca948ff34d3797e4e31003f76cc9e05fb9c19e967fc48113a070e1389f0fa23fe765a04b23e98f98db6d630e3a035c1c7c968142ababb85a1df901fbf901f8a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080808080808b7465737420686561646572a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"
bstring, _ := hex.DecodeString(bodyRlp)

body := new(types.Body)
rlp.DecodeBytes([]byte(bstring), body)

require.Nil(body.Withdrawals)
require.Equal(2, len(body.Transactions))
}

// Tests pre-shanghai bodyForStorage to make sure withdrawals doesn't panic
func TestPreShanghaiBodyForStorageNoPanicOnWithdrawals(t *testing.T) {
require := require.New(t)

const bodyForStorageRlp = "c38002c0"
bstring, _ := hex.DecodeString(bodyForStorageRlp)

body := new(types.BodyForStorage)
rlp.DecodeBytes([]byte(bstring), body)

require.Nil(body.Withdrawals)
require.Equal(uint32(2), body.TxAmount)
}

// Tests shanghai bodyForStorage to make sure withdrawals are present
func TestShanghaiBodyForStorageHasWithdrawals(t *testing.T) {
require := require.New(t)

const bodyForStorageRlp = "f83f8002c0f83adc0f82157c94ff000000000000000000000000000000000000008203e8dc1082157d94ff000000000000000000000000000000000000008203e9"
bstring, _ := hex.DecodeString(bodyForStorageRlp)

body := new(types.BodyForStorage)
rlp.DecodeBytes([]byte(bstring), body)

require.NotNil(body.Withdrawals)
require.Equal(2, len(body.Withdrawals))
require.Equal(uint32(2), body.TxAmount)
}

// Tests shanghai bodyForStorage to make sure when no withdrawals the slice is empty (not nil)
func TestShanghaiBodyForStorageNoWithdrawals(t *testing.T) {
require := require.New(t)

const bodyForStorageRlp = "c48002c0c0c0"
bstring, _ := hex.DecodeString(bodyForStorageRlp)

body := new(types.BodyForStorage)
rlp.DecodeBytes([]byte(bstring), body)

require.NotNil(body.Withdrawals)
require.Equal(0, len(body.Withdrawals))
require.Equal(uint32(2), body.TxAmount)
}

func checkReceiptsRLP(have, want types.Receipts) error {
if len(have) != len(want) {
return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want))
Expand Down
Loading

0 comments on commit d7d9249

Please sign in to comment.