From 71c3bf757c154b021a6b067a88e95cef96d99044 Mon Sep 17 00:00:00 2001 From: atvanguard <93arpit@gmail.com> Date: Thu, 14 May 2020 14:48:02 +0530 Subject: [PATCH] refactor: Tests + add test for OutOfTurnSign --- consensus/bor/bor.go | 37 ++++++-------- consensus/bor/bor_test/bor_test.go | 77 +++++++++++++++++++++------- consensus/bor/bor_test/helper.go | 80 ++++++++++++++++++++---------- consensus/bor/bor_test/span.json | 54 +++++--------------- consensus/bor/errors.go | 35 ++++++++++++- consensus/bor/snapshot.go | 6 +-- 6 files changed, 176 insertions(+), 113 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 3151e6e549..7ee2f52be1 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -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. diff --git a/consensus/bor/bor_test/bor_test.go b/consensus/bor/bor_test/bor_test.go index f478175b28..62d929220a 100644 --- a/consensus/bor/bor_test/bor_test.go +++ b/consensus/bor/bor_test/bor_test.go @@ -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) +} diff --git a/consensus/bor/bor_test/helper.go b/consensus/bor/bor_test/helper.go index 2a050eff8e..b59c3731fc 100644 --- a/consensus/bor/bor_test/helper.go +++ b/consensus/bor/bor_test/helper.go @@ -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 +} diff --git a/consensus/bor/bor_test/span.json b/consensus/bor/bor_test/span.json index 1f2679523a..893d3db4a6 100644 --- a/consensus/bor/bor_test/span.json +++ b/consensus/bor/bor_test/span.json @@ -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, diff --git a/consensus/bor/errors.go b/consensus/bor/errors.go index 7e7ee17915..f357705db4 100644 --- a/consensus/bor/errors.go +++ b/consensus/bor/errors.go @@ -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, + ) +} diff --git a/consensus/bor/snapshot.go b/consensus/bor/snapshot.go index 08088d7911..a62c2014b9 100644 --- a/consensus/bor/snapshot.go +++ b/consensus/bor/snapshot.go @@ -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