beacon/engine: don't omit empty withdrawals in ExecutionPayloadBodies (#26698)

This ensures the "withdrawals" field will always be present in responses
to getPayloadBodiesByRangeV1 and getPayloadBodiesByHashV1.

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Marius van der Wijden 2023-03-07 16:30:04 +01:00 committed by GitHub
parent 1e3177de22
commit 78429f7733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 33 deletions

View File

@ -233,5 +233,5 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1 // ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
type ExecutionPayloadBodyV1 struct { type ExecutionPayloadBodyV1 struct {
TransactionData []hexutil.Bytes `json:"transactions"` TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
} }

View File

@ -19,8 +19,11 @@ package catalyst
import ( import (
"bytes" "bytes"
"context" "context"
crand "crypto/rand"
"fmt" "fmt"
"math/big" "math/big"
"math/rand"
"reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -41,7 +44,6 @@ import (
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -473,18 +475,21 @@ func TestFullAPI(t *testing.T) {
ethservice.TxPool().AddLocal(tx) ethservice.TxPool().AddLocal(tx)
} }
setupBlocks(t, ethservice, 10, parent, callback) setupBlocks(t, ethservice, 10, parent, callback, nil)
} }
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header)) []*types.Header { func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal) []*types.Header {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
var blocks []*types.Header var blocks []*types.Header
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
callback(parent) callback(parent)
var w []*types.Withdrawal
if withdrawals != nil {
w = withdrawals[i]
}
payload := getNewPayload(t, api, parent) payload := getNewPayload(t, api, parent, w)
execResp, err := api.NewPayloadV2(*payload)
execResp, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
t.Fatalf("can't execute payload: %v", err) t.Fatalf("can't execute payload: %v", err)
} }
@ -676,10 +681,10 @@ func TestEmptyBlocks(t *testing.T) {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}) setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
// (1) check LatestValidHash by sending a normal payload (P1'') // (1) check LatestValidHash by sending a normal payload (P1'')
payload := getNewPayload(t, api, commonAncestor) payload := getNewPayload(t, api, commonAncestor, nil)
status, err := api.NewPayloadV1(*payload) status, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
@ -693,7 +698,7 @@ func TestEmptyBlocks(t *testing.T) {
} }
// (2) Now send P1' which is invalid // (2) Now send P1' which is invalid
payload = getNewPayload(t, api, commonAncestor) payload = getNewPayload(t, api, commonAncestor, nil)
payload.GasUsed += 1 payload.GasUsed += 1
payload = setBlockhash(payload) payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor // Now latestValidHash should be the common ancestor
@ -711,7 +716,7 @@ func TestEmptyBlocks(t *testing.T) {
} }
// (3) Now send a payload with unknown parent // (3) Now send a payload with unknown parent
payload = getNewPayload(t, api, commonAncestor) payload = getNewPayload(t, api, commonAncestor, nil)
payload.ParentHash = common.Hash{1} payload.ParentHash = common.Hash{1}
payload = setBlockhash(payload) payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor // Now latestValidHash should be the common ancestor
@ -727,11 +732,12 @@ func TestEmptyBlocks(t *testing.T) {
} }
} }
func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header) *engine.ExecutableData { func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal) *engine.ExecutableData {
params := engine.PayloadAttributes{ params := engine.PayloadAttributes{
Timestamp: parent.Time + 1, Timestamp: parent.Time + 1,
Random: crypto.Keccak256Hash([]byte{byte(1)}), Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase, SuggestedFeeRecipient: parent.Coinbase,
Withdrawals: withdrawals,
} }
payload, err := assembleBlock(api, parent.Hash(), &params) payload, err := assembleBlock(api, parent.Hash(), &params)
@ -799,7 +805,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
commonAncestor := ethserviceA.BlockChain().CurrentBlock() commonAncestor := ethserviceA.BlockChain().CurrentBlock()
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}) setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil)
commonAncestor = ethserviceA.BlockChain().CurrentBlock() commonAncestor = ethserviceA.BlockChain().CurrentBlock()
var invalidChain []*engine.ExecutableData var invalidChain []*engine.ExecutableData
@ -808,7 +814,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
//invalidChain = append(invalidChain, payload1) //invalidChain = append(invalidChain, payload1)
// create an invalid payload2 (P2) // create an invalid payload2 (P2)
payload2 := getNewPayload(t, apiA, commonAncestor) payload2 := getNewPayload(t, apiA, commonAncestor, nil)
//payload2.ParentHash = payload1.BlockHash //payload2.ParentHash = payload1.BlockHash
payload2.GasUsed += 1 payload2.GasUsed += 1
payload2 = setBlockhash(payload2) payload2 = setBlockhash(payload2)
@ -817,7 +823,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
head := payload2 head := payload2
// create some valid payloads on top // create some valid payloads on top
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
payload := getNewPayload(t, apiA, commonAncestor) payload := getNewPayload(t, apiA, commonAncestor, nil)
payload.ParentHash = head.BlockHash payload.ParentHash = head.BlockHash
payload = setBlockhash(payload) payload = setBlockhash(payload)
invalidChain = append(invalidChain, payload) invalidChain = append(invalidChain, payload)
@ -855,10 +861,10 @@ func TestInvalidBloom(t *testing.T) {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain // Setup 10 blocks on the canonical chain
setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}) setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil)
// (1) check LatestValidHash by sending a normal payload (P1'') // (1) check LatestValidHash by sending a normal payload (P1'')
payload := getNewPayload(t, api, commonAncestor) payload := getNewPayload(t, api, commonAncestor, nil)
payload.LogsBloom = append(payload.LogsBloom, byte(1)) payload.LogsBloom = append(payload.LogsBloom, byte(1))
status, err := api.NewPayloadV1(*payload) status, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
@ -1233,8 +1239,10 @@ func TestNilWithdrawals(t *testing.T) {
} }
func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
genesis, preMergeBlocks := generateMergeChain(10, false) genesis, blocks := generateMergeChain(10, true)
n, ethservice := startEthService(t, genesis, preMergeBlocks) n, ethservice := startEthService(t, genesis, blocks)
// enable shanghai on the last block
ethservice.BlockChain().Config().ShanghaiTime = &blocks[len(blocks)-1].Header().Time
var ( var (
parent = ethservice.BlockChain().CurrentBlock() parent = ethservice.BlockChain().CurrentBlock()
@ -1249,12 +1257,38 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
ethservice.TxPool().AddLocal(tx) ethservice.TxPool().AddLocal(tx)
} }
postMergeHeaders := setupBlocks(t, ethservice, 10, parent, callback) withdrawals := make([][]*types.Withdrawal, 10)
postMergeBlocks := make([]*types.Block, len(postMergeHeaders)) withdrawals[0] = nil // should be filtered out by miner
for i, header := range postMergeHeaders { withdrawals[1] = make([]*types.Withdrawal, 0)
postMergeBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64()) for i := 2; i < len(withdrawals); i++ {
addr := make([]byte, 20)
crand.Read(addr)
withdrawals[i] = []*types.Withdrawal{
{Index: rand.Uint64(), Validator: rand.Uint64(), Amount: rand.Uint64(), Address: common.BytesToAddress(addr)},
} }
return n, ethservice, append(preMergeBlocks, postMergeBlocks...) }
postShanghaiHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals)
postShanghaiBlocks := make([]*types.Block, len(postShanghaiHeaders))
for i, header := range postShanghaiHeaders {
postShanghaiBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
}
return n, ethservice, append(blocks, postShanghaiBlocks...)
}
func allHashes(blocks []*types.Block) []common.Hash {
var hashes []common.Hash
for _, b := range blocks {
hashes = append(hashes, b.Hash())
}
return hashes
}
func allBodies(blocks []*types.Block) []*types.Body {
var bodies []*types.Body
for _, b := range blocks {
bodies = append(bodies, b.Body())
}
return bodies
} }
func TestGetBlockBodiesByHash(t *testing.T) { func TestGetBlockBodiesByHash(t *testing.T) {
@ -1296,6 +1330,11 @@ func TestGetBlockBodiesByHash(t *testing.T) {
results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()}, results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()},
hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()}, hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()},
}, },
// all blocks
{
results: allBodies(blocks),
hashes: allHashes(blocks),
},
} }
for k, test := range tests { for k, test := range tests {
@ -1364,6 +1403,12 @@ func TestGetBlockBodiesByRange(t *testing.T) {
start: 22, start: 22,
count: 2, count: 2,
}, },
// allBlocks
{
results: allBodies(blocks),
start: 1,
count: hexutil.Uint64(len(blocks)),
},
} }
for k, test := range tests { for k, test := range tests {
@ -1434,15 +1479,14 @@ func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool {
} else if a == nil || b == nil { } else if a == nil || b == nil {
return false return false
} }
var want []hexutil.Bytes if len(a.Transactions) != len(b.TransactionData) {
for _, tx := range a.Transactions {
data, _ := tx.MarshalBinary()
want = append(want, hexutil.Bytes(data))
}
aBytes, errA := rlp.EncodeToBytes(want)
bBytes, errB := rlp.EncodeToBytes(b.TransactionData)
if errA != errB {
return false return false
} }
return bytes.Equal(aBytes, bBytes) for i, tx := range a.Transactions {
data, _ := tx.MarshalBinary()
if !bytes.Equal(data, b.TransactionData[i]) {
return false
}
}
return reflect.DeepEqual(a.Withdrawals, b.Withdrawals)
} }