Skip to content

Commit abe386c

Browse files
authored
Ensure validator bytes are correctly verified prior Rio (#1749)
* Ensure validator bytes are correctly verified prior Rio * Add new special block for Amoy * Fix unit test * Add new blocks
1 parent 214d2bd commit abe386c

File tree

6 files changed

+80
-15
lines changed

6 files changed

+80
-15
lines changed

builder/files/genesis-amoy.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"ahmedabadBlock": 11865856,
2424
"bhilaiBlock": 22765056,
2525
"rioBlock": 26272256,
26+
"skipValidatorByteCheck": [26160367, 26161087, 26171567, 26173743, 26175647],
2627
"stateSyncConfirmationDelay": {
2728
"0": 128
2829
},

consensus/bor/bor.go

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"math/big"
12+
"slices"
1213
"sort"
1314
"strconv"
1415
"sync"
@@ -111,8 +112,8 @@ var (
111112
// be modified via out-of-range or non-contiguous headers.
112113
errOutOfRangeChain = errors.New("out of range or non-contiguous chain")
113114

114-
errUncleDetected = errors.New("uncles not allowed")
115-
// errUnknownValidators = errors.New("unknown validators")
115+
errUncleDetected = errors.New("uncles not allowed")
116+
errUnknownValidators = errors.New("unknown validators")
116117
)
117118

118119
// SignerFn is a signer callback function to request a header to be signed by a
@@ -523,6 +524,69 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainHeaderReader, header *t
523524
return ErrInvalidTimestamp
524525
}
525526

527+
if !c.config.IsRio(header.Number) && !slices.Contains(c.config.SkipValidatorByteCheck, number) {
528+
// Retrieve the snapshot needed to verify this header and cache it
529+
snap, err := c.snapshot(chain, header, parents, false)
530+
if err != nil {
531+
return err
532+
}
533+
534+
// Verify if the producer set in header's extra data matches with the list in span.
535+
// We skip the check for 0th span as the producer set in contract v/s producer set
536+
// in heimdall span is different which will lead a mismatch. Moreover, to make the
537+
// validation stateless, we use the span from heimdall (via span store) instead of
538+
// span from validator set genesis contract as both are supposed to be equivalent.
539+
if number > zerothSpanEnd && IsSprintStart(number+1, c.config.CalculateSprint(number)) {
540+
span, err := c.spanStore.spanByBlockNumber(context.Background(), number+1)
541+
if err != nil {
542+
return err
543+
}
544+
545+
// Use producer set from span as it's equivalent to the data we get from genesis contract
546+
selectedProducers := borSpan.ConvertHeimdallValidatorsToBorValidators(span.SelectedProducers)
547+
newValidators := make([]*valset.Validator, len(selectedProducers))
548+
for i, val := range selectedProducers {
549+
newValidators[i] = &val
550+
}
551+
sort.Sort(valset.ValidatorsByAddress(newValidators))
552+
553+
headerVals, err := valset.ParseValidators(header.GetValidatorBytes(c.chainConfig))
554+
if err != nil {
555+
return err
556+
}
557+
558+
if len(newValidators) != len(headerVals) {
559+
log.Warn("Invalid validator set", "block number", number, "newValidators", newValidators, "headerVals", headerVals)
560+
return errInvalidSpanValidators
561+
}
562+
563+
for i, val := range newValidators {
564+
if !bytes.Equal(val.HeaderBytes(), headerVals[i].HeaderBytes()) {
565+
log.Warn("Invalid validator set", "block number", number, "index", i, "local validator", val, "header validator", headerVals[i])
566+
return errInvalidSpanValidators
567+
}
568+
}
569+
}
570+
571+
// verify the validator list in the last sprint block
572+
if IsSprintStart(number, c.config.CalculateSprint(number)) && !slices.Contains(c.config.SkipValidatorByteCheck, number-1) {
573+
parentValidatorBytes := parent.GetValidatorBytes(c.chainConfig)
574+
validatorsBytes := make([]byte, len(snap.ValidatorSet.Validators)*validatorHeaderBytesLength)
575+
576+
currentValidators := snap.ValidatorSet.Copy().Validators
577+
// sort validator by address
578+
sort.Sort(valset.ValidatorsByAddress(currentValidators))
579+
580+
for i, validator := range currentValidators {
581+
copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes())
582+
}
583+
// len(header.Extra) >= extraVanity+extraSeal has already been validated in validateHeaderExtraField, so this won't result in a panic
584+
if !bytes.Equal(parentValidatorBytes, validatorsBytes) {
585+
return &MismatchingValidatorsError{number - 1, validatorsBytes, parentValidatorBytes}
586+
}
587+
}
588+
}
589+
526590
// All basic checks passed, verify the seal and return
527591
return c.verifySeal(chain, header, parents)
528592
}
@@ -854,17 +918,9 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header) e
854918

855919
// get validator set if number
856920
if IsSprintStart(number+1, c.config.CalculateSprint(number)) && !c.config.IsRio(header.Number) {
857-
span, err := c.spanStore.spanByBlockNumber(context.Background(), number+1)
921+
newValidators, err := c.spanner.GetCurrentValidatorsByHash(context.Background(), header.ParentHash, number+1)
858922
if err != nil {
859-
return err
860-
}
861-
862-
newValidators := make([]*valset.Validator, len(span.SelectedProducers))
863-
for i, val := range span.SelectedProducers {
864-
newValidators[i] = &valset.Validator{
865-
Address: common.HexToAddress(val.Signer),
866-
VotingPower: val.VotingPower,
867-
}
923+
return errUnknownValidators
868924
}
869925

870926
// sort validator by address

consensus/bor/span_store.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ func (s *SpanStore) spanById(ctx context.Context, spanId uint64) (*borTypes.Span
215215
log.Warn("Unable to fetch span from heimdall", "id", spanId, "err", err)
216216
return nil, err
217217
}
218+
219+
if len(currentSpan.SelectedProducers) == 0 {
220+
log.Warn("Span from Heimdall has empty SelectedProducers", "spanId", spanId, "selectedProducers", currentSpan.SelectedProducers, "validators", currentSpan.ValidatorSet.Validators, "startBlock", currentSpan.StartBlock, "endBlock", currentSpan.EndBlock)
221+
return nil, fmt.Errorf("span %d has empty SelectedProducers, possibly incomplete", spanId)
222+
}
218223
}
219224

220225
if currentSpan == nil {
@@ -241,7 +246,7 @@ func (s *SpanStore) spanByBlockNumber(ctx context.Context, blockNumber uint64) (
241246
// that we still check if the block number lies in the range of span before returning it.
242247
estimatedSpanId := s.estimateSpanId(blockNumber)
243248
defer func() {
244-
if res != nil && err == nil {
249+
if res != nil && len(res.SelectedProducers) > 0 && err == nil {
245250
s.lastUsedSpan.Store(res)
246251
}
247252
}()

consensus/bor/span_store_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,8 +1309,8 @@ func TestSpanStore_WaitForNewSpan(t *testing.T) {
13091309
}()
13101310

13111311
found, err := store.waitForNewSpan(150, author1Address, 1*time.Second)
1312-
require.NoError(t, err)
1313-
require.True(t, found)
1312+
require.Error(t, err)
1313+
require.False(t, found)
13141314
})
13151315

13161316
t.Run("target block not initially in span", func(t *testing.T) {

internal/cli/server/chains/amoy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ var amoyTestnet = &Chain{
6464
"0": "0x0000000000000000000000000000000000000000",
6565
"26272256": "0x7Ee41D8A25641000661B1EF5E6AE8A00400466B0",
6666
},
67+
SkipValidatorByteCheck: []uint64{26160367, 26161087, 26171567, 26173743, 26175647},
6768
BlockAlloc: map[string]interface{}{
6869
"11865856": map[string]interface{}{
6970
// StateReceiver contract

params/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ var (
366366
"0": "0x0000000000000000000000000000000000000000",
367367
"26272256": "0x7Ee41D8A25641000661B1EF5E6AE8A00400466B0",
368368
},
369+
SkipValidatorByteCheck: []uint64{26160367, 26161087, 26171567, 26173743, 26175647},
369370
BlockAlloc: map[string]interface{}{
370371
// write as interface since that is how it is decoded in genesis
371372
"11865856": map[string]interface{}{
@@ -865,6 +866,7 @@ type BorConfig struct {
865866
BlockAlloc map[string]interface{} `json:"blockAlloc"`
866867
BurntContract map[string]string `json:"burntContract"` // governance contract where the token will be sent to and burnt in london fork
867868
Coinbase map[string]string `json:"coinbase"` // coinbase address
869+
SkipValidatorByteCheck []uint64 `json:"skipValidatorByteCheck"` // skip validator byte check
868870
JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on jaipur)
869871
DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on delhi)
870872
IndoreBlock *big.Int `json:"indoreBlock"` // Indore switch block (nil = no fork, 0 = already on indore)

0 commit comments

Comments
 (0)