refactor: Tests + add test for OutOfTurnSign

This commit is contained in:
atvanguard 2020-05-14 14:48:02 +05:30 committed by Arpit Agarwal
parent c27b8b7801
commit 71c3bf757c
6 changed files with 176 additions and 113 deletions

View file

@ -114,10 +114,6 @@ var (
// errInvalidDifficulty is returned if the difficulty of a block neither 1 or 2.
errInvalidDifficulty = errors.New("invalid difficulty")
// errWrongDifficulty is returned if the difficulty of a block doesn't match the
// turn of the signer.
errWrongDifficulty = errors.New("wrong difficulty")
// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
// the previous block's timestamp + the minimum block period.
ErrInvalidTimestamp = errors.New("invalid timestamp")
@ -126,9 +122,6 @@ var (
// be modified via out-of-range or non-contiguous headers.
errOutOfRangeChain = errors.New("out of range or non-contiguous chain")
// errUnauthorizedSigner is returned if a header is signed by a non-authorized entity.
errUnauthorizedSigner = errors.New("unauthorized signer")
// errRecentlySigned is returned if a header is signed by an authorized entity
// that already signed a header recently, thus is temporarily not allowed to.
errRecentlySigned = errors.New("recently signed")
@ -200,13 +193,6 @@ func encodeSigHeader(w io.Writer, header *types.Header) {
}
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have based on the previous blocks in the chain and the
// current signer.
func CalcDifficulty(snap *Snapshot, signer common.Address, sprint uint64) *big.Int {
return big.NewInt(0).SetUint64(snap.inturn(snap.Number+1, signer, sprint))
}
// CalcProducerDelay is the block delay algorithm based on block time, period, producerDelay and turn-ness of a signer
func CalcProducerDelay(number uint64, succession int, c *params.BorConfig) uint64 {
// When the block is the first block of the sprint, it is expected to be delayed by `producerDelay`.
@ -580,7 +566,7 @@ func (c *Bor) verifySeal(chain consensus.ChainReader, header *types.Header, pare
return err
}
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
return errUnauthorizedSigner
return &UnauthorizedSignerError{number, signer.Bytes()}
}
succession, err := snap.GetSignerSuccessionNumber(signer)
@ -601,9 +587,9 @@ func (c *Bor) verifySeal(chain consensus.ChainReader, header *types.Header, pare
// Ensure that the difficulty corresponds to the turn-ness of the signer
if !c.fakeDiff {
difficulty := snap.inturn(header.Number.Uint64(), signer, c.config.Sprint)
difficulty := snap.Difficulty(signer)
if header.Difficulty.Uint64() != difficulty {
return errWrongDifficulty
return &WrongDifficultyError{number, difficulty, header.Difficulty.Uint64(), signer.Bytes()}
}
}
@ -625,7 +611,7 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
}
// Set the correct difficulty
header.Difficulty = CalcDifficulty(snap, c.signer, c.config.Sprint)
header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.signer))
// Ensure the extra data has all it's components
if len(header.Extra) < extraVanity {
@ -659,10 +645,15 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
return consensus.ErrUnknownAncestor
}
succession, err := snap.GetSignerSuccessionNumber(c.signer)
if err != nil {
return err
var succession int
// if signer is not empty
if bytes.Compare(c.signer.Bytes(), common.Address{}.Bytes()) != 0 {
succession, err = snap.GetSignerSuccessionNumber(c.signer)
if err != nil {
return err
}
}
header.Time = parent.Time + CalcProducerDelay(number, succession, c.config)
if header.Time < uint64(time.Now().Unix()) {
header.Time = uint64(time.Now().Unix())
@ -764,7 +755,7 @@ func (c *Bor) Seal(chain consensus.ChainReader, block *types.Block, results chan
// Bail out if we're unauthorized to sign a block
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
return errUnauthorizedSigner
return &UnauthorizedSignerError{number, signer.Bytes()}
}
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
@ -852,7 +843,7 @@ func (c *Bor) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *t
if err != nil {
return nil
}
return CalcDifficulty(snap, c.signer, c.config.Sprint)
return new(big.Int).SetUint64(snap.Difficulty(c.signer))
}
// SealHash returns the hash of a block prior to it being sealed.

View file

@ -8,6 +8,7 @@ import (
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus/bor"
"github.com/maticnetwork/bor/core/rawdb"
"github.com/maticnetwork/bor/crypto"
"github.com/stretchr/testify/assert"
"github.com/maticnetwork/bor/core/types"
@ -28,23 +29,17 @@ func TestCommitSpan(t *testing.T) {
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
statedb, err := chain.State()
if err != nil {
t.Fatalf("%s", err)
}
_key, _ := hex.DecodeString(privKey)
block := init.genesis.ToBlock(db)
// 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)
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
// FetchWithRetry is invoked 2 times
// 1. bor.FinalizeAndAssemble to prepare a new block when calling insertNewBlock
// 1. bor.FinalizeAndAssemble to prepare a new block when calling buildNextBlock
// 2. bor.Finalize via(bc.insertChain => bc.processor.Process)
assert.True(t, h.AssertNumberOfCalls(t, "FetchWithRetry", 2))
assert.True(t, h.AssertCalled(t, "FetchWithRetry", "bor", "span", "1"))
validators, err := _bor.GetCurrentValidators(sprintSize, 256) // new span starts at 256
if err != nil {
t.Fatalf("%s", err)
@ -88,16 +83,11 @@ func TestIsValidatorAction(t *testing.T) {
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
statedb, err := chain.State()
if err != nil {
t.Fatalf("%s", err)
}
_key, _ := hex.DecodeString(privKey)
block := init.genesis.ToBlock(db)
for i := uint64(1); i <= spanSize; i++ {
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor)
block = insertNewBlock(t, _bor, chain, header, statedb, _key)
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
for _, validator := range heimdallSpan.SelectedProducers {
@ -119,3 +109,54 @@ func TestIsValidatorAction(t *testing.T) {
assert.True(t, _bor.IsValidatorAction(chain, _addr, tx))
}
}
func TestOutOfTurnSigning(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
res, _ := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
for i := uint64(1); i < spanSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
// insert spanSize-th block
// This account is one the out-of-turn validators for 1st (0-indexed) span
signer := "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd9"
signerKey, _ := hex.DecodeString(signer)
key, _ = crypto.HexToECDSA(signer)
addr = crypto.PubkeyToAddress(key.PublicKey)
expectedSuccessionNumber := 2
block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor)
_, err := chain.InsertChain([]*types.Block{block})
assert.Equal(t,
*err.(*bor.BlockTooSoonError),
bor.BlockTooSoonError{Number: spanSize, Succession: expectedSuccessionNumber})
expectedDifficulty := uint64(3 - expectedSuccessionNumber) // len(validators) - succession
header := block.Header()
header.Time += (bor.CalcProducerDelay(header.Number.Uint64(), expectedSuccessionNumber, init.genesis.Config.Bor) -
bor.CalcProducerDelay(header.Number.Uint64(), 0, init.genesis.Config.Bor))
sign(t, header, signerKey)
block = types.NewBlockWithHeader(header)
_, err = chain.InsertChain([]*types.Block{block})
assert.Equal(t,
*err.(*bor.WrongDifficultyError),
bor.WrongDifficultyError{Number: spanSize, Expected: expectedDifficulty, Actual: 3, Signer: addr.Bytes()})
header.Difficulty = new(big.Int).SetUint64(expectedDifficulty)
sign(t, header, signerKey)
block = types.NewBlockWithHeader(header)
_, err = chain.InsertChain([]*types.Block{block})
assert.Nil(t, err)
}

View file

@ -11,7 +11,6 @@ import (
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus/bor"
"github.com/maticnetwork/bor/core"
"github.com/maticnetwork/bor/core/state"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/crypto/secp256k1"
@ -23,11 +22,19 @@ import (
var (
// The genesis for tests was generated with following parameters
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7
validatorHeaderBytesLength = common.AddressLength + 20 // address + power
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
// Only this account is a validator for the 0th span
privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
key, _ = crypto.HexToECDSA(privKey)
addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7
// This account is one the validators for 1st span (0-indexed)
privKey2 = "9b28f36fbd67381120752d6172ecdcf10e06ab2d9a1367aac00cdcd6ac7855d3"
key2, _ = crypto.HexToECDSA(privKey2)
addr2 = crypto.PubkeyToAddress(key2.PublicKey) // 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791
validatorHeaderBytesLength = common.AddressLength + 20 // address + power
sprintSize uint64 = 4
spanSize uint64 = 8
)
@ -88,40 +95,36 @@ 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) *types.Block {
_, err := _bor.FinalizeAndAssemble(chain, header, statedb, nil, nil, nil)
if err != nil {
t.Fatalf("%s", err)
}
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), privKey)
if err != nil {
t.Fatalf("%s", err)
}
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
block := types.NewBlockWithHeader(header)
func insertNewBlock(t *testing.T, chain *core.BlockChain, block *types.Block) {
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
t.Fatalf("%s", err)
}
return block
}
func buildMinimalNextHeader(t *testing.T, block *types.Block, borConfig *params.BorConfig) *types.Header {
func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block *types.Block, signer []byte, borConfig *params.BorConfig) *types.Block {
header := block.Header()
header.Number.Add(header.Number, big.NewInt(1))
number := header.Number.Uint64()
if signer == nil {
signer = getSignerKey(header.Number.Uint64())
}
header.ParentHash = block.Hash()
header.Time += bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig)
header.Extra = make([]byte, 32+65) // vanity + extraSeal
currentValidators := []*bor.Validator{bor.NewValidator(addr, 10)}
isSpanEnd := (header.Number.Uint64()+1)%spanSize == 0
isSpanEnd := (number+1)%spanSize == 0
isSpanStart := number%spanSize == 0
isSprintEnd := (header.Number.Uint64()+1)%sprintSize == 0
if isSpanEnd {
_, heimdallSpan := loadSpanFromFile(t)
// this is to stash the validator bytes in the header
currentValidators = heimdallSpan.ValidatorSet.Validators
} else if header.Number.Uint64()%spanSize == 0 {
header.Difficulty = new(big.Int).SetInt64(5)
} else if isSpanStart {
header.Difficulty = new(big.Int).SetInt64(3)
}
if isSprintEnd {
sort.Sort(bor.ValidatorsByAddress(currentValidators))
@ -132,13 +135,25 @@ func buildMinimalNextHeader(t *testing.T, block *types.Block, borConfig *params.
}
copy(header.Extra[32:], validatorBytes)
}
_key, _ := hex.DecodeString(privKey)
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), _key)
state, err := chain.State()
if err != nil {
t.Fatalf("%s", err)
}
_, err = _bor.FinalizeAndAssemble(chain, header, state, nil, nil, nil)
if err != nil {
t.Fatalf("%s", err)
}
sign(t, header, signer)
return types.NewBlockWithHeader(header)
}
func sign(t *testing.T, header *types.Header, signer []byte) {
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), signer)
if err != nil {
t.Fatalf("%s", err)
}
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
return header
}
func loadSpanFromFile(t *testing.T) (*bor.ResponseWithHeight, *bor.HeimdallSpan) {
@ -157,3 +172,14 @@ func loadSpanFromFile(t *testing.T) (*bor.ResponseWithHeight, *bor.HeimdallSpan)
}
return res, heimdallSpan
}
func getSignerKey(number uint64) []byte {
signerKey := privKey
isSpanStart := number%spanSize == 0
if isSpanStart {
// validator set in the new span has changed
signerKey = privKey2
}
_key, _ := hex.DecodeString(signerKey)
return _key
}

View file

@ -6,85 +6,57 @@
"end_block": 6655,
"validator_set": {
"validators": [{
"ID": 3,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"pubKey": "0x046434e10a34ade13c4fea917346a9fd1473eac2138a0b4e2a36426871918be63188fde4edbf598457592c9a49fe3b0036dd5497079495d132e5045bf499c4bdb1",
"signer": "0xc787af4624cb3e80ee23ae7faac0f2acea2be34c",
"last_updated": 0,
"accum": -40000
}, {
"ID": 4,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"pubKey": "0x04d9d09f2afc9da3cccc164e8112eb6911a63f5ede10169768f800df83cf99c73f944411e9d4fac3543b11c5f84a82e56b36cfcd34f1d065855c1e2b27af8b5247",
"signer": "0x461295d3d9249215e758e939a150ab180950720b",
"last_updated": 0,
"accum": 10000
}, {
"ID": 5,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"power": 30,
"pubKey": "0x04a36f6ed1f93acb0a38f4cacbe2467c72458ac41ce3b12b34d758205b2bc5d930a4e059462da7a0976c32fce766e1f7e8d73933ae72ac2af231fe161187743932",
"signer": "0x836fe3e3dd0a5f77d9d5b0f67e48048aaafcd5a0",
"signer": "0x9fB29AAc15b9A4B7F17c3385939b007540f4d791",
"last_updated": 0,
"accum": 10000
}, {
"ID": 1,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"power": 20,
"pubKey": "0x04a312814042a6655c8e5ecf0c52cba0b6a6f3291c87cc42260a3c0222410c0d0d59b9139d1c56542e5df0ce2fce3a86ce13e93bd9bde0dc8ff664f8dd5294dead",
"signer": "0x925a91f8003aaeabea6037103123b93c50b86ca3",
"signer": "0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a",
"last_updated": 0,
"accum": 10000
}, {
"ID": 2,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"power": 10,
"pubKey": "0x0469536ae98030a7e83ec5ef3baffed2d05a32e31d978e58486f6bdb0fbbf240293838325116090190c0639db03f9cbd8b9aecfd269d016f46e3a2287fbf9ad232",
"signer": "0x71562b71999873DB5b286dF957af199Ec94617F7",
"signer": "0xc787af4624cb3e80ee23ae7faac0f2acea2be34c",
"last_updated": 0,
"accum": 10000
}],
"proposer": {
"ID": 3,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"pubKey": "0x046434e10a34ade13c4fea917346a9fd1473eac2138a0b4e2a36426871918be63188fde4edbf598457592c9a49fe3b0036dd5497079495d132e5045bf499c4bdb1",
"signer": "0x1c4f0f054a0d6a1415382dc0fd83c6535188b220",
"last_updated": 0,
"accum": -40000
}
"accum": 5000
}]
},
"selected_producers": [{
"ID": 5,
"startEpoch": 0,
"endEpoch": 0,
"power": 1,
"power": 30,
"pubKey": "0x04a36f6ed1f93acb0a38f4cacbe2467c72458ac41ce3b12b34d758205b2bc5d930a4e059462da7a0976c32fce766e1f7e8d73933ae72ac2af231fe161187743932",
"signer": "0x836fe3e3dd0a5f77d9d5b0f67e48048aaafcd5a0",
"signer": "0x9fB29AAc15b9A4B7F17c3385939b007540f4d791",
"last_updated": 0,
"accum": 10000
}, {
"ID": 1,
"startEpoch": 0,
"endEpoch": 0,
"power": 1,
"power": 20,
"pubKey": "0x04a312814042a6655c8e5ecf0c52cba0b6a6f3291c87cc42260a3c0222410c0d0d59b9139d1c56542e5df0ce2fce3a86ce13e93bd9bde0dc8ff664f8dd5294dead",
"signer": "0x925a91f8003aaeabea6037103123b93c50b86ca3",
"signer": "0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a",
"last_updated": 0,
"accum": 10000
}, {
"ID": 2,
"startEpoch": 0,
"endEpoch": 0,
"power": 2,
"power": 10,
"pubKey": "0x0469536ae98030a7e83ec5ef3baffed2d05a32e31d978e58486f6bdb0fbbf240293838325116090190c0639db03f9cbd8b9aecfd269d016f46e3a2287fbf9ad232",
"signer": "0xc787af4624cb3e80ee23ae7faac0f2acea2be34c",
"last_updated": 0,

View file

@ -90,7 +90,7 @@ type MismatchingValidatorsError struct {
func (e *MismatchingValidatorsError) Error() string {
return fmt.Sprintf(
"Mismatching validators at block %d\nValidatorBytes from snapshot: %x\nValidatorBytes in Header: %x\n",
"Mismatching validators at block %d\nValidatorBytes from snapshot: 0x%x\nValidatorBytes in Header: 0x%x\n",
e.Number,
e.ValidatorSetSnap,
e.ValidatorSetHeader,
@ -109,3 +109,36 @@ func (e *BlockTooSoonError) Error() string {
e.Succession,
)
}
// UnauthorizedSignerError is returned if a header is signed by a non-authorized entity.
type UnauthorizedSignerError struct {
Number uint64
Signer []byte
}
func (e *UnauthorizedSignerError) Error() string {
return fmt.Sprintf(
"Validator set for block %d doesn't contain the signer 0x%x\n",
e.Number,
e.Signer,
)
}
// WrongDifficultyError is returned if the difficulty of a block doesn't match the
// turn of the signer.
type WrongDifficultyError struct {
Number uint64
Expected uint64
Actual uint64
Signer []byte
}
func (e *WrongDifficultyError) Error() string {
return fmt.Sprintf(
"Wrong difficulty at block %d, expected: %d, actual %d. Signer was %x\n",
e.Number,
e.Expected,
e.Actual,
e.Signer,
)
}

View file

@ -155,7 +155,7 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
// check if signer is in validator set
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
return nil, errUnauthorizedSigner
return nil, &UnauthorizedSignerError{number, signer.Bytes()}
}
if _, err = snap.GetSignerSuccessionNumber(signer); err != nil {
@ -222,8 +222,8 @@ func (s *Snapshot) signers() []common.Address {
return sigs
}
// inturn returns if a signer at a given block height is in-turn or not.
func (s *Snapshot) inturn(number uint64, signer common.Address, epoch uint64) uint64 {
// Difficulty returns the difficulty for a particular signer at the current snapshot number
func (s *Snapshot) Difficulty(signer common.Address) uint64 {
// if signer is empty
if bytes.Compare(signer.Bytes(), common.Address{}.Bytes()) == 0 {
return 1