Skip to content

Commit a8c9e53

Browse files
authored
enforce backoff time for out-turn validator (ethereum#23)
1 parent 83d72b8 commit a8c9e53

File tree

8 files changed

+188
-41
lines changed

8 files changed

+188
-41
lines changed

cmd/geth/retesteth.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ type CParamsParams struct {
133133
ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"`
134134
ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"`
135135
IstanbulBlock *math.HexOrDecimal64 `json:"istanbulForkBlock"`
136+
RamanujanForkBlock *math.HexOrDecimal64 `json:"ramanujanForkBlock"`
136137
ChainID *math.HexOrDecimal256 `json:"chainID"`
137138
MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"`
138139
TieBreakingGas bool `json:"tieBreakingGas"`
@@ -322,6 +323,7 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa
322323
constantinopleBlock *big.Int
323324
petersburgBlock *big.Int
324325
istanbulBlock *big.Int
326+
ramanujanBlock *big.Int
325327
)
326328
if chainParams.Params.HomesteadForkBlock != nil {
327329
homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock))
@@ -351,6 +353,9 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa
351353
if chainParams.Params.IstanbulBlock != nil {
352354
istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock))
353355
}
356+
if chainParams.Params.RamanujanForkBlock != nil {
357+
ramanujanBlock = big.NewInt(int64(*chainParams.Params.RamanujanForkBlock))
358+
}
354359

355360
genesis := &core.Genesis{
356361
Config: &params.ChainConfig{
@@ -365,6 +370,7 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa
365370
ConstantinopleBlock: constantinopleBlock,
366371
PetersburgBlock: petersburgBlock,
367372
IstanbulBlock: istanbulBlock,
373+
RamanujanBlock: ramanujanBlock,
368374
},
369375
Nonce: uint64(chainParams.Genesis.Nonce),
370376
Timestamp: uint64(chainParams.Genesis.Timestamp),

consensus/parlia/parlia.go

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ const (
4848
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
4949

5050
validatorBytesLength = common.AddressLength
51-
wiggleTime = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
52-
fixedBackOffTime = 200 * time.Millisecond
51+
wiggleTime = uint64(1) // second, Random delay (per signer) to allow concurrent signers
52+
initialBackOffTime = uint64(1) // second
5353

5454
systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system
5555

@@ -81,7 +81,7 @@ var (
8181
common.HexToAddress(GovHubContract): true,
8282
common.HexToAddress(TokenHubContract): true,
8383
common.HexToAddress(RelayerIncentivizeContract): true,
84-
common.HexToAddress(CrossChainContract): true,
84+
common.HexToAddress(CrossChainContract): true,
8585
}
8686
)
8787

@@ -395,6 +395,16 @@ func (p *Parlia) verifyCascadingFields(chain consensus.ChainReader, header *type
395395
return consensus.ErrUnknownAncestor
396396
}
397397

398+
snap, err := p.snapshot(chain, number-1, header.ParentHash, parents)
399+
if err != nil {
400+
return err
401+
}
402+
403+
err = p.blockTimeVerifyForRamanujanFork(snap, header, parent)
404+
if err != nil {
405+
return nil
406+
}
407+
398408
// Verify that the gas limit is <= 2^63-1
399409
capacity := uint64(0x7fffffffffffffff)
400410
if header.GasLimit > capacity {
@@ -570,7 +580,7 @@ func (p *Parlia) verifySeal(chain consensus.ChainReader, header *types.Header, p
570580

571581
// Ensure that the difficulty corresponds to the turn-ness of the signer
572582
if !p.fakeDiff {
573-
inturn := snap.inturn(header.Number.Uint64(), signer)
583+
inturn := snap.inturn(signer)
574584
if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
575585
return errWrongDifficulty
576586
}
@@ -626,8 +636,7 @@ func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) erro
626636
if parent == nil {
627637
return consensus.ErrUnknownAncestor
628638
}
629-
630-
header.Time = parent.Time + p.config.Period
639+
header.Time = p.blockTimeForRamanujanFork(snap, header, parent)
631640
if header.Time < uint64(time.Now().Unix()) {
632641
header.Time = uint64(time.Now().Unix())
633642
}
@@ -809,14 +818,7 @@ func (p *Parlia) Seal(chain consensus.ChainReader, block *types.Block, results c
809818
}
810819

811820
// Sweet, the protocol permits us to sign the block, wait for our time
812-
delay := time.Until(time.Unix(int64(header.Time), 0)) // nolint: gosimple
813-
if header.Difficulty.Cmp(diffNoTurn) == 0 {
814-
// It's not our turn explicitly to sign, delay it a bit
815-
wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTime
816-
delay += time.Duration(fixedBackOffTime) + time.Duration(rand.Int63n(int64(wiggle)))
817-
818-
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
819-
}
821+
delay := p.delayForRamanujanFork(snap, header)
820822

821823
log.Info("Sealing block with", "number", number, "delay", delay, "headerDifficulty", header.Difficulty, "val", val.Hex())
822824

@@ -861,7 +863,7 @@ func (p *Parlia) CalcDifficulty(chain consensus.ChainReader, time uint64, parent
861863
// that a new block should have based on the previous blocks in the chain and the
862864
// current signer.
863865
func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
864-
if snap.inturn(snap.Number+1, signer) {
866+
if snap.inturn(signer) {
865867
return new(big.Int).Set(diffInTurn)
866868
}
867869
return new(big.Int).Set(diffNoTurn)
@@ -1140,6 +1142,26 @@ func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) {
11401142
}
11411143
}
11421144

1145+
func backOffTime(snap *Snapshot, val common.Address) uint64 {
1146+
if snap.inturn(val) {
1147+
return 0
1148+
} else {
1149+
dis := snap.distanceToInTurn(val)
1150+
s := rand.NewSource(int64(snap.Number))
1151+
r := rand.New(s)
1152+
n := len(snap.Validators)
1153+
backOffSteps := make([]uint64, 0, n)
1154+
for idx := uint64(0); idx < uint64(n); idx++ {
1155+
backOffSteps = append(backOffSteps, idx)
1156+
}
1157+
r.Shuffle(n, func(i, j int) {
1158+
backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i]
1159+
})
1160+
delay := initialBackOffTime + backOffSteps[dis]*wiggleTime
1161+
return delay
1162+
}
1163+
}
1164+
11431165
// chain context
11441166
type chainContext struct {
11451167
Chain consensus.ChainReader
@@ -1194,4 +1216,3 @@ func applyMessage(
11941216
}
11951217
return msg.Gas() - returnGas, err
11961218
}
1197-

consensus/parlia/parlia_test.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"math/rand"
66
"testing"
7-
"time"
87

98
"github.com/ethereum/go-ethereum/common"
109
)
@@ -57,7 +56,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
5756
return validators[idx]
5857
}
5958

60-
downDelay := time.Duration(0)
59+
downDelay := uint64(0)
6160
for h := 1; h <= downBlocks; h++ {
6261
if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit {
6362
delete(recents, uint64(h)-limit)
@@ -73,21 +72,21 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
7372
if len(candidates) == 0 {
7473
panic("can not test such case")
7574
}
76-
idx, delay := producerBlockDelay(candidates, totalValidators)
75+
idx, delay := producerBlockDelay(candidates, h, totalValidators)
7776
downDelay = downDelay + delay
7877
recents[uint64(h)] = idx
7978
} else {
8079
recents[uint64(h)] = proposer
8180
}
8281
}
8382
fmt.Printf("average delay is %v when there is %d validators and %d is down \n",
84-
downDelay/time.Duration(downBlocks), totalValidators, downValidators)
83+
downDelay/uint64(downBlocks), totalValidators, downValidators)
8584

8685
for i := 0; i < downValidators; i++ {
8786
validators[down[i]] = true
8887
}
8988

90-
recoverDelay := time.Duration(0)
89+
recoverDelay := uint64(0)
9190
lastseen := downBlocks
9291
for h := downBlocks + 1; h <= downBlocks+recoverBlocks; h++ {
9392
if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit {
@@ -105,7 +104,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
105104
if len(candidates) == 0 {
106105
panic("can not test such case")
107106
}
108-
idx, delay := producerBlockDelay(candidates, totalValidators)
107+
idx, delay := producerBlockDelay(candidates, h, totalValidators)
109108
recoverDelay = recoverDelay + delay
110109
recents[uint64(h)] = idx
111110
} else {
@@ -116,18 +115,28 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
116115
recoverDelay, downValidators, lastseen)
117116
}
118117

119-
func producerBlockDelay(candidates map[int]bool, numOfValidators int) (int, time.Duration) {
120-
minDur := time.Duration(0)
121-
minIdx := 0
122-
wiggle := time.Duration(numOfValidators/2+1) * wiggleTime
123-
for idx := range candidates {
124-
sleepTime := rand.Int63n(int64(wiggle))
125-
if int64(minDur) < sleepTime {
126-
minDur = time.Duration(rand.Int63n(int64(wiggle)))
127-
minIdx = idx
118+
func producerBlockDelay(candidates map[int]bool, height, numOfValidators int) (int, uint64) {
119+
120+
s := rand.NewSource(int64(height))
121+
r := rand.New(s)
122+
n := numOfValidators
123+
backOffSteps := make([]int, 0, n)
124+
for idx := 0; idx < n; idx++ {
125+
backOffSteps = append(backOffSteps, idx)
126+
}
127+
r.Shuffle(n, func(i, j int) {
128+
backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i]
129+
})
130+
minDelay := numOfValidators
131+
minCandidate := 0
132+
for c := range candidates {
133+
if minDelay > backOffSteps[c] {
134+
minDelay = backOffSteps[c]
135+
minCandidate = c
128136
}
129137
}
130-
return minIdx, minDur
138+
delay := initialBackOffTime + uint64(minDelay)*wiggleTime
139+
return minCandidate, delay
131140
}
132141

133142
func randomAddress() common.Address {

consensus/parlia/ramanujanfork.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package parlia
2+
3+
import (
4+
"math/rand"
5+
"time"
6+
7+
"github.com/ethereum/go-ethereum/consensus"
8+
"github.com/ethereum/go-ethereum/core/types"
9+
)
10+
11+
const (
12+
wiggleTimeBeforeFork = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
13+
fixedBackOffTimeBeforeFork = 200 * time.Millisecond
14+
)
15+
16+
func (p *Parlia) delayForRamanujanFork(snap *Snapshot, header *types.Header) time.Duration {
17+
delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple
18+
if p.chainConfig.IsRamanujan(header.Number) {
19+
return delay
20+
}
21+
wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTimeBeforeFork
22+
return delay + time.Duration(fixedBackOffTimeBeforeFork) + time.Duration(rand.Int63n(int64(wiggle)))
23+
}
24+
25+
func (p *Parlia) blockTimeForRamanujanFork(snap *Snapshot, header, parent *types.Header) uint64 {
26+
blockTime := parent.Time + p.config.Period
27+
if p.chainConfig.IsRamanujan(header.Number) {
28+
blockTime = blockTime + backOffTime(snap, p.val)
29+
}
30+
return blockTime
31+
}
32+
33+
func (p *Parlia) blockTimeVerifyForRamanujanFork(snap *Snapshot, header, parent *types.Header) error {
34+
if p.chainConfig.IsRamanujan(header.Number) {
35+
if header.Time < parent.Time+p.config.Period+backOffTime(snap, header.Coinbase) {
36+
return consensus.ErrFutureBlock
37+
}
38+
}
39+
return nil
40+
}

consensus/parlia/snapshot.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,26 @@ func (s *Snapshot) validators() []common.Address {
210210
}
211211

212212
// inturn returns if a validator at a given block height is in-turn or not.
213-
func (s *Snapshot) inturn(number uint64, validator common.Address) bool {
213+
func (s *Snapshot) inturn(validator common.Address) bool {
214214
validators := s.validators()
215-
offset := number % uint64(len(validators))
215+
offset := (s.Number + 1) % uint64(len(validators))
216216
return validators[offset] == validator
217217
}
218218

219+
func (s *Snapshot) distanceToInTurn(validator common.Address) uint64 {
220+
validators := s.validators()
221+
offset := (s.Number + 1) % uint64(len(validators))
222+
idx := uint64(0)
223+
for idx < uint64(len(validator)) && validators[idx] != validator {
224+
idx++
225+
}
226+
if offset > idx {
227+
return uint64(len(validators)) + idx - offset
228+
} else {
229+
return idx - offset
230+
}
231+
}
232+
219233
func (s *Snapshot) supposeValidator() common.Address {
220234
validators := s.validators()
221235
index := (s.Number + 1) % uint64(len(validators))

core/genesis.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
222222
// Special case: don't change the existing config of a non-mainnet chain if no new
223223
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
224224
// if we just continued here.
225-
if genesis == nil && stored != params.MainnetGenesisHash {
225+
// The full node of two BSC testnets may run without genesis file after been inited.
226+
if genesis == nil && stored != params.MainnetGenesisHash &&
227+
stored != params.ChapelGenesisHash && stored != params.RialtoGenesisHash {
226228
return storedcfg, stored, nil
227229
}
228230

@@ -252,6 +254,10 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
252254
return params.RinkebyChainConfig
253255
case ghash == params.GoerliGenesisHash:
254256
return params.GoerliChainConfig
257+
case ghash == params.ChapelGenesisHash:
258+
return params.ChapelChainConfig
259+
case ghash == params.RialtoGenesisHash:
260+
return params.RialtoChainConfig
255261
default:
256262
return params.AllEthashProtocolChanges
257263
}

eth/fetcher/block_fetcher.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -681,11 +681,12 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
681681
go f.broadcastBlock(block, true)
682682

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

686687
default:
687688
// Something went very wrong, drop the peer
688-
log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
689+
log.Error("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
689690
f.dropPeer(peer)
690691
return
691692
}

0 commit comments

Comments
 (0)