From 90dc9d5a6d4368e4c55942715a716cb5b045e855 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 3 Aug 2020 17:34:09 +0800 Subject: [PATCH] enforce backoff time for out-turn validator --- consensus/parlia/parlia.go | 56 +++++++++++++++++++++++---------- consensus/parlia/parlia_test.go | 41 ++++++++++++++---------- consensus/parlia/snapshot.go | 18 +++++++++-- 3 files changed, 81 insertions(+), 34 deletions(-) diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 7d5b324df9..dafba1c12b 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -48,8 +48,8 @@ const ( extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal validatorBytesLength = common.AddressLength - wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers - fixedBackOffTime = 200 * time.Millisecond + wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers + initialBackOffTime = uint64(1) // second systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system @@ -81,7 +81,7 @@ var ( common.HexToAddress(GovHubContract): true, common.HexToAddress(TokenHubContract): true, common.HexToAddress(RelayerIncentivizeContract): true, - common.HexToAddress(CrossChainContract): true, + common.HexToAddress(CrossChainContract): true, } ) @@ -293,7 +293,11 @@ func (p *Parlia) Author(header *types.Header) (common.Address, error) { // VerifyHeader checks whether a header conforms to the consensus rules. func (p *Parlia) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { - return p.verifyHeader(chain, header, nil) + err := p.verifyHeader(chain, header, nil) + if err != nil { + log.Error("==== failed to verify header", "err", err) + } + return err } // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The @@ -395,6 +399,15 @@ func (p *Parlia) verifyCascadingFields(chain consensus.ChainReader, header *type return consensus.ErrUnknownAncestor } + snap, err := p.snapshot(chain, number-1, header.ParentHash, parents) + if err != nil { + return err + } + + if header.Time < parent.Time+p.config.Period+backOffTime(snap, header.Coinbase) { + return consensus.ErrFutureBlock + } + // Verify that the gas limit is <= 2^63-1 cap := uint64(0x7fffffffffffffff) if header.GasLimit > cap { @@ -570,7 +583,7 @@ func (p *Parlia) verifySeal(chain consensus.ChainReader, header *types.Header, p // Ensure that the difficulty corresponds to the turn-ness of the signer if !p.fakeDiff { - inturn := snap.inturn(header.Number.Uint64(), signer) + inturn := snap.inturn(signer) if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { return errWrongDifficulty } @@ -626,8 +639,7 @@ func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) erro if parent == nil { return consensus.ErrUnknownAncestor } - - header.Time = parent.Time + p.config.Period + header.Time = parent.Time + p.config.Period + backOffTime(snap, p.val) if header.Time < uint64(time.Now().Unix()) { header.Time = uint64(time.Now().Unix()) } @@ -810,13 +822,6 @@ func (p *Parlia) Seal(chain consensus.ChainReader, block *types.Block, results c // 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 - if header.Difficulty.Cmp(diffNoTurn) == 0 { - // It's not our turn explicitly to sign, delay it a bit - wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTime - delay += time.Duration(fixedBackOffTime) + time.Duration(rand.Int63n(int64(wiggle))) - - log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) - } log.Info("Sealing block with", "number", number, "delay", delay, "headerDifficulty", header.Difficulty, "val", val.Hex()) @@ -861,7 +866,7 @@ func (p *Parlia) CalcDifficulty(chain consensus.ChainReader, time uint64, parent // 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) *big.Int { - if snap.inturn(snap.Number+1, signer) { + if snap.inturn(signer) { return new(big.Int).Set(diffInTurn) } return new(big.Int).Set(diffNoTurn) @@ -1140,6 +1145,26 @@ func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) { } } +func backOffTime(snap *Snapshot, val common.Address) uint64 { + if snap.inturn(val) { + return 0 + } else { + dis := snap.distanceToInTurn(val) + s := rand.NewSource(int64(snap.Number)) + r := rand.New(s) + n := len(snap.Validators) + backOffSteps := make([]uint64, 0, n) + for idx := uint64(0); idx < uint64(n); idx++ { + backOffSteps = append(backOffSteps, idx) + } + r.Shuffle(n, func(i, j int) { + backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i] + }) + delay := initialBackOffTime + backOffSteps[dis]*wiggleTime + return delay + } +} + // chain context type chainContext struct { Chain consensus.ChainReader @@ -1194,4 +1219,3 @@ func applyMessage( } return msg.Gas() - returnGas, err } - diff --git a/consensus/parlia/parlia_test.go b/consensus/parlia/parlia_test.go index 2c0b754a44..fc05013e92 100644 --- a/consensus/parlia/parlia_test.go +++ b/consensus/parlia/parlia_test.go @@ -4,7 +4,6 @@ import ( "fmt" "math/rand" "testing" - "time" "github.com/ethereum/go-ethereum/common" ) @@ -57,7 +56,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { return validators[idx] } - downDelay := time.Duration(0) + downDelay := uint64(0) for h := 1; h <= downBlocks; h++ { if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit { delete(recents, uint64(h)-limit) @@ -73,7 +72,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { if len(candidates) == 0 { panic("can not test such case") } - idx, delay := producerBlockDelay(candidates, totalValidators) + idx, delay := producerBlockDelay(candidates, h, totalValidators) downDelay = downDelay + delay recents[uint64(h)] = idx } else { @@ -81,13 +80,13 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { } } fmt.Printf("average delay is %v when there is %d validators and %d is down \n", - downDelay/time.Duration(downBlocks), totalValidators, downValidators) + downDelay/uint64(downBlocks), totalValidators, downValidators) for i := 0; i < downValidators; i++ { validators[down[i]] = true } - recoverDelay := time.Duration(0) + recoverDelay := uint64(0) lastseen := downBlocks for h := downBlocks + 1; h <= downBlocks+recoverBlocks; h++ { if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit { @@ -105,7 +104,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { if len(candidates) == 0 { panic("can not test such case") } - idx, delay := producerBlockDelay(candidates, totalValidators) + idx, delay := producerBlockDelay(candidates, h, totalValidators) recoverDelay = recoverDelay + delay recents[uint64(h)] = idx } else { @@ -116,18 +115,28 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) { recoverDelay, downValidators, lastseen) } -func producerBlockDelay(candidates map[int]bool, numOfValidators int) (int, time.Duration) { - minDur := time.Duration(0) - minIdx := 0 - wiggle := time.Duration(numOfValidators/2+1) * wiggleTime - for idx := range candidates { - sleepTime := rand.Int63n(int64(wiggle)) - if int64(minDur) < sleepTime { - minDur = time.Duration(rand.Int63n(int64(wiggle))) - minIdx = idx +func producerBlockDelay(candidates map[int]bool, height, numOfValidators int) (int, uint64) { + + s := rand.NewSource(int64(height)) + r := rand.New(s) + n := numOfValidators + backOffSteps := make([]int, 0, n) + for idx := 0; idx < n; idx++ { + backOffSteps = append(backOffSteps, idx) + } + r.Shuffle(n, func(i, j int) { + backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i] + }) + minDelay := numOfValidators + minCandidate := 0 + for c := range candidates { + if minDelay > backOffSteps[c] { + minDelay = backOffSteps[c] + minCandidate = c } } - return minIdx, minDur + delay := initialBackOffTime + uint64(minDelay)*wiggleTime + return minCandidate, delay } func randomAddress() common.Address { diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index b1af95f5fc..0d361ad53f 100644 --- a/consensus/parlia/snapshot.go +++ b/consensus/parlia/snapshot.go @@ -210,12 +210,26 @@ func (s *Snapshot) validators() []common.Address { } // inturn returns if a validator at a given block height is in-turn or not. -func (s *Snapshot) inturn(number uint64, validator common.Address) bool { +func (s *Snapshot) inturn(validator common.Address) bool { validators := s.validators() - offset := number % uint64(len(validators)) + offset := (s.Number + 1) % uint64(len(validators)) return validators[offset] == validator } +func (s *Snapshot) distanceToInTurn(validator common.Address) uint64 { + validators := s.validators() + offset := (s.Number + 1) % uint64(len(validators)) + idx := uint64(0) + for idx < uint64(len(validator)) && validators[idx] != validator { + idx++ + } + if offset > idx { + return uint64(len(validators)) + idx - offset + } else { + return idx - offset + } +} + func (s *Snapshot) supposeValidator() common.Address { validators := s.validators() index := (s.Number + 1) % uint64(len(validators))