Skip to content

Commit aeb1532

Browse files
MariusVanDerWijdenholiman
authored andcommitted
eth/catalyst: implement engine_getPayloadBodiesByHash/Range methods (ethereum#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>
1 parent 0a2b1b6 commit aeb1532

File tree

3 files changed

+260
-1
lines changed

3 files changed

+260
-1
lines changed

core/beacon/types.go

+6
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
229229
}
230230
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
231231
}
232+
233+
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
234+
type ExecutionPayloadBodyV1 struct {
235+
TransactionData []hexutil.Bytes `json:"transactions"`
236+
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
237+
}

eth/catalyst/api.go

+60
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ var caps = []string{
8888
"engine_getPayloadV2",
8989
"engine_newPayloadV1",
9090
"engine_newPayloadV2",
91+
"engine_getPayloadBodiesByHashV1",
92+
"engine_getPayloadBodiesByRangeV1",
9193
}
9294

9395
type ConsensusAPI struct {
@@ -756,3 +758,61 @@ func (api *ConsensusAPI) heartbeat() {
756758
func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
757759
return caps
758760
}
761+
762+
// GetPayloadBodiesV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
763+
// of block bodies by the engine api.
764+
func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*beacon.ExecutionPayloadBodyV1 {
765+
var bodies = make([]*beacon.ExecutionPayloadBodyV1, len(hashes))
766+
for i, hash := range hashes {
767+
block := api.eth.BlockChain().GetBlockByHash(hash)
768+
bodies[i] = getBody(block)
769+
}
770+
return bodies
771+
}
772+
773+
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
774+
// of block bodies by the engine api.
775+
func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count uint64) ([]*beacon.ExecutionPayloadBodyV1, error) {
776+
if start == 0 || count == 0 || count > 1024 {
777+
return nil, beacon.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
778+
}
779+
// limit count up until current
780+
current := api.eth.BlockChain().CurrentBlock().NumberU64()
781+
end := start + count
782+
if end > current {
783+
end = current
784+
}
785+
var bodies []*beacon.ExecutionPayloadBodyV1
786+
for i := start; i < end; i++ {
787+
block := api.eth.BlockChain().GetBlockByNumber(i)
788+
bodies = append(bodies, getBody(block))
789+
}
790+
return bodies, nil
791+
}
792+
793+
func getBody(block *types.Block) *beacon.ExecutionPayloadBodyV1 {
794+
if block == nil {
795+
return nil
796+
}
797+
798+
var (
799+
body = block.Body()
800+
txs = make([]hexutil.Bytes, len(body.Transactions))
801+
withdrawals = body.Withdrawals
802+
)
803+
804+
for j, tx := range body.Transactions {
805+
data, _ := tx.MarshalBinary()
806+
txs[j] = hexutil.Bytes(data)
807+
}
808+
809+
// Post-shanghai withdrawals MUST be set to empty slice instead of nil
810+
if withdrawals == nil && block.Header().WithdrawalsHash != nil {
811+
withdrawals = make([]*types.Withdrawal, 0)
812+
}
813+
814+
return &beacon.ExecutionPayloadBodyV1{
815+
TransactionData: txs,
816+
Withdrawals: withdrawals,
817+
}
818+
}

eth/catalyst/api_test.go

+194-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/ethereum/go-ethereum/node"
4242
"github.com/ethereum/go-ethereum/p2p"
4343
"github.com/ethereum/go-ethereum/params"
44+
"github.com/ethereum/go-ethereum/rlp"
4445
"github.com/ethereum/go-ethereum/rpc"
4546
"github.com/ethereum/go-ethereum/trie"
4647
)
@@ -475,8 +476,9 @@ func TestFullAPI(t *testing.T) {
475476
setupBlocks(t, ethservice, 10, parent, callback)
476477
}
477478

478-
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Block, callback func(parent *types.Block)) {
479+
func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Block, callback func(parent *types.Block)) []*types.Block {
479480
api := NewConsensusAPI(ethservice)
481+
var blocks []*types.Block
480482
for i := 0; i < n; i++ {
481483
callback(parent)
482484

@@ -504,7 +506,9 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Bl
504506
t.Fatal("Finalized block should be updated")
505507
}
506508
parent = ethservice.BlockChain().CurrentBlock()
509+
blocks = append(blocks, parent)
507510
}
511+
return blocks
508512
}
509513

510514
func TestExchangeTransitionConfig(t *testing.T) {
@@ -1225,3 +1229,192 @@ func TestNilWithdrawals(t *testing.T) {
12251229
}
12261230
}
12271231
}
1232+
1233+
func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
1234+
genesis, preMergeBlocks := generateMergeChain(10, false)
1235+
n, ethservice := startEthService(t, genesis, preMergeBlocks)
1236+
1237+
var (
1238+
parent = ethservice.BlockChain().CurrentBlock()
1239+
// This EVM code generates a log when the contract is created.
1240+
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
1241+
)
1242+
1243+
callback := func(parent *types.Block) {
1244+
statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
1245+
nonce := statedb.GetNonce(testAddr)
1246+
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
1247+
ethservice.TxPool().AddLocal(tx)
1248+
}
1249+
1250+
postMergeBlocks := setupBlocks(t, ethservice, 10, parent, callback)
1251+
return n, ethservice, append(preMergeBlocks, postMergeBlocks...)
1252+
}
1253+
1254+
func TestGetBlockBodiesByHash(t *testing.T) {
1255+
node, eth, blocks := setupBodies(t)
1256+
api := NewConsensusAPI(eth)
1257+
defer node.Close()
1258+
1259+
tests := []struct {
1260+
results []*types.Body
1261+
hashes []common.Hash
1262+
}{
1263+
// First pow block
1264+
{
1265+
results: []*types.Body{eth.BlockChain().GetBlockByNumber(0).Body()},
1266+
hashes: []common.Hash{eth.BlockChain().GetBlockByNumber(0).Hash()},
1267+
},
1268+
// Last pow block
1269+
{
1270+
results: []*types.Body{blocks[9].Body()},
1271+
hashes: []common.Hash{blocks[9].Hash()},
1272+
},
1273+
// First post-merge block
1274+
{
1275+
results: []*types.Body{blocks[10].Body()},
1276+
hashes: []common.Hash{blocks[10].Hash()},
1277+
},
1278+
// Pre & post merge blocks
1279+
{
1280+
results: []*types.Body{blocks[0].Body(), blocks[9].Body(), blocks[14].Body()},
1281+
hashes: []common.Hash{blocks[0].Hash(), blocks[9].Hash(), blocks[14].Hash()},
1282+
},
1283+
// unavailable block
1284+
{
1285+
results: []*types.Body{blocks[0].Body(), nil, blocks[14].Body()},
1286+
hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[14].Hash()},
1287+
},
1288+
// same block multiple times
1289+
{
1290+
results: []*types.Body{blocks[0].Body(), nil, blocks[0].Body(), blocks[0].Body()},
1291+
hashes: []common.Hash{blocks[0].Hash(), {1, 2}, blocks[0].Hash(), blocks[0].Hash()},
1292+
},
1293+
}
1294+
1295+
for k, test := range tests {
1296+
result := api.GetPayloadBodiesByHashV1(test.hashes)
1297+
for i, r := range result {
1298+
if !equalBody(test.results[i], r) {
1299+
t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
1300+
}
1301+
}
1302+
}
1303+
}
1304+
1305+
func TestGetBlockBodiesByRange(t *testing.T) {
1306+
node, eth, blocks := setupBodies(t)
1307+
api := NewConsensusAPI(eth)
1308+
defer node.Close()
1309+
1310+
tests := []struct {
1311+
results []*types.Body
1312+
start uint64
1313+
count uint64
1314+
}{
1315+
// Genesis
1316+
{
1317+
results: []*types.Body{blocks[0].Body()},
1318+
start: 1,
1319+
count: 1,
1320+
},
1321+
// First post-merge block
1322+
{
1323+
results: []*types.Body{blocks[9].Body()},
1324+
start: 10,
1325+
count: 1,
1326+
},
1327+
// Pre & post merge blocks
1328+
{
1329+
results: []*types.Body{blocks[7].Body(), blocks[8].Body(), blocks[9].Body(), blocks[10].Body()},
1330+
start: 8,
1331+
count: 4,
1332+
},
1333+
// unavailable block
1334+
{
1335+
results: []*types.Body{blocks[18].Body()},
1336+
start: 19,
1337+
count: 3,
1338+
},
1339+
// after range
1340+
{
1341+
results: make([]*types.Body, 0),
1342+
start: 20,
1343+
count: 2,
1344+
},
1345+
}
1346+
1347+
for k, test := range tests {
1348+
result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
1349+
if err != nil {
1350+
t.Fatal(err)
1351+
}
1352+
if len(result) == len(test.results) {
1353+
for i, r := range result {
1354+
if !equalBody(test.results[i], r) {
1355+
t.Fatalf("test %v: invalid response: expected %+v got %+v", k, test.results[i], r)
1356+
}
1357+
}
1358+
} else {
1359+
t.Fatalf("invalid length want %v got %v", len(test.results), len(result))
1360+
}
1361+
}
1362+
}
1363+
1364+
func TestGetBlockBodiesByRangeInvalidParams(t *testing.T) {
1365+
node, eth, _ := setupBodies(t)
1366+
api := NewConsensusAPI(eth)
1367+
defer node.Close()
1368+
1369+
tests := []struct {
1370+
start uint64
1371+
count uint64
1372+
}{
1373+
// Genesis
1374+
{
1375+
start: 0,
1376+
count: 1,
1377+
},
1378+
// No block requested
1379+
{
1380+
start: 1,
1381+
count: 0,
1382+
},
1383+
// Genesis & no block
1384+
{
1385+
start: 0,
1386+
count: 0,
1387+
},
1388+
// More than 1024 blocks
1389+
{
1390+
start: 1,
1391+
count: 1025,
1392+
},
1393+
}
1394+
1395+
for _, test := range tests {
1396+
result, err := api.GetPayloadBodiesByRangeV1(test.start, test.count)
1397+
if err == nil {
1398+
t.Fatalf("expected error, got %v", result)
1399+
}
1400+
}
1401+
}
1402+
1403+
func equalBody(a *types.Body, b *beacon.ExecutionPayloadBodyV1) bool {
1404+
if a == nil && b == nil {
1405+
return true
1406+
} else if a == nil || b == nil {
1407+
return false
1408+
}
1409+
var want []hexutil.Bytes
1410+
for _, tx := range a.Transactions {
1411+
data, _ := tx.MarshalBinary()
1412+
want = append(want, hexutil.Bytes(data))
1413+
}
1414+
aBytes, errA := rlp.EncodeToBytes(want)
1415+
bBytes, errB := rlp.EncodeToBytes(b.TransactionData)
1416+
if errA != errB {
1417+
return false
1418+
}
1419+
return bytes.Equal(aBytes, bBytes)
1420+
}

0 commit comments

Comments
 (0)