diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 695005f0907af8c41a6c6bf5c53c1644cad8cc53..e431c17dd5b5bd69cc255db95c4c6a90cf924b26 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -111,6 +111,7 @@ func (w *wizard) makeGenesis() { Period: 1, ProducerDelay: 5, Sprint: 60, + BackupMultiplier: 1, ValidatorContract: "0x0000000000000000000000000000000000001000", StateReceiverContract: "0x0000000000000000000000000000000000001001", } diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 204c560eba249222fe10e132de74d8602ca700b1..3151e6e5499b1baa2cac88c9a5fea75766ef9880 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -207,14 +207,18 @@ func CalcDifficulty(snap *Snapshot, signer common.Address, sprint uint64) *big.I return big.NewInt(0).SetUint64(snap.inturn(snap.Number+1, signer, sprint)) } -// CalcProducerDelay is the block delay algorithm based on block time and period / producerDelay values in genesis -func CalcProducerDelay(number uint64, period uint64, sprint uint64, producerDelay uint64) uint64 { +// 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`. // That is to allow time for block propagation in the last sprint - if number%sprint == 0 { - return producerDelay + delay := c.Period + if number%c.Sprint == 0 { + delay = c.ProducerDelay } - return period + if succession > 0 { + delay += uint64(succession) * c.BackupMultiplier + } + return delay } // BorRLP returns the rlp bytes which needs to be signed for the bor @@ -334,17 +338,6 @@ func (c *Bor) verifyHeader(chain consensus.ChainReader, header *types.Header, pa } number := header.Number.Uint64() - var parent *types.Header - if len(parents) > 0 { // if parents is nil, len(parents) is zero - parent = parents[len(parents)-1] - } else if number > 0 { - parent = chain.GetHeader(header.ParentHash, number-1) - } - - if parent != nil && header.Time < parent.Time+CalcProducerDelay(number, c.config.Period, c.config.Sprint, c.config.ProducerDelay) { - return consensus.ErrBlockTooSoon - } - // Don't waste time checking blocks from the future if header.Time > uint64(time.Now().Unix()) { return consensus.ErrFutureBlock @@ -590,10 +583,22 @@ func (c *Bor) verifySeal(chain consensus.ChainReader, header *types.Header, pare return errUnauthorizedSigner } - if _, err = snap.GetSignerSuccessionNumber(signer); err != nil { + succession, err := snap.GetSignerSuccessionNumber(signer) + if err != nil { return err } + var parent *types.Header + if len(parents) > 0 { // if parents is nil, len(parents) is zero + parent = parents[len(parents)-1] + } else if number > 0 { + parent = chain.GetHeader(header.ParentHash, number-1) + } + + if parent != nil && header.Time < parent.Time+CalcProducerDelay(number, succession, c.config) { + return &BlockTooSoonError{number, succession} + } + // 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) @@ -654,7 +659,11 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error { return consensus.ErrUnknownAncestor } - header.Time = parent.Time + CalcProducerDelay(number, c.config.Period, c.config.Sprint, c.config.ProducerDelay) + 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()) } @@ -765,8 +774,8 @@ func (c *Bor) Seal(chain consensus.ChainReader, block *types.Block, results chan // Sweet, the protocol permits us to sign the block, wait for our time delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple - wiggle := time.Duration(2*c.config.Period) * time.Second * time.Duration(successionNumber) - delay += wiggle + // wiggle was already accounted for in header.Time, this is just for logging + wiggle := time.Duration(successionNumber) * time.Duration(c.config.BackupMultiplier) * time.Second // Sign all the things! sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeBor, BorRLP(header)) diff --git a/consensus/bor/bor_test/bor_test.go b/consensus/bor/bor_test/bor_test.go index b7cf4659bda8612aa7a7ddece538a30e7aed7bac..f478175b28da5d0905906488714b97c3c861b63f 100644 --- a/consensus/bor/bor_test/bor_test.go +++ b/consensus/bor/bor_test/bor_test.go @@ -24,11 +24,7 @@ func TestCommitSpan(t *testing.T) { // Mock HeimdallClient.FetchWithRetry to return span data from span.json res, heimdallSpan := loadSpanFromFile(t) h := &mocks.IHeimdallClient{} - // FetchWithRetry is invoked 3 times - // 1. bor.FinalizeAndAssemble to prepare a new block when calling insertNewBlock - // 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) + h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil) _bor.SetHeimdallClient(h) db := init.ethereum.ChainDb() @@ -45,7 +41,10 @@ func TestCommitSpan(t *testing.T) { block = insertNewBlock(t, _bor, chain, header, statedb, _key) } - assert.True(t, h.AssertNumberOfCalls(t, "FetchWithRetry", 3)) + // FetchWithRetry is invoked 2 times + // 1. bor.FinalizeAndAssemble to prepare a new block when calling insertNewBlock + // 2. bor.Finalize via(bc.insertChain => bc.processor.Process) + assert.True(t, h.AssertNumberOfCalls(t, "FetchWithRetry", 2)) validators, err := _bor.GetCurrentValidators(sprintSize, 256) // new span starts at 256 if err != nil { t.Fatalf("%s", err) diff --git a/consensus/bor/bor_test/genesis.json b/consensus/bor/bor_test/genesis.json index 5525732b9d0e96d94c09ec326e38158b58222274..5113190b181e2f2445673e531c1155188ce092b3 100644 --- a/consensus/bor/bor_test/genesis.json +++ b/consensus/bor/bor_test/genesis.json @@ -11,6 +11,7 @@ "bor": { "period": 1, "producerDelay": 4, + "backupMultiplier": 1, "sprint": 4, "validatorContract": "0x0000000000000000000000000000000000001000", "stateReceiverContract": "0x0000000000000000000000000000000000001001" diff --git a/consensus/bor/bor_test/helper.go b/consensus/bor/bor_test/helper.go index f812599dc9e487f7914bcdc93eb43b6ee8e20f04..2a050eff8e35f7572edc62bbc4bc24bf92191acf 100644 --- a/consensus/bor/bor_test/helper.go +++ b/consensus/bor/bor_test/helper.go @@ -111,7 +111,7 @@ func buildMinimalNextHeader(t *testing.T, block *types.Block, borConfig *params. header := block.Header() header.Number.Add(header.Number, big.NewInt(1)) header.ParentHash = block.Hash() - header.Time += bor.CalcProducerDelay(header.Number.Uint64(), borConfig.Period, borConfig.Sprint, borConfig.ProducerDelay) + header.Time += bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig) header.Extra = make([]byte, 32+65) // vanity + extraSeal currentValidators := []*bor.Validator{bor.NewValidator(addr, 10)} diff --git a/consensus/bor/errors.go b/consensus/bor/errors.go index b058adc7f4c64986a4d252f919a431ec0e8aa725..7e7ee17915f83557066e7f07c50cbf80bfec54d0 100644 --- a/consensus/bor/errors.go +++ b/consensus/bor/errors.go @@ -96,3 +96,16 @@ func (e *MismatchingValidatorsError) Error() string { e.ValidatorSetHeader, ) } + +type BlockTooSoonError struct { + Number uint64 + Succession int +} + +func (e *BlockTooSoonError) Error() string { + return fmt.Sprintf( + "Block %d was created too soon. Signer turn-ness number is %d\n", + e.Number, + e.Succession, + ) +} diff --git a/params/config.go b/params/config.go index e56336fe2d5f6d01a12c247f6ca846817d6eb251..3af6bb064becce65bc2a228232bf740163c3ae3c 100644 --- a/params/config.go +++ b/params/config.go @@ -319,6 +319,7 @@ type BorConfig struct { Period uint64 `json:"period"` // Number of seconds between blocks to enforce ProducerDelay uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval Sprint uint64 `json:"sprint"` // Epoch length to proposer + BackupMultiplier uint64 `json:"backupMultiplier"` // Backup multiplier to determine the wiggle time ValidatorContract string `json:"validatorContract"` // Validator set contract StateReceiverContract string `json:"stateReceiverContract"` // State receiver contract }