eth/catalyst: implement engine_getPayloadBodiesByHash/Range methods (#26232)
This change implements engine_getPayloadBodiesByHash and engine_getPayloadBodiesByRange, according to the specification at https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#specification-4 . Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
parent
877d2174fb
commit
9826cd65bc
@ -229,3 +229,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
|
|||||||
}
|
}
|
||||||
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
|
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
|
||||||
|
type ExecutionPayloadBodyV1 struct {
|
||||||
|
TransactionData []hexutil.Bytes `json:"transactions"`
|
||||||
|
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
|
||||||
|
}
|
||||||
|
@ -88,6 +88,8 @@ var caps = []string{
|
|||||||
"engine_getPayloadV2",
|
"engine_getPayloadV2",
|
||||||
"engine_newPayloadV1",
|
"engine_newPayloadV1",
|
||||||
"engine_newPayloadV2",
|
"engine_newPayloadV2",
|
||||||
|
"engine_getPayloadBodiesByHashV1",
|
||||||
|
"engine_getPayloadBodiesByRangeV1",
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConsensusAPI struct {
|
type ConsensusAPI struct {
|
||||||
@ -756,3 +758,61 @@ func (api *ConsensusAPI) heartbeat() {
|
|||||||
func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
|
func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
|
||||||
return caps
|
return caps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPayloadBodiesV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
|
||||||
|
// of block bodies by the engine api.
|
||||||
|
func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*beacon.ExecutionPayloadBodyV1 {
|
||||||
|
var bodies = make([]*beacon.ExecutionPayloadBodyV1, len(hashes))
|
||||||
|
for i, hash := range hashes {
|
||||||
|
block := api.eth.BlockChain().GetBlockByHash(hash)
|
||||||
|
bodies[i] = getBody(block)
|
||||||
|
}
|
||||||
|
return bodies
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
|
||||||
|
// of block bodies by the engine api.
|
||||||
|
func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count uint64) ([]*beacon.ExecutionPayloadBodyV1, error) {
|
||||||
|
if start == 0 || count == 0 || count > 1024 {
|
||||||
|
return nil, beacon.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
|
||||||
|
}
|
||||||
|
// limit count up until current
|
||||||
|
current := api.eth.BlockChain().CurrentBlock().NumberU64()
|
||||||
|
end := start + count
|
||||||
|
if end > current {
|
||||||
|
end = current
|
||||||
|
}
|
||||||
|
var bodies []*beacon.ExecutionPayloadBodyV1
|
||||||
|
for i := start; i < end; i++ {
|
||||||
|
block := api.eth.BlockChain().GetBlockByNumber(i)
|
||||||
|
bodies = append(bodies, getBody(block))
|
||||||
|
}
|
||||||
|
return bodies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBody(block *types.Block) *beacon.ExecutionPayloadBodyV1 {
|
||||||
|
if block == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
body = block.Body()
|
||||||
|
txs = make([]hexutil.Bytes, len(body.Transactions))
|
||||||
|
withdrawals = body.Withdrawals
|
||||||
|
)
|
||||||
|
|
||||||
|
for j, tx := range body.Transactions {
|
||||||
|
data, _ := tx.MarshalBinary()
|
||||||
|
txs[j] = hexutil.Bytes(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-shanghai withdrawals MUST be set to empty slice instead of nil
|
||||||
|
if withdrawals == nil && block.Header().WithdrawalsHash != nil {
|
||||||
|
withdrawals = make([]*types.Withdrawal, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &beacon.ExecutionPayloadBodyV1{
|
||||||
|
TransactionData: txs,
|
||||||
|
Withdrawals: withdrawals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,6 +41,7 @@ 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"
|
||||||
)
|
)
|
||||||
@ -475,8 +476,9 @@ func TestFullAPI(t *testing.T) {
|
|||||||
setupBlocks(t, ethservice, 10, parent, callback)
|
setupBlocks(t, ethservice, 10, parent, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Block, callback func(parent *types.Block)) {
|
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Block, callback func(parent *types.Block)) []*types.Block {
|
||||||
api := NewConsensusAPI(ethservice)
|
api := NewConsensusAPI(ethservice)
|
||||||
|
var blocks []*types.Block
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
callback(parent)
|
callback(parent)
|
||||||
|
|
||||||
@ -504,7 +506,9 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Bl
|
|||||||
t.Fatal("Finalized block should be updated")
|
t.Fatal("Finalized block should be updated")
|
||||||
}
|
}
|
||||||
parent = ethservice.BlockChain().CurrentBlock()
|
parent = ethservice.BlockChain().CurrentBlock()
|
||||||
|
blocks = append(blocks, parent)
|
||||||
}
|
}
|
||||||
|
return blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExchangeTransitionConfig(t *testing.T) {
|
func TestExchangeTransitionConfig(t *testing.T) {
|
||||||
@ -1225,3 +1229,192 @@ func TestNilWithdrawals(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
|
||||||
|
genesis, preMergeBlocks := generateMergeChain(10, false)
|
||||||
|
n, ethservice := startEthService(t, genesis, preMergeBlocks)
|
||||||
|
|
||||||
|
var (
|
||||||
|
parent = ethservice.BlockChain().CurrentBlock()
|
||||||
|
// This EVM code generates a log when the contract is created.
|
||||||
|
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
||||||
|
)
|
||||||
|
|
||||||
|
callback := func(parent *types.Block) {
|
||||||
|
statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
|
||||||
|
nonce := statedb.GetNonce(testAddr)
|
||||||
|
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
|
||||||
|
ethservice.TxPool().AddLocal(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
postMergeBlocks := setupBlocks(t, ethservice, 10, parent, callback)
|
||||||
|
return n, ethservice, append(preMergeBlocks, postMergeBlocks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockBodiesByHash(t *testing.T) {
|
||||||
|
node, eth, blocks := setupBodies(t)
|
||||||
|
api := NewConsensusAPI(eth)
|
||||||
|
defer node.Close()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
results []*types.Body
|
||||||
|
hashes []common.Hash
|
||||||
|
}{
|
||||||
|
// First pow block
|
||||||
|
{
|
||||||
|
results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()},
|
||||||
|
hashes: []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()},
|
||||||
|
},
|
||||||
|
// Last pow block
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[9].Body()},
|
||||||
|
hashes: []common.Hash{blocks[9].Hash()},
|
||||||
|
},
|
||||||
|
// First post-merge block
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[10].Body()},
|
||||||
|
hashes: []common.Hash{blocks[10].Hash()},
|
||||||
|
},
|
||||||
|
// Pre & post merge blocks
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()},
|
||||||
|
hashes: []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()},
|
||||||
|
},
|
||||||
|
// unavailable block
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()},
|
||||||
|
hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()},
|
||||||
|
},
|
||||||
|
// same block multiple times
|
||||||
|
{
|
||||||
|
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()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, test := range tests {
|
||||||
|
result := api.GetPayloadBodiesByHashV1(test.hashes)
|
||||||
|
for i, r := range result {
|
||||||
|
if !equalBody(test.results[i], r) {
|
||||||
|
t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockBodiesByRange(t *testing.T) {
|
||||||
|
node, eth, blocks := setupBodies(t)
|
||||||
|
api := NewConsensusAPI(eth)
|
||||||
|
defer node.Close()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
results []*types.Body
|
||||||
|
start uint64
|
||||||
|
count uint64
|
||||||
|
}{
|
||||||
|
// Genesis
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[0].Body()},
|
||||||
|
start: 1,
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
// First post-merge block
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[9].Body()},
|
||||||
|
start: 10,
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
// Pre & post merge blocks
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()},
|
||||||
|
start: 8,
|
||||||
|
count: 4,
|
||||||
|
},
|
||||||
|
// unavailable block
|
||||||
|
{
|
||||||
|
results: []*types.Body{blocks[18].Body()},
|
||||||
|
start: 19,
|
||||||
|
count: 3,
|
||||||
|
},
|
||||||
|
// after range
|
||||||
|
{
|
||||||
|
results: make([]*types.Body, 0),
|
||||||
|
start: 20,
|
||||||
|
count: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, test := range tests {
|
||||||
|
result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(result) == len(test.results) {
|
||||||
|
for i, r := range result {
|
||||||
|
if !equalBody(test.results[i], r) {
|
||||||
|
t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("invalid length want %v got %v", len(test.results), len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
|
||||||
|
node, eth, _ := setupBodies(t)
|
||||||
|
api := NewConsensusAPI(eth)
|
||||||
|
defer node.Close()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
start uint64
|
||||||
|
count uint64
|
||||||
|
}{
|
||||||
|
// Genesis
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
// No block requested
|
||||||
|
{
|
||||||
|
start: 1,
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
// Genesis & no block
|
||||||
|
{
|
||||||
|
start: 0,
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
// More than 1024 blocks
|
||||||
|
{
|
||||||
|
start: 1,
|
||||||
|
count: 1025,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalBody(a *types.Body, b *beacon.ExecutionPayloadBodyV1) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
} else if a == nil || b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var want []hexutil.Bytes
|
||||||
|
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 bytes.Equal(aBytes, bBytes)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user