Skip to content

Commit

Permalink
enforce backoff time for out-turn validator
Browse files Browse the repository at this point in the history
  • Loading branch information
unclezoro committed Aug 5, 2020
1 parent 5cc893a commit 546f569
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 35 deletions.
50 changes: 35 additions & 15 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
}
)

Expand Down Expand Up @@ -395,6 +395,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 {
Expand Down Expand Up @@ -570,7 +579,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
}
Expand Down Expand Up @@ -626,8 +635,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())
}
Expand Down Expand Up @@ -810,13 +818,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())

Expand Down Expand Up @@ -861,7 +862,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)
Expand Down Expand Up @@ -1140,6 +1141,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
Expand Down Expand Up @@ -1194,4 +1215,3 @@ func applyMessage(
}
return msg.Gas() - returnGas, err
}

41 changes: 25 additions & 16 deletions consensus/parlia/parlia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"math/rand"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
)
Expand Down Expand Up @@ -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)
Expand All @@ -73,21 +72,21 @@ 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 {
recents[uint64(h)] = proposer
}
}
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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
18 changes: 16 additions & 2 deletions consensus/parlia/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
5 changes: 3 additions & 2 deletions eth/fetcher/block_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,11 +681,12 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
go f.broadcastBlock(block, true)

case consensus.ErrFutureBlock:
// Weird future block, don't fail, but neither propagate
log.Error("Received future block", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
f.dropPeer(peer)

default:
// Something went very wrong, drop the peer
log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
log.Error("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
f.dropPeer(peer)
return
}
Expand Down

0 comments on commit 546f569

Please sign in to comment.