Skip to content

Commit bca6c40

Browse files
fjlzsfelfoldi
andauthored
beacon/blsync: support for deneb fork (#29180)
This adds support for the Deneb beacon chain fork, and fork handling in general, to the beacon chain light client implementation. Co-authored-by: Zsolt Felfoldi <[email protected]>
1 parent 04bf1c8 commit bca6c40

File tree

21 files changed

+5074
-348
lines changed

21 files changed

+5074
-348
lines changed

beacon/blsync/block_sync.go

Lines changed: 19 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,25 @@
1717
package blsync
1818

1919
import (
20-
"fmt"
21-
"math/big"
22-
23-
"github.com/ethereum/go-ethereum/beacon/engine"
2420
"github.com/ethereum/go-ethereum/beacon/light/request"
2521
"github.com/ethereum/go-ethereum/beacon/light/sync"
2622
"github.com/ethereum/go-ethereum/beacon/types"
2723
"github.com/ethereum/go-ethereum/common"
2824
"github.com/ethereum/go-ethereum/common/lru"
29-
ctypes "github.com/ethereum/go-ethereum/core/types"
3025
"github.com/ethereum/go-ethereum/event"
3126
"github.com/ethereum/go-ethereum/log"
32-
"github.com/ethereum/go-ethereum/trie"
33-
"github.com/holiman/uint256"
34-
"github.com/protolambda/zrnt/eth2/beacon/capella"
35-
"github.com/protolambda/zrnt/eth2/configs"
36-
"github.com/protolambda/ztyp/tree"
3727
)
3828

3929
// beaconBlockSync implements request.Module; it fetches the beacon blocks belonging
4030
// to the validated and prefetch heads.
4131
type beaconBlockSync struct {
42-
recentBlocks *lru.Cache[common.Hash, *capella.BeaconBlock]
32+
recentBlocks *lru.Cache[common.Hash, *types.BeaconBlock]
4333
locked map[common.Hash]request.ServerAndID
4434
serverHeads map[request.Server]common.Hash
4535
headTracker headTracker
4636

4737
lastHeadInfo types.HeadInfo
48-
chainHeadFeed *event.Feed
38+
chainHeadFeed event.FeedOf[types.ChainHeadEvent]
4939
}
5040

5141
type headTracker interface {
@@ -55,16 +45,19 @@ type headTracker interface {
5545
}
5646

5747
// newBeaconBlockSync returns a new beaconBlockSync.
58-
func newBeaconBlockSync(headTracker headTracker, chainHeadFeed *event.Feed) *beaconBlockSync {
48+
func newBeaconBlockSync(headTracker headTracker) *beaconBlockSync {
5949
return &beaconBlockSync{
60-
headTracker: headTracker,
61-
chainHeadFeed: chainHeadFeed,
62-
recentBlocks: lru.NewCache[common.Hash, *capella.BeaconBlock](10),
63-
locked: make(map[common.Hash]request.ServerAndID),
64-
serverHeads: make(map[request.Server]common.Hash),
50+
headTracker: headTracker,
51+
recentBlocks: lru.NewCache[common.Hash, *types.BeaconBlock](10),
52+
locked: make(map[common.Hash]request.ServerAndID),
53+
serverHeads: make(map[request.Server]common.Hash),
6554
}
6655
}
6756

57+
func (s *beaconBlockSync) SubscribeChainHead(ch chan<- types.ChainHeadEvent) event.Subscription {
58+
return s.chainHeadFeed.Subscribe(ch)
59+
}
60+
6861
// Process implements request.Module.
6962
func (s *beaconBlockSync) Process(requester request.Requester, events []request.Event) {
7063
for _, event := range events {
@@ -73,7 +66,7 @@ func (s *beaconBlockSync) Process(requester request.Requester, events []request.
7366
sid, req, resp := event.RequestInfo()
7467
blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
7568
if resp != nil {
76-
s.recentBlocks.Add(blockRoot, resp.(*capella.BeaconBlock))
69+
s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
7770
}
7871
if s.locked[blockRoot] == sid {
7972
delete(s.locked, blockRoot)
@@ -112,63 +105,11 @@ func (s *beaconBlockSync) tryRequestBlock(requester request.Requester, blockRoot
112105
}
113106
}
114107

115-
func blockHeadInfo(block *capella.BeaconBlock) types.HeadInfo {
108+
func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo {
116109
if block == nil {
117110
return types.HeadInfo{}
118111
}
119-
return types.HeadInfo{Slot: uint64(block.Slot), BlockRoot: beaconBlockHash(block)}
120-
}
121-
122-
// beaconBlockHash calculates the hash of a beacon block.
123-
func beaconBlockHash(beaconBlock *capella.BeaconBlock) common.Hash {
124-
return common.Hash(beaconBlock.HashTreeRoot(configs.Mainnet, tree.GetHashFn()))
125-
}
126-
127-
// getExecBlock extracts the execution block from the beacon block's payload.
128-
func getExecBlock(beaconBlock *capella.BeaconBlock) (*ctypes.Block, error) {
129-
payload := &beaconBlock.Body.ExecutionPayload
130-
txs := make([]*ctypes.Transaction, len(payload.Transactions))
131-
for i, opaqueTx := range payload.Transactions {
132-
var tx ctypes.Transaction
133-
if err := tx.UnmarshalBinary(opaqueTx); err != nil {
134-
return nil, fmt.Errorf("failed to parse tx %d: %v", i, err)
135-
}
136-
txs[i] = &tx
137-
}
138-
withdrawals := make([]*ctypes.Withdrawal, len(payload.Withdrawals))
139-
for i, w := range payload.Withdrawals {
140-
withdrawals[i] = &ctypes.Withdrawal{
141-
Index: uint64(w.Index),
142-
Validator: uint64(w.ValidatorIndex),
143-
Address: common.Address(w.Address),
144-
Amount: uint64(w.Amount),
145-
}
146-
}
147-
wroot := ctypes.DeriveSha(ctypes.Withdrawals(withdrawals), trie.NewStackTrie(nil))
148-
execHeader := &ctypes.Header{
149-
ParentHash: common.Hash(payload.ParentHash),
150-
UncleHash: ctypes.EmptyUncleHash,
151-
Coinbase: common.Address(payload.FeeRecipient),
152-
Root: common.Hash(payload.StateRoot),
153-
TxHash: ctypes.DeriveSha(ctypes.Transactions(txs), trie.NewStackTrie(nil)),
154-
ReceiptHash: common.Hash(payload.ReceiptsRoot),
155-
Bloom: ctypes.Bloom(payload.LogsBloom),
156-
Difficulty: common.Big0,
157-
Number: new(big.Int).SetUint64(uint64(payload.BlockNumber)),
158-
GasLimit: uint64(payload.GasLimit),
159-
GasUsed: uint64(payload.GasUsed),
160-
Time: uint64(payload.Timestamp),
161-
Extra: []byte(payload.ExtraData),
162-
MixDigest: common.Hash(payload.PrevRandao), // reused in merge
163-
Nonce: ctypes.BlockNonce{}, // zero
164-
BaseFee: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(),
165-
WithdrawalsHash: &wroot,
166-
}
167-
execBlock := ctypes.NewBlockWithHeader(execHeader).WithBody(txs, nil).WithWithdrawals(withdrawals)
168-
if execBlockHash := execBlock.Hash(); execBlockHash != common.Hash(payload.BlockHash) {
169-
return execBlock, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", common.Hash(payload.BlockHash), execBlockHash)
170-
}
171-
return execBlock, nil
112+
return types.HeadInfo{Slot: block.Slot(), BlockRoot: block.Root()}
172113
}
173114

174115
func (s *beaconBlockSync) updateEventFeed() {
@@ -190,14 +131,16 @@ func (s *beaconBlockSync) updateEventFeed() {
190131
return
191132
}
192133
s.lastHeadInfo = headInfo
134+
193135
// new head block and finality info available; extract executable data and send event to feed
194-
execBlock, err := getExecBlock(headBlock)
136+
execBlock, err := headBlock.ExecutionPayload()
195137
if err != nil {
196138
log.Error("Error extracting execution block from validated beacon block", "error", err)
197139
return
198140
}
199141
s.chainHeadFeed.Send(types.ChainHeadEvent{
200-
HeadBlock: engine.BlockToExecutableData(execBlock, nil, nil).ExecutionPayload,
201-
Finalized: common.Hash(finality.Finalized.PayloadHeader.BlockHash),
142+
BeaconHead: head.Header,
143+
Block: execBlock,
144+
Finalized: finality.Finalized.PayloadHeader.BlockHash(),
202145
})
203146
}

beacon/blsync/block_sync_test.go

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,70 +23,69 @@ import (
2323
"github.com/ethereum/go-ethereum/beacon/light/sync"
2424
"github.com/ethereum/go-ethereum/beacon/types"
2525
"github.com/ethereum/go-ethereum/common"
26-
"github.com/ethereum/go-ethereum/event"
27-
"github.com/protolambda/zrnt/eth2/beacon/capella"
28-
"github.com/protolambda/zrnt/eth2/configs"
29-
"github.com/protolambda/ztyp/tree"
26+
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
27+
"github.com/protolambda/zrnt/eth2/beacon/deneb"
3028
)
3129

3230
var (
3331
testServer1 = "testServer1"
3432
testServer2 = "testServer2"
3533

36-
testBlock1 = &capella.BeaconBlock{
34+
testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{
3735
Slot: 123,
38-
Body: capella.BeaconBlockBody{
39-
ExecutionPayload: capella.ExecutionPayload{BlockNumber: 456},
36+
Body: deneb.BeaconBlockBody{
37+
ExecutionPayload: deneb.ExecutionPayload{
38+
BlockNumber: 456,
39+
BlockHash: zrntcommon.Hash32(common.HexToHash("905ac721c4058d9ed40b27b6b9c1bdd10d4333e4f3d9769100bf9dfb80e5d1f6")),
40+
},
4041
},
41-
}
42-
testBlock2 = &capella.BeaconBlock{
42+
})
43+
testBlock2 = types.NewBeaconBlock(&deneb.BeaconBlock{
4344
Slot: 124,
44-
Body: capella.BeaconBlockBody{
45-
ExecutionPayload: capella.ExecutionPayload{BlockNumber: 457},
45+
Body: deneb.BeaconBlockBody{
46+
ExecutionPayload: deneb.ExecutionPayload{
47+
BlockNumber: 457,
48+
BlockHash: zrntcommon.Hash32(common.HexToHash("011703f39c664efc1c6cf5f49ca09b595581eec572d4dfddd3d6179a9e63e655")),
49+
},
4650
},
47-
}
51+
})
4852
)
4953

50-
func init() {
51-
eb1, _ := getExecBlock(testBlock1)
52-
testBlock1.Body.ExecutionPayload.BlockHash = tree.Root(eb1.Hash())
53-
eb2, _ := getExecBlock(testBlock2)
54-
testBlock2.Body.ExecutionPayload.BlockHash = tree.Root(eb2.Hash())
55-
}
56-
5754
func TestBlockSync(t *testing.T) {
5855
ht := &testHeadTracker{}
59-
eventFeed := new(event.Feed)
60-
blockSync := newBeaconBlockSync(ht, eventFeed)
56+
blockSync := newBeaconBlockSync(ht)
6157
headCh := make(chan types.ChainHeadEvent, 16)
62-
eventFeed.Subscribe(headCh)
58+
blockSync.SubscribeChainHead(headCh)
6359
ts := sync.NewTestScheduler(t, blockSync)
6460
ts.AddServer(testServer1, 1)
6561
ts.AddServer(testServer2, 1)
6662

67-
expHeadBlock := func(tci int, expHead *capella.BeaconBlock) {
63+
expHeadBlock := func(expHead *types.BeaconBlock) {
64+
t.Helper()
6865
var expNumber, headNumber uint64
6966
if expHead != nil {
70-
expNumber = uint64(expHead.Body.ExecutionPayload.BlockNumber)
67+
p, _ := expHead.ExecutionPayload()
68+
expNumber = p.NumberU64()
7169
}
7270
select {
7371
case event := <-headCh:
74-
headNumber = event.HeadBlock.Number
72+
headNumber = event.Block.NumberU64()
7573
default:
7674
}
7775
if headNumber != expNumber {
78-
t.Errorf("Wrong head block in test case #%d (expected block number %d, got %d)", tci, expNumber, headNumber)
76+
t.Errorf("Wrong head block, expected block number %d, got %d)", expNumber, headNumber)
7977
}
8078
}
8179

8280
// no block requests expected until head tracker knows about a head
8381
ts.Run(1)
84-
expHeadBlock(1, nil)
82+
expHeadBlock(nil)
8583

8684
// set block 1 as prefetch head, announced by server 2
8785
head1 := blockHeadInfo(testBlock1)
8886
ht.prefetch = head1
8987
ts.ServerEvent(sync.EvNewHead, testServer2, head1)
88+
9089
// expect request to server 2 which has announced the head
9190
ts.Run(2, testServer2, sync.ReqBeaconBlock(head1.BlockRoot))
9291

@@ -95,12 +94,12 @@ func TestBlockSync(t *testing.T) {
9594
ts.AddAllowance(testServer2, 1)
9695
ts.Run(3)
9796
// head block still not expected as the fetched block is not the validated head yet
98-
expHeadBlock(3, nil)
97+
expHeadBlock(nil)
9998

10099
// set as validated head, expect no further requests but block 1 set as head block
101-
ht.validated.Header = blockHeader(testBlock1)
100+
ht.validated.Header = testBlock1.Header()
102101
ts.Run(4)
103-
expHeadBlock(4, testBlock1)
102+
expHeadBlock(testBlock1)
104103

105104
// set block 2 as prefetch head, announced by server 1
106105
head2 := blockHeadInfo(testBlock2)
@@ -114,26 +113,16 @@ func TestBlockSync(t *testing.T) {
114113
ts.Run(6)
115114

116115
// set as validated head before retrieving block; now it's assumed to be available from server 2 too
117-
ht.validated.Header = blockHeader(testBlock2)
116+
ht.validated.Header = testBlock2.Header()
118117
// expect req2 retry to server 2
119118
ts.Run(7, testServer2, sync.ReqBeaconBlock(head2.BlockRoot))
120119
// now head block should be unavailable again
121-
expHeadBlock(4, nil)
120+
expHeadBlock(nil)
122121

123122
// valid response, now head block should be block 2 immediately as it is already validated
124123
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testBlock2)
125124
ts.Run(8)
126-
expHeadBlock(5, testBlock2)
127-
}
128-
129-
func blockHeader(block *capella.BeaconBlock) types.Header {
130-
return types.Header{
131-
Slot: uint64(block.Slot),
132-
ProposerIndex: uint64(block.ProposerIndex),
133-
ParentRoot: common.Hash(block.ParentRoot),
134-
StateRoot: common.Hash(block.StateRoot),
135-
BodyRoot: common.Hash(block.Body.HashTreeRoot(configs.Mainnet, tree.GetHashFn())),
136-
}
125+
expHeadBlock(testBlock2)
137126
}
138127

139128
type testHeadTracker struct {
@@ -151,9 +140,10 @@ func (h *testHeadTracker) ValidatedHead() (types.SignedHeader, bool) {
151140

152141
// TODO add test case for finality
153142
func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
143+
finalized := types.NewExecutionHeader(new(deneb.ExecutionPayloadHeader))
154144
return types.FinalityUpdate{
155145
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
156-
Finalized: types.HeaderWithExecProof{PayloadHeader: &capella.ExecutionPayloadHeader{}},
146+
Finalized: types.HeaderWithExecProof{PayloadHeader: finalized},
157147
Signature: h.validated.Signature,
158148
SignatureSlot: h.validated.SignatureSlot,
159149
}, h.validated.Header != (types.Header{})

0 commit comments

Comments
 (0)