Skip to content

Commit 224af70

Browse files
committed
light/beacon: added comments, moved functions
1 parent 8af7869 commit 224af70

File tree

4 files changed

+116
-85
lines changed

4 files changed

+116
-85
lines changed

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
586586
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
587587
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
588588
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
589+
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
589590
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
590591
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
591592
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=

light/beacon/api/rest_api.go

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,46 @@ type RestApi struct {
3838
Url string
3939
}
4040

41+
// GetBestUpdateAndCommittee fetches and validates LightClientUpdate for given period and full serialized
42+
// committee for the next period (committee root hash equals update.NextSyncCommitteeRoot).
43+
// Note that the results are validated but the update signature should be verified by the caller as its
44+
// validity depends on the update chain.
4145
func (api *RestApi) GetBestUpdateAndCommittee(period uint64) (beacon.LightClientUpdate, []byte, error) {
42-
c, err := api.getCommitteeUpdate(period)
46+
periodStr := strconv.Itoa(int(period))
47+
resp, err := http.Get(api.Url + "/eth/v1/beacon/light_client/updates?start_period=" + periodStr + "&count=1")
4348
if err != nil {
4449
return beacon.LightClientUpdate{}, nil, err
4550
}
51+
body, err := ioutil.ReadAll(resp.Body)
52+
resp.Body.Close()
53+
if err != nil {
54+
return beacon.LightClientUpdate{}, nil, err
55+
}
56+
57+
type committeeUpdate struct {
58+
Header beacon.Header `json:"attested_header"`
59+
NextSyncCommittee syncCommitteeJson `json:"next_sync_committee"`
60+
NextSyncCommitteeBranch beacon.MerkleValues `json:"next_sync_committee_branch"`
61+
FinalizedHeader beacon.Header `json:"finalized_header"`
62+
FinalityBranch beacon.MerkleValues `json:"finality_branch"`
63+
Aggregate syncAggregate `json:"sync_aggregate"`
64+
ForkVersion hexutil.Bytes `json:"fork_version"`
65+
}
66+
67+
var data struct {
68+
Data []committeeUpdate `json:"data"`
69+
}
70+
if err := json.Unmarshal(body, &data); err != nil {
71+
return beacon.LightClientUpdate{}, nil, err
72+
}
73+
if len(data.Data) != 1 {
74+
return beacon.LightClientUpdate{}, nil, errors.New("invalid number of committee updates")
75+
}
76+
c := data.Data[0]
77+
if len(c.NextSyncCommittee.Pubkeys) != 512 {
78+
return beacon.LightClientUpdate{}, nil, errors.New("invalid number of pubkeys in next_sync_committee")
79+
}
80+
4681
committee, ok := c.NextSyncCommittee.serialize()
4782
if !ok {
4883
return beacon.LightClientUpdate{}, nil, errors.New("invalid sync committee")
@@ -57,14 +92,24 @@ func (api *RestApi) GetBestUpdateAndCommittee(period uint64) (beacon.LightClient
5792
SyncCommitteeSignature: c.Aggregate.Signature,
5893
ForkVersion: c.ForkVersion,
5994
}
95+
if err := update.Validate(); err != nil {
96+
return beacon.LightClientUpdate{}, nil, err
97+
}
98+
if beacon.SerializedCommitteeRoot(committee) != update.NextSyncCommitteeRoot {
99+
return beacon.LightClientUpdate{}, nil, errors.New("sync committee root does not match")
100+
}
60101
return update, committee, nil
61102
}
62103

104+
// syncAggregate represents an aggregated BLS signature with BitMask referring to a subset
105+
// of the corresponding sync committee
63106
type syncAggregate struct {
64107
BitMask hexutil.Bytes `json:"sync_committee_bits"`
65108
Signature hexutil.Bytes `json:"sync_committee_signature"`
66109
}
67110

111+
// GetHeadUpdate fetches the latest available signed header.
112+
// Note that the signature should be verified by the caller as its validity depends on the update chain.
68113
func (api *RestApi) GetHeadUpdate() (beacon.SignedHead, error) {
69114
resp, err := http.Get(api.Url + "/eth/v1/beacon/light_client/optimistic_update/")
70115
if err != nil {
@@ -98,11 +143,13 @@ func (api *RestApi) GetHeadUpdate() (beacon.SignedHead, error) {
98143
}, nil
99144
}
100145

146+
// syncCommitteeJson is the JSON representation of a sync committee
101147
type syncCommitteeJson struct {
102148
Pubkeys []hexutil.Bytes `json:"pubkeys"`
103149
Aggregate hexutil.Bytes `json:"aggregate_pubkey"`
104150
}
105151

152+
// serialize returns the serialized version of the committee
106153
func (s *syncCommitteeJson) serialize() ([]byte, bool) {
107154
if len(s.Pubkeys) != 512 {
108155
return nil, false
@@ -121,45 +168,8 @@ func (s *syncCommitteeJson) serialize() ([]byte, bool) {
121168
return sk, true
122169
}
123170

124-
type committeeUpdate struct {
125-
Header beacon.Header `json:"attested_header"`
126-
NextSyncCommittee syncCommitteeJson `json:"next_sync_committee"`
127-
NextSyncCommitteeBranch beacon.MerkleValues `json:"next_sync_committee_branch"`
128-
FinalizedHeader beacon.Header `json:"finalized_header"`
129-
FinalityBranch beacon.MerkleValues `json:"finality_branch"`
130-
Aggregate syncAggregate `json:"sync_aggregate"`
131-
ForkVersion hexutil.Bytes `json:"fork_version"`
132-
}
133-
134-
func (api *RestApi) getCommitteeUpdate(period uint64) (committeeUpdate, error) {
135-
periodStr := strconv.Itoa(int(period))
136-
resp, err := http.Get(api.Url + "/eth/v1/beacon/light_client/updates?start_period=" + periodStr + "&count=1")
137-
if err != nil {
138-
return committeeUpdate{}, err
139-
}
140-
body, err := ioutil.ReadAll(resp.Body)
141-
resp.Body.Close()
142-
if err != nil {
143-
return committeeUpdate{}, err
144-
}
145-
146-
var data struct {
147-
Data []committeeUpdate `json:"data"`
148-
}
149-
if err := json.Unmarshal(body, &data); err != nil {
150-
return committeeUpdate{}, err
151-
}
152-
if len(data.Data) != 1 {
153-
return committeeUpdate{}, errors.New("invalid number of committee updates")
154-
}
155-
update := data.Data[0]
156-
if len(update.NextSyncCommittee.Pubkeys) != 512 {
157-
return committeeUpdate{}, errors.New("invalid number of pubkeys in next_sync_committee")
158-
}
159-
return update, nil
160-
}
161-
162-
// null hash -> current head
171+
// GetHead fetches and validates the beacon header with the given blockRoot.
172+
// If blockRoot is null hash then the latest head header is fetched.
163173
func (api *RestApi) GetHeader(blockRoot common.Hash) (beacon.Header, error) {
164174
url := api.Url + "/eth/v1/beacon/headers/"
165175
if blockRoot == (common.Hash{}) {
@@ -200,6 +210,10 @@ func (api *RestApi) GetHeader(blockRoot common.Hash) (beacon.Header, error) {
200210
return header, nil
201211
}
202212

213+
// GetStateProof fetches and validates a Merkle proof for the specified parts of the recent
214+
// beacon state referenced by stateRoot. If successful the returned multiproof has the format
215+
// specified by expFormat. The state subset specified by the list of string keys (paths) should
216+
// cover the subset specified by expFormat.
203217
func (api *RestApi) GetStateProof(stateRoot common.Hash, paths []string, expFormat beacon.ProofFormat) (beacon.MultiProof, error) {
204218
url := api.Url + "/eth/v1/beacon/light_client/proof/" + stateRoot.Hex() + "?paths=" + paths[0]
205219
for i := 1; i < len(paths); i++ {
@@ -226,6 +240,7 @@ func (api *RestApi) GetStateProof(stateRoot common.Hash, paths []string, expForm
226240
return beacon.MultiProof{Format: expFormat, Values: values}, nil
227241
}
228242

243+
// GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint.
229244
func (api *RestApi) GetCheckpointData(ctx context.Context, checkpoint common.Hash) (beacon.Header, beacon.CheckpointData, []byte, error) {
230245
resp, err := http.Get(api.Url + "/eth/v1/beacon/light_client/bootstrap/" + checkpoint.String())
231246
if err != nil {
@@ -267,9 +282,10 @@ func (api *RestApi) GetCheckpointData(ctx context.Context, checkpoint common.Has
267282

268283
}
269284

270-
// beacon block root -> exec block
271-
func (api *RestApi) GetExecutionPayload( /*ctx context.Context, */ blockRoot, execRoot common.Hash) (*types.Block, error) {
272-
resp, err := http.Get(api.Url + "/eth/v2/beacon/blocks/" + blockRoot.Hex())
285+
// GetExecutionPayload fetches the execution block belonging to the beacon block specified
286+
// by beaconRoot and validates its block hash against the expected execRoot.
287+
func (api *RestApi) GetExecutionPayload(beaconRoot, execRoot common.Hash) (*types.Block, error) {
288+
resp, err := http.Get(api.Url + "/eth/v2/beacon/blocks/" + beaconRoot.Hex())
273289
if err != nil {
274290
return nil, err
275291
}

light/beacon/api/syncer.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
headPollCount = 50
3333
)
3434

35+
// CommitteeSyncer syncs committee updates and signed heads from RestApi to SyncCommitteeTracker
3536
type CommitteeSyncer struct {
3637
api *RestApi
3738

@@ -44,7 +45,8 @@ type CommitteeSyncer struct {
4445
headTriggerCh, closedCh chan struct{}
4546
}
4647

47-
// genesisData is only needed when light syncing (using GetInitData for bootstrap)
48+
// NewCommitteeSyncer creates a new CommitteeSyncer
49+
// Note: genesisData is only needed when light syncing (using GetInitData for bootstrap)
4850
func NewCommitteeSyncer(api *RestApi, genesisData beacon.GenesisData) *CommitteeSyncer {
4951
updateCache, _ := lru.New(beacon.MaxCommitteeUpdateFetch)
5052
committeeCache, _ := lru.New(beacon.MaxCommitteeUpdateFetch / beacon.CommitteeCostFactor)
@@ -58,28 +60,23 @@ func NewCommitteeSyncer(api *RestApi, genesisData beacon.GenesisData) *Committee
5860
}
5961
}
6062

63+
// Start starts the syncing of the given SyncCommitteeTracker
6164
func (cs *CommitteeSyncer) Start(sct *beacon.SyncCommitteeTracker) {
6265
cs.sct = sct
6366
sct.SyncWithPeer(cs, nil)
6467
go cs.headPollLoop()
6568
}
6669

67-
func (cs *CommitteeSyncer) TriggerNewHead() {
68-
cs.updateCache.Purge()
69-
cs.committeeCache.Purge()
70-
71-
select {
72-
case cs.headTriggerCh <- struct{}{}:
73-
default:
74-
}
75-
}
76-
70+
// Stop stops the syncing process
7771
func (cs *CommitteeSyncer) Stop() {
7872
cs.sct.Disconnect(cs)
7973
close(cs.headTriggerCh)
8074
<-cs.closedCh
8175
}
8276

77+
// headPollLoop polls the signed head update endpoint and feeds signed heads into the tracker.
78+
// Twice each sync period (not long before the end of the period and after the end of each period)
79+
// it queries the best available committee update and advertises it to the tracker.
8380
func (cs *CommitteeSyncer) headPollLoop() {
8481
//fmt.Println("Started headPollLoop()")
8582
timer := time.NewTimer(0)
@@ -98,6 +95,8 @@ func (cs *CommitteeSyncer) headPollLoop() {
9895
if head, err := cs.api.GetHeadUpdate(); err == nil {
9996
//fmt.Println(" headPollLoop head update for slot", head.Header.Slot, nextAdvertiseSlot)
10097
if !head.Equal(&lastHead) {
98+
cs.updateCache.Purge()
99+
cs.committeeCache.Purge()
101100
//fmt.Println("head poll: new head", head.Header.Slot, nextAdvertiseSlot)
102101
cs.sct.AddSignedHeads(cs, []beacon.SignedHead{head})
103102
lastHead = head
@@ -134,6 +133,9 @@ func (cs *CommitteeSyncer) headPollLoop() {
134133
}
135134
}
136135

136+
// advertiseUpdates queries committee updates that the tracker does not have or might have
137+
// improved since the last query and advertises them to the tracker. The tracker might then
138+
// fetch the actual updates and committees via GetBestCommitteeProofs.
137139
func (cs *CommitteeSyncer) advertiseUpdates(lastPeriod uint64) bool {
138140
nextPeriod := cs.sct.NextPeriod()
139141
if nextPeriod < 1 {
@@ -161,6 +163,7 @@ func (cs *CommitteeSyncer) advertiseUpdates(lastPeriod uint64) bool {
161163
return true
162164
}
163165

166+
// GetBestCommitteeProofs fetches updates and committees for the specified periods
164167
func (cs *CommitteeSyncer) GetBestCommitteeProofs(ctx context.Context, req beacon.CommitteeRequest) (beacon.CommitteeReply, error) {
165168
reply := beacon.CommitteeReply{
166169
Updates: make([]beacon.LightClientUpdate, len(req.UpdatePeriods)),
@@ -180,6 +183,7 @@ func (cs *CommitteeSyncer) GetBestCommitteeProofs(ctx context.Context, req beaco
180183
return reply, nil
181184
}
182185

186+
// getBestUpdate returns the best update for the given period
183187
func (cs *CommitteeSyncer) getBestUpdate(period uint64) (beacon.LightClientUpdate, error) {
184188
if c, _ := cs.updateCache.Get(period); c != nil {
185189
return c.(beacon.LightClientUpdate), nil
@@ -188,7 +192,9 @@ func (cs *CommitteeSyncer) getBestUpdate(period uint64) (beacon.LightClientUpdat
188192
return update, err
189193
}
190194

195+
// getCommittee returns the committee for the given period
191196
func (cs *CommitteeSyncer) getCommittee(period uint64) ([]byte, error) {
197+
//TODO period 0 (same as period 1 if available?)
192198
if cs.checkpointCommittee != nil && period == cs.checkpointPeriod {
193199
return cs.checkpointCommittee, nil
194200
}
@@ -199,6 +205,8 @@ func (cs *CommitteeSyncer) getCommittee(period uint64) ([]byte, error) {
199205
return committee, err
200206
}
201207

208+
// getBestUpdateAndCommittee fetches the best update for period and corresponding committee
209+
// for period+1 and caches the results until a new head is received by headPollLoop
202210
func (cs *CommitteeSyncer) getBestUpdateAndCommittee(period uint64) (beacon.LightClientUpdate, []byte, error) {
203211
update, committee, err := cs.api.GetBestUpdateAndCommittee(period)
204212
if err != nil {
@@ -209,6 +217,8 @@ func (cs *CommitteeSyncer) getBestUpdateAndCommittee(period uint64) (beacon.Ligh
209217
return update, committee, nil
210218
}
211219

220+
// GetInitData fetches the bootstrap data and returns LightClientInitData (the corresponding
221+
// committee is stored so that a subsequent GetBestCommitteeProofs can return it when requested)
212222
func (cs *CommitteeSyncer) GetInitData(ctx context.Context, checkpoint common.Hash) (beacon.Header, beacon.LightClientInitData, error) {
213223
if cs.genesisData == (beacon.GenesisData{}) {
214224
return beacon.Header{}, beacon.LightClientInitData{}, errors.New("missing genesis data")
@@ -221,10 +231,12 @@ func (cs *CommitteeSyncer) GetInitData(ctx context.Context, checkpoint common.Ha
221231
return header, beacon.LightClientInitData{GenesisData: cs.genesisData, CheckpointData: checkpointData}, nil
222232
}
223233

234+
// ClosedChannel returns a channel that is closed when the syncer is stopped
224235
func (cs *CommitteeSyncer) ClosedChannel() chan struct{} {
225236
return cs.closedCh
226237
}
227238

239+
// WrongReply is called by the tracker when the RestApi has provided wrong committee updates or signed heads
228240
func (cs *CommitteeSyncer) WrongReply(description string) {
229241
log.Error("Beacon node API data source delivered wrong reply", "error", description)
230242
}

light/beacon/sync_committee.go

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"context"
2323
"encoding/binary"
2424

25+
"errors"
2526
"fmt"
2627
"io"
2728
"math"
@@ -252,38 +253,11 @@ func (s *SyncCommitteeTracker) deleteBestUpdate(period uint64) {
252253
s.updateInfoChanged()
253254
}
254255

255-
// verifyUpdate checks whether the sync committee Merkle proof and the header signature is
256-
// correct and the update fits into the specified constraints
256+
// verifyUpdate checks whether the header signature is correct and the update fits into the specified constraints
257+
// (assumes that the update has been successfully validated previously)
257258
func (s *SyncCommitteeTracker) verifyUpdate(update *LightClientUpdate) bool {
258-
//fmt.Println("verify update", update)
259-
if update.Header.Slot&0x1fff == 0x1fff {
260-
// last slot of each period is not suitable for an update because it is signed by the next period's sync committee, proves the same committee it is signed by
261-
return false
262-
}
263-
if !s.checkForksAndConstraints(update) {
264-
return false
265-
}
266-
var checkRoot common.Hash
267-
if update.hasFinality() {
268-
if update.FinalizedHeader.Slot>>13 != update.Header.Slot>>13 {
269-
// finalized header is from the previous period, proves the same committee it is signed by
270-
return false
271-
}
272-
if root, ok := VerifySingleProof(update.FinalityBranch, BsiFinalBlock, MerkleValue(update.FinalizedHeader.Hash()), 0); !ok || root != update.Header.StateRoot {
273-
return false
274-
}
275-
checkRoot = update.FinalizedHeader.StateRoot
276-
} else {
277-
checkRoot = update.Header.StateRoot
278-
}
279-
if root, ok := VerifySingleProof(update.NextSyncCommitteeBranch, BsiNextSyncCommittee, MerkleValue(update.NextSyncCommitteeRoot), 0); !ok || root != checkRoot {
280-
if ok && root == update.Header.StateRoot {
281-
log.Warn("update.NextSyncCommitteeBranch rooted in update.Header.StateRoot (Lodestar bug workaround applied)")
282-
} else {
283-
return false
284-
}
285-
}
286-
return s.verifySignature(SignedHead{Header: update.Header, Signature: update.SyncCommitteeSignature, BitMask: update.SyncCommitteeBits})
259+
return s.checkForksAndConstraints(update) &&
260+
s.verifySignature(SignedHead{Header: update.Header, Signature: update.SyncCommitteeSignature, BitMask: update.SyncCommitteeBits})
287261
}
288262

289263
const (
@@ -1026,6 +1000,34 @@ type LightClientUpdate struct {
10261000
score UpdateScore
10271001
}
10281002

1003+
func (update *LightClientUpdate) Validate() error {
1004+
//fmt.Println("verify update", update)
1005+
if update.Header.Slot&0x1fff == 0x1fff {
1006+
// last slot of each period is not suitable for an update because it is signed by the next period's sync committee, proves the same committee it is signed by
1007+
return errors.New("Last slot of period")
1008+
}
1009+
var checkRoot common.Hash
1010+
if update.hasFinality() {
1011+
if update.FinalizedHeader.Slot>>13 != update.Header.Slot>>13 {
1012+
return errors.New("FinalizedHeader is from previous period") // proves the same committee it is signed by
1013+
}
1014+
if root, ok := VerifySingleProof(update.FinalityBranch, BsiFinalBlock, MerkleValue(update.FinalizedHeader.Hash()), 0); !ok || root != update.Header.StateRoot {
1015+
return errors.New("Invalid FinalizedHeader merkle proof")
1016+
}
1017+
checkRoot = update.FinalizedHeader.StateRoot
1018+
} else {
1019+
checkRoot = update.Header.StateRoot
1020+
}
1021+
if root, ok := VerifySingleProof(update.NextSyncCommitteeBranch, BsiNextSyncCommittee, MerkleValue(update.NextSyncCommitteeRoot), 0); !ok || root != checkRoot {
1022+
if ok && root == update.Header.StateRoot {
1023+
log.Warn("update.NextSyncCommitteeBranch rooted in update.Header.StateRoot (Lodestar bug workaround applied)")
1024+
} else {
1025+
return errors.New("Invalid NextSyncCommittee merkle proof")
1026+
}
1027+
}
1028+
return nil
1029+
}
1030+
10291031
func (l *LightClientUpdate) hasFinality() bool {
10301032
return l.FinalizedHeader.BodyRoot != (common.Hash{})
10311033
}

0 commit comments

Comments
 (0)