mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-12 23:13:45 +00:00
fix: Check validator bytes at first block of sprint (MAT-1311) (#56)
* Check validator bytes at first block of sprint * not required to get parent again * fix tests * fix build error
This commit is contained in:
parent
d1ea515af1
commit
0f8d3b1006
6 changed files with 78 additions and 53 deletions
|
|
@ -105,10 +105,6 @@ var (
|
||||||
// invalid list of validators (i.e. non divisible by 40 bytes).
|
// invalid list of validators (i.e. non divisible by 40 bytes).
|
||||||
errInvalidSpanValidators = errors.New("invalid validator list on sprint end block")
|
errInvalidSpanValidators = errors.New("invalid validator list on sprint end block")
|
||||||
|
|
||||||
// errMismatchingSprintValidators is returned if a sprint block contains a
|
|
||||||
// list of validators different than the one the local node calculated.
|
|
||||||
errMismatchingSprintValidators = errors.New("mismatching validator list on sprint block")
|
|
||||||
|
|
||||||
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
|
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
|
||||||
errInvalidMixDigest = errors.New("non-zero mix digest")
|
errInvalidMixDigest = errors.New("non-zero mix digest")
|
||||||
|
|
||||||
|
|
@ -436,9 +432,9 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainReader, header *types.H
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
isSprintEnd := (number+1)%c.config.Sprint == 0
|
|
||||||
// verify the validator list in the last sprint block
|
// verify the validator list in the last sprint block
|
||||||
if isSprintEnd {
|
if isSprintStart(number, c.config.Sprint) {
|
||||||
|
parentValidatorBytes := parent.Extra[extraVanity : len(parent.Extra)-extraSeal]
|
||||||
validatorsBytes := make([]byte, len(snap.ValidatorSet.Validators)*validatorHeaderBytesLength)
|
validatorsBytes := make([]byte, len(snap.ValidatorSet.Validators)*validatorHeaderBytesLength)
|
||||||
|
|
||||||
currentValidators := snap.ValidatorSet.Copy().Validators
|
currentValidators := snap.ValidatorSet.Copy().Validators
|
||||||
|
|
@ -448,8 +444,8 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainReader, header *types.H
|
||||||
copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes())
|
copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes())
|
||||||
}
|
}
|
||||||
// len(header.Extra) >= extraVanity+extraSeal has already been validated in validateHeaderExtraField, so this won't result in a panic
|
// len(header.Extra) >= extraVanity+extraSeal has already been validated in validateHeaderExtraField, so this won't result in a panic
|
||||||
if !bytes.Equal(header.Extra[extraVanity:len(header.Extra)-extraSeal], validatorsBytes) {
|
if !bytes.Equal(parentValidatorBytes, validatorsBytes) {
|
||||||
return errMismatchingSprintValidators
|
return &MismatchingValidatorsError{number - 1, validatorsBytes, parentValidatorBytes}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1417,3 +1413,7 @@ func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*Validator)
|
||||||
v.UpdateWithChangeSet(changes)
|
v.UpdateWithChangeSet(changes)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSprintStart(number, sprint uint64) bool {
|
||||||
|
return number%sprint == 0
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,31 +24,34 @@ func TestCommitSpan(t *testing.T) {
|
||||||
// Mock HeimdallClient.FetchWithRetry to return span data from span.json
|
// Mock HeimdallClient.FetchWithRetry to return span data from span.json
|
||||||
res, heimdallSpan := loadSpanFromFile(t)
|
res, heimdallSpan := loadSpanFromFile(t)
|
||||||
h := &mocks.IHeimdallClient{}
|
h := &mocks.IHeimdallClient{}
|
||||||
h.On("FetchWithRetry", "bor", "span", "1").
|
// FetchWithRetry is invoked 3 times
|
||||||
Return(res, nil).
|
// 1. bor.FinalizeAndAssemble to prepare a new block when calling insertNewBlock
|
||||||
Times(2) // both FinalizeAndAssemble and chain.InsertChain call HeimdallClient.FetchWithRetry. @todo Investigate this in depth
|
// 2. bor.Finalize via(bc.insertChain => bc.processor.Process)
|
||||||
|
// 3. bor.FinalizeAndAssemble via worker.commit
|
||||||
|
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil).Times(3)
|
||||||
_bor.SetHeimdallClient(h)
|
_bor.SetHeimdallClient(h)
|
||||||
|
|
||||||
db := init.ethereum.ChainDb()
|
db := init.ethereum.ChainDb()
|
||||||
block := init.genesis.ToBlock(db)
|
|
||||||
// Build 1st block's header
|
|
||||||
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor)
|
|
||||||
|
|
||||||
statedb, err := chain.State()
|
statedb, err := chain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s", err)
|
t.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_key, _ := hex.DecodeString(privKey)
|
_key, _ := hex.DecodeString(privKey)
|
||||||
insertNewBlock(t, _bor, chain, header, statedb, _key)
|
|
||||||
|
|
||||||
assert.True(t, h.AssertNumberOfCalls(t, "FetchWithRetry", 2))
|
block := init.genesis.ToBlock(db)
|
||||||
validators, err := _bor.GetCurrentValidators(1, 256) // new span starts at 256
|
// Insert sprintSize # of blocks so that span is fetched at the start of a new sprint
|
||||||
|
for i := uint64(1); i <= sprintSize; i++ {
|
||||||
|
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor)
|
||||||
|
block = insertNewBlock(t, _bor, chain, header, statedb, _key)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, h.AssertNumberOfCalls(t, "FetchWithRetry", 3))
|
||||||
|
validators, err := _bor.GetCurrentValidators(sprintSize, 256) // new span starts at 256
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s", err)
|
t.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, len(validators), 3)
|
assert.Equal(t, 3, len(validators))
|
||||||
for i, validator := range validators {
|
for i, validator := range validators {
|
||||||
assert.Equal(t, validator.Address.Bytes(), heimdallSpan.SelectedProducers[i].Address.Bytes())
|
assert.Equal(t, validator.Address.Bytes(), heimdallSpan.SelectedProducers[i].Address.Bytes())
|
||||||
assert.Equal(t, validator.VotingPower, heimdallSpan.SelectedProducers[i].VotingPower)
|
assert.Equal(t, validator.VotingPower, heimdallSpan.SelectedProducers[i].VotingPower)
|
||||||
|
|
@ -85,29 +88,17 @@ func TestIsValidatorAction(t *testing.T) {
|
||||||
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
|
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
|
||||||
_bor.SetHeimdallClient(h)
|
_bor.SetHeimdallClient(h)
|
||||||
|
|
||||||
// Build 1st block's header
|
|
||||||
db := init.ethereum.ChainDb()
|
db := init.ethereum.ChainDb()
|
||||||
block := init.genesis.ToBlock(db)
|
|
||||||
|
|
||||||
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor)
|
|
||||||
statedb, err := chain.State()
|
statedb, err := chain.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s", err)
|
t.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_key, _ := hex.DecodeString(privKey)
|
_key, _ := hex.DecodeString(privKey)
|
||||||
insertNewBlock(t, _bor, chain, header, statedb, _key)
|
|
||||||
block = types.NewBlockWithHeader(header)
|
|
||||||
|
|
||||||
var headers []*types.Header
|
block := init.genesis.ToBlock(db)
|
||||||
for i := int64(2); i <= 255; i++ {
|
for i := uint64(1); i <= spanSize; i++ {
|
||||||
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor)
|
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor)
|
||||||
headers = append(headers, header)
|
block = insertNewBlock(t, _bor, chain, header, statedb, _key)
|
||||||
block = types.NewBlockWithHeader(header)
|
|
||||||
}
|
|
||||||
t.Logf("inserting %v headers", len(headers))
|
|
||||||
if _, err := chain.InsertHeaderChain(headers, 0); err != nil {
|
|
||||||
t.Fatalf("%s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, validator := range heimdallSpan.SelectedProducers {
|
for _, validator := range heimdallSpan.SelectedProducers {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -5,8 +5,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/maticnetwork/bor/common"
|
||||||
"github.com/maticnetwork/bor/consensus/bor"
|
"github.com/maticnetwork/bor/consensus/bor"
|
||||||
"github.com/maticnetwork/bor/core"
|
"github.com/maticnetwork/bor/core"
|
||||||
"github.com/maticnetwork/bor/core/state"
|
"github.com/maticnetwork/bor/core/state"
|
||||||
|
|
@ -20,10 +22,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
// The genesis for tests was generated with following parameters
|
||||||
privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
||||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
||||||
addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7
|
||||||
|
validatorHeaderBytesLength = common.AddressLength + 20 // address + power
|
||||||
|
sprintSize uint64 = 4
|
||||||
|
spanSize uint64 = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
type initializeData struct {
|
type initializeData struct {
|
||||||
|
|
@ -82,7 +88,7 @@ func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertNewBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, header *types.Header, statedb *state.StateDB, privKey []byte) {
|
func insertNewBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, header *types.Header, statedb *state.StateDB, privKey []byte) *types.Block {
|
||||||
_, err := _bor.FinalizeAndAssemble(chain, header, statedb, nil, nil, nil)
|
_, err := _bor.FinalizeAndAssemble(chain, header, statedb, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s", err)
|
t.Fatalf("%s", err)
|
||||||
|
|
@ -98,6 +104,7 @@ func insertNewBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, header
|
||||||
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
|
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
|
||||||
t.Fatalf("%s", err)
|
t.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMinimalNextHeader(t *testing.T, block *types.Block, borConfig *params.BorConfig) *types.Header {
|
func buildMinimalNextHeader(t *testing.T, block *types.Block, borConfig *params.BorConfig) *types.Header {
|
||||||
|
|
@ -105,15 +112,25 @@ func buildMinimalNextHeader(t *testing.T, block *types.Block, borConfig *params.
|
||||||
header.Number.Add(header.Number, big.NewInt(1))
|
header.Number.Add(header.Number, big.NewInt(1))
|
||||||
header.ParentHash = block.Hash()
|
header.ParentHash = block.Hash()
|
||||||
header.Time += bor.CalcProducerDelay(header.Number.Uint64(), borConfig.Period, borConfig.Sprint, borConfig.ProducerDelay)
|
header.Time += bor.CalcProducerDelay(header.Number.Uint64(), borConfig.Period, borConfig.Sprint, borConfig.ProducerDelay)
|
||||||
isSprintEnd := (header.Number.Uint64()+1)%borConfig.Sprint == 0
|
header.Extra = make([]byte, 32+65) // vanity + extraSeal
|
||||||
|
|
||||||
|
currentValidators := []*bor.Validator{bor.NewValidator(addr, 10)}
|
||||||
|
isSpanEnd := (header.Number.Uint64()+1)%spanSize == 0
|
||||||
|
isSprintEnd := (header.Number.Uint64()+1)%sprintSize == 0
|
||||||
|
if isSpanEnd {
|
||||||
|
_, heimdallSpan := loadSpanFromFile(t)
|
||||||
|
currentValidators = heimdallSpan.ValidatorSet.Validators
|
||||||
|
} else if header.Number.Uint64()%spanSize == 0 {
|
||||||
|
header.Difficulty = new(big.Int).SetInt64(5)
|
||||||
|
}
|
||||||
if isSprintEnd {
|
if isSprintEnd {
|
||||||
header.Extra = make([]byte, 32+40+65) // vanity + validatorBytes + extraSeal
|
sort.Sort(bor.ValidatorsByAddress(currentValidators))
|
||||||
// the genesis file was initialized with a validator 0x71562b71999873db5b286df957af199ec94617f7 with power 10
|
validatorBytes := make([]byte, len(currentValidators)*validatorHeaderBytesLength)
|
||||||
// So, if you change ./genesis.json, do change the following as well
|
header.Extra = make([]byte, 32+len(validatorBytes)+65) // vanity + validatorBytes + extraSeal
|
||||||
validatorBytes, _ := hex.DecodeString("71562b71999873db5b286df957af199ec94617f7000000000000000000000000000000000000000a")
|
for i, val := range currentValidators {
|
||||||
copy(header.Extra[32:72], validatorBytes)
|
copy(validatorBytes[i*validatorHeaderBytesLength:], val.HeaderBytes())
|
||||||
} else {
|
}
|
||||||
header.Extra = make([]byte, 32+65) // vanity + extraSeal
|
copy(header.Extra[32:], validatorBytes)
|
||||||
}
|
}
|
||||||
_key, _ := hex.DecodeString(privKey)
|
_key, _ := hex.DecodeString(privKey)
|
||||||
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), _key)
|
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), _key)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
"endEpoch": 0,
|
"endEpoch": 0,
|
||||||
"power": 10000,
|
"power": 10000,
|
||||||
"pubKey": "0x0469536ae98030a7e83ec5ef3baffed2d05a32e31d978e58486f6bdb0fbbf240293838325116090190c0639db03f9cbd8b9aecfd269d016f46e3a2287fbf9ad232",
|
"pubKey": "0x0469536ae98030a7e83ec5ef3baffed2d05a32e31d978e58486f6bdb0fbbf240293838325116090190c0639db03f9cbd8b9aecfd269d016f46e3a2287fbf9ad232",
|
||||||
"signer": "0x1c4f0f054a0d6a1415382dc0fd83c6535188b220",
|
"signer": "0x71562b71999873DB5b286dF957af199Ec94617F7",
|
||||||
"last_updated": 0,
|
"last_updated": 0,
|
||||||
"accum": 10000
|
"accum": 10000
|
||||||
}],
|
}],
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,20 @@ func (e *SealingInFlightError) Error() string {
|
||||||
e.Number,
|
e.Number,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MismatchingValidatorsError is returned if a last block in sprint contains a
|
||||||
|
// list of validators different from the one that local node calculated
|
||||||
|
type MismatchingValidatorsError struct {
|
||||||
|
Number uint64
|
||||||
|
ValidatorSetSnap []byte
|
||||||
|
ValidatorSetHeader []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MismatchingValidatorsError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Mismatching validators at block %d\nValidatorBytes from snapshot: %x\nValidatorBytes in Header: %x\n",
|
||||||
|
e.Number,
|
||||||
|
e.ValidatorSetSnap,
|
||||||
|
e.ValidatorSetHeader,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue