Support validator withdrawals (#265)

- Retrieves indexed withdrawal objects with blocks over RPC.
- Includes system-tests in CI workflow.
- Uses external fixturenet stack in CI tests.

Depends on
- cerc-io/ipld-eth-db#7
- cerc-io/plugeth-statediff#25

To induce and test validator withdrawals:
  - cerc-io/lighthouse#1
  - cerc-io/fixturenet-eth-stacks#19
  - cerc-io/system-tests#16

Reviewed-on: cerc-io/ipld-eth-server#265
This commit is contained in:
2024-08-05 15:50:53 +00:00
parent 688b5c817a
commit 190b36bcd1
16 changed files with 149 additions and 102 deletions
+2 -1
View File
@@ -1245,7 +1245,8 @@ func (pea *PublicEthAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx boo
if inclTx {
td, err := pea.B.GetTd(b.Hash())
if err != nil {
log.Errorf("error getting td for block with hash and number %s, %s: %s", b.Hash().String(), b.Number().String(), err)
err = fmt.Errorf("error getting TD for block at (%s, %s): %s", b.Number(), b.Hash(), err)
log.Error(err)
return nil, err
}
fields["totalDifficulty"] = (*hexutil.Big)(td)
+2 -2
View File
@@ -50,7 +50,7 @@ var (
blockHash = test_helpers.MockBlock.Header().Hash()
baseFee = test_helpers.MockLondonBlock.BaseFee()
ctx = context.Background()
chainConfig = &*params.TestChainConfig
chainConfig = &*params.MergedTestChainConfig
expectedBlock = map[string]interface{}{
"number": (*hexutil.Big)(test_helpers.MockBlock.Number()),
@@ -390,7 +390,7 @@ var _ = Describe("API", func() {
Expect(block).To(BeZero())
})
It("Fetch BaseFee from london block by block hash, returns `nil` for legacy block", func() {
block, err := api.GetBlockByHash(ctx, test_helpers.MockBlock.Hash(), true)
block, err := api.GetBlockByHash(ctx, test_helpers.MockBlock.Hash(), false)
Expect(err).ToNot(HaveOccurred())
_, ok := block["baseFeePerGas"]
Expect(ok).To(Equal(false))
+26 -4
View File
@@ -271,7 +271,7 @@ func (b *Backend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.Blo
func (b *Backend) BlockByNumber(ctx context.Context, blockNumber rpc.BlockNumber) (*types.Block, error) {
number, err := b.NormalizeBlockNumber(blockNumber)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to normalize block number: %w", err)
}
canonicalHash, err := b.GetCanonicalHash(uint64(number))
if err != nil {
@@ -349,11 +349,16 @@ func (b *Backend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Blo
return nil, err
}
// Placeholder for withdrawal processing (TODO: https://git.vdb.to/cerc-io/ipld-eth-server/pulls/265)
// Fetch withdrawals
var withdrawals types.Withdrawals
if b.Config.ChainConfig.IsShanghai(header.Number, header.Time) {
// All blocks after Shanghai must include a withdrawals root.
withdrawals = make(types.Withdrawals, 0)
withdrawals, err = b.GetWithdrawals(tx, hash, blockNumber)
if err != nil && err != sql.ErrNoRows {
log.Error("error fetching withdrawals: ", err)
return nil, err
}
} else if len(withdrawals) > 0 {
return nil, errors.New("withdrawals set before Shanghai activation")
}
// Compose everything together into a complete block
@@ -501,6 +506,23 @@ func (b *Backend) GetReceiptsByBlockHashAndNumber(tx *sqlx.Tx, hash common.Hash,
return rcts, nil
}
// GetWithdrawals retrieves transactions for a provided block hash and number
func (b *Backend) GetWithdrawals(tx *sqlx.Tx, hash common.Hash, number uint64) (types.Withdrawals, error) {
_, rlpBytes, err := b.Retriever.RetrieveWithdrawals(tx, hash, number)
if err != nil {
return nil, err
}
withdrawals := make(types.Withdrawals, len(rlpBytes))
for i, bytes := range rlpBytes {
withdrawals[i] = new(types.Withdrawal)
if err := rlp.DecodeBytes(bytes, withdrawals[i]); err != nil {
return nil, err
}
}
return withdrawals, nil
}
// GetTransaction retrieves a tx by hash
// It also returns the blockhash, blocknumber, and tx index associated with the transaction
func (b *Backend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
+18
View File
@@ -506,6 +506,24 @@ func (r *Retriever) RetrieveReceiptsByBlockHash(tx *sqlx.Tx, hash common.Hash) (
return cids, rcts, txs, nil
}
// RetrieveWithdrawals returns the CIDs and RLP bytes for the withdrawals corresponding to the
// provided block hash, number. Returned CIDs correspond to the leaf node data which contains the
// withdrawal object.
func (r *Retriever) RetrieveWithdrawals(tx *sqlx.Tx, hash common.Hash, number uint64) ([]string, [][]byte, error) {
results := make([]ipldResult, 0)
if err := tx.Select(&results, RetrieveWithdrawalsPgStr, hash.Hex(), number); err != nil {
return nil, nil, err
}
cids := make([]string, len(results))
withdrawals := make([][]byte, len(results))
for i, res := range results {
cids[i] = res.CID
withdrawals[i] = res.Data
}
return cids, withdrawals, nil
}
// RetrieveAccountByAddressAndBlockHash returns the cid and rlp bytes for the account corresponding to the provided address and block hash
// TODO: ensure this handles deleted accounts appropriately
func (r *Retriever) RetrieveAccountByAddressAndBlockHash(address common.Address, hash common.Hash) (StateAccountRecord, error) {
+15
View File
@@ -107,6 +107,21 @@ WHERE header_cids.block_hash = $1
AND blocks.key = receipt_cids.cid
ORDER BY eth.transaction_cids.index ASC
`
RetrieveWithdrawalsPgStr = `
SELECT withdrawal_cids.cid,
blocks.data
FROM eth.withdrawal_cids
JOIN eth.header_cids
ON header_cids.block_hash = $1
AND header_cids.block_number = $2
AND header_cids.canonical
AND withdrawal_cids.block_number = header_cids.block_number
AND withdrawal_cids.header_id = header_cids.block_hash
JOIN ipld.blocks
ON blocks.block_number = header_cids.block_number
AND blocks.key = withdrawal_cids.cid
ORDER BY eth.withdrawal_cids.index ASC`
RetrieveAccountByLeafKeyAndBlockHashPgStr = `
SELECT state_cids.nonce,
state_cids.balance,
+10 -6
View File
@@ -53,7 +53,11 @@ var (
Extra: []byte{},
}
MockTransactions, MockReceipts, SenderAddr = createLegacyTransactionsAndReceipts()
MockUncles = []*types.Header{
MockWithdrawals = types.Withdrawals{
{Index: 0, Validator: 1, Address: Address, Amount: 1000000000},
{Index: 1, Validator: 5, Address: AnotherAddress, Amount: 2000000000},
}
MockUncles = []*types.Header{
{
Time: 1,
Number: big.NewInt(BlockNumber1 + 1),
@@ -75,7 +79,7 @@ var (
ParentHash: Genesis.Hash(),
},
}
MockBlock = createNewBlock(&MockHeader, MockTransactions, MockUncles, MockReceipts, trie.NewEmpty(nil))
MockBlock = createNewBlock(&MockHeader, MockTransactions, MockUncles, MockReceipts, nil, trie.NewEmpty(nil))
MockChildHeader = types.Header{
Time: 0,
Number: big.NewInt(BlockNumber1 + 1),
@@ -326,11 +330,11 @@ var (
Extra: []byte{},
},
}
MockLondonBlock = createNewBlock(&MockLondonHeader, MockLondonTransactions, MockLondonUncles, MockLondonReceipts, trie.NewEmpty(nil))
MockLondonBlock = createNewBlock(&MockLondonHeader, MockLondonTransactions, MockLondonUncles, MockLondonReceipts, MockWithdrawals, trie.NewEmpty(nil))
)
func createNewBlock(header *types.Header, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, hasher types.TrieHasher) *types.Block {
block := types.NewBlock(header, txs, uncles, receipts, hasher)
func createNewBlock(header *types.Header, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals types.Withdrawals, hasher types.TrieHasher) *types.Block {
block := types.NewBlockWithWithdrawals(header, txs, uncles, receipts, withdrawals, hasher)
bHash := block.Hash()
for _, r := range receipts {
for _, l := range r.Logs {
@@ -455,7 +459,7 @@ func createLegacyTransactionsAndReceipts() (types.Transactions, types.Receipts,
func getReceiptCIDs(rcts []*types.Receipt) ([]cid.Cid, error) {
cids := make([]cid.Cid, len(rcts))
for i, rct := range rcts {
ethRct, err := ipld.NewReceipt(rct)
ethRct, err := ipld.EncodeReceipt(rct)
if err != nil {
return nil, err
}
+2 -24
View File
@@ -27,6 +27,7 @@ import (
"github.com/cerc-io/plugeth-statediff/indexer/interfaces"
"github.com/cerc-io/plugeth-statediff/indexer/models"
"github.com/cerc-io/plugeth-statediff/indexer/node"
"github.com/cerc-io/plugeth-statediff/indexer/test_helpers"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/jmoiron/sqlx"
@@ -55,30 +56,7 @@ func SetupDB() *sqlx.DB {
// TearDownDB is used to tear down the watcher dbs after tests
func TearDownDB(db *sqlx.DB) {
tx, err := db.Beginx()
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM nodes`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM ipld.blocks`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.header_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.uncle_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.transaction_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.receipt_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.state_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.storage_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth.log_cids`)
Expect(err).NotTo(HaveOccurred())
_, err = tx.Exec(`DELETE FROM eth_meta.watched_addresses`)
Expect(err).NotTo(HaveOccurred())
err = tx.Commit()
err := test_helpers.ClearSqlxDB(db)
Expect(err).NotTo(HaveOccurred())
}