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 4, 2020
1 parent 5cc893a commit 929b9b0
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 33 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

0 comments on commit 929b9b0

Please sign in to comment.