From a8c9e53eb0d636ce6d0def3febf040baec3d2ab6 Mon Sep 17 00:00:00 2001 From: zjubfd <296179868@qq.com> Date: Fri, 7 Aug 2020 17:06:29 +0800 Subject: [PATCH] enforce backoff time for out-turn validator (#23) --- cmd/geth/retesteth.go | 6 ++++ consensus/parlia/parlia.go | 53 +++++++++++++++++++--------- consensus/parlia/parlia_test.go | 41 +++++++++++++--------- consensus/parlia/ramanujanfork.go | 40 +++++++++++++++++++++ consensus/parlia/snapshot.go | 18 ++++++++-- core/genesis.go | 8 ++++- eth/fetcher/block_fetcher.go | 5 +-- params/config.go | 58 ++++++++++++++++++++++++++++--- 8 files changed, 188 insertions(+), 41 deletions(-) create mode 100644 consensus/parlia/ramanujanfork.go diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index 5aad5ace1296..a904d752592c 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -133,6 +133,7 @@ type CParamsParams struct { ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"` ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"` IstanbulBlock *math.HexOrDecimal64 `json:"istanbulForkBlock"` + RamanujanForkBlock *math.HexOrDecimal64 `json:"ramanujanForkBlock"` ChainID *math.HexOrDecimal256 `json:"chainID"` MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"` TieBreakingGas bool `json:"tieBreakingGas"` @@ -322,6 +323,7 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa constantinopleBlock *big.Int petersburgBlock *big.Int istanbulBlock *big.Int + ramanujanBlock *big.Int ) if chainParams.Params.HomesteadForkBlock != nil { homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock)) @@ -351,6 +353,9 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa if chainParams.Params.IstanbulBlock != nil { istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock)) } + if chainParams.Params.RamanujanForkBlock != nil { + ramanujanBlock = big.NewInt(int64(*chainParams.Params.RamanujanForkBlock)) + } genesis := &core.Genesis{ Config: ¶ms.ChainConfig{ @@ -365,6 +370,7 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa ConstantinopleBlock: constantinopleBlock, PetersburgBlock: petersburgBlock, IstanbulBlock: istanbulBlock, + RamanujanBlock: ramanujanBlock, }, Nonce: uint64(chainParams.Genesis.Nonce), Timestamp: uint64(chainParams.Genesis.Timestamp), diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 0b30a1bdf6b1..0353c33b3bb4 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, } ) @@ -395,6 +395,16 @@ 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 + } + + err = p.blockTimeVerifyForRamanujanFork(snap, header, parent) + if err != nil { + return nil + } + // Verify that the gas limit is <= 2^63-1 capacity := uint64(0x7fffffffffffffff) if header.GasLimit > capacity { @@ -570,7 +580,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 +636,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 = p.blockTimeForRamanujanFork(snap, header, parent) if header.Time < uint64(time.Now().Unix()) { header.Time = uint64(time.Now().Unix()) } @@ -809,14 +818,7 @@ 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.Until(time.Unix(int64(header.Time), 0)) // 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)) - } + delay := p.delayForRamanujanFork(snap, header) log.Info("Sealing block with", "number", number, "delay", delay, "headerDifficulty", header.Difficulty, "val", val.Hex()) @@ -861,7 +863,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 +1142,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 +1216,3 @@ func applyMessage( } return msg.Gas() - returnGas, err } - diff --git a/consensus/parlia/parlia_test.go b/consensus/parlia/parlia_test.go index 2c0b754a4444..fc05013e92e2 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/ramanujanfork.go b/consensus/parlia/ramanujanfork.go new file mode 100644 index 000000000000..db062d3807ee --- /dev/null +++ b/consensus/parlia/ramanujanfork.go @@ -0,0 +1,40 @@ +package parlia + +import ( + "math/rand" + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + wiggleTimeBeforeFork = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers + fixedBackOffTimeBeforeFork = 200 * time.Millisecond +) + +func (p *Parlia) delayForRamanujanFork(snap *Snapshot, header *types.Header) time.Duration { + delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple + if p.chainConfig.IsRamanujan(header.Number) { + return delay + } + wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTimeBeforeFork + return delay + time.Duration(fixedBackOffTimeBeforeFork) + time.Duration(rand.Int63n(int64(wiggle))) +} + +func (p *Parlia) blockTimeForRamanujanFork(snap *Snapshot, header, parent *types.Header) uint64 { + blockTime := parent.Time + p.config.Period + if p.chainConfig.IsRamanujan(header.Number) { + blockTime = blockTime + backOffTime(snap, p.val) + } + return blockTime +} + +func (p *Parlia) blockTimeVerifyForRamanujanFork(snap *Snapshot, header, parent *types.Header) error { + if p.chainConfig.IsRamanujan(header.Number) { + if header.Time < parent.Time+p.config.Period+backOffTime(snap, header.Coinbase) { + return consensus.ErrFutureBlock + } + } + return nil +} diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index b1af95f5fc0a..0d361ad53f97 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)) diff --git a/core/genesis.go b/core/genesis.go index b31980f0a3c2..f116a650c3f7 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -222,7 +222,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override // Special case: don't change the existing config of a non-mainnet chain if no new // config is supplied. These chains would get AllProtocolChanges (and a compat error) // if we just continued here. - if genesis == nil && stored != params.MainnetGenesisHash { + // The full node of two BSC testnets may run without genesis file after been inited. + if genesis == nil && stored != params.MainnetGenesisHash && + stored != params.ChapelGenesisHash && stored != params.RialtoGenesisHash { return storedcfg, stored, nil } @@ -252,6 +254,10 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig + case ghash == params.ChapelGenesisHash: + return params.ChapelChainConfig + case ghash == params.RialtoGenesisHash: + return params.RialtoChainConfig default: return params.AllEthashProtocolChanges } diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index d6951d2f172b..d96957d418cd 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -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 } diff --git a/params/config.go b/params/config.go index d8e10e38f358..aea6da3ff86f 100644 --- a/params/config.go +++ b/params/config.go @@ -31,6 +31,9 @@ var ( RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") + + ChapelGenesisHash = common.HexToHash("0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34") + RialtoGenesisHash = common.HexToHash("0xaa1c1e0af675e846942719466ab72822eff51ebf8462ead0897ae1240e3c0da1") ) // TrustedCheckpoints associates each known checkpoint with the genesis hash of @@ -210,21 +213,57 @@ var ( Threshold: 2, } + ChapelChainConfig = &ChainConfig{ + ChainID: big.NewInt(97), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + RamanujanBlock: big.NewInt(1066095), + Parlia: &ParliaConfig{ + Period: 3, + Epoch: 200, + }, + } + + RialtoChainConfig = &ChainConfig{ + ChainID: big.NewInt(1417), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + RamanujanBlock: big.NewInt(200987), + Parlia: &ParliaConfig{ + Period: 3, + Epoch: 200, + }, + } + // AllEthashProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Ethash consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -296,6 +335,7 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty" toml:",omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty" toml:",omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) EWASMBlock *big.Int `json:"ewasmBlock,omitempty" toml:",omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) + RamanujanBlock *big.Int `json:"ramanujanBlock,omitempty" toml:",omitempty"` // ramanujanBlock switch block (nil = no fork, 0 = already activated) // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty" toml:",omitempty"` @@ -346,7 +386,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Ramanujan: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -359,6 +399,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, + c.RamanujanBlock, engine, ) } @@ -398,6 +439,11 @@ func (c *ChainConfig) IsConstantinople(num *big.Int) bool { return isForked(c.ConstantinopleBlock, num) } +// IsRamanujan returns whether num is either equal to the IsRamanujan fork block or greater. +func (c *ChainConfig) IsRamanujan(num *big.Int) bool { + return isForked(c.RamanujanBlock, num) +} + // IsMuirGlacier returns whether num is either equal to the Muir Glacier (EIP-2384) fork block or greater. func (c *ChainConfig) IsMuirGlacier(num *big.Int) bool { return isForked(c.MuirGlacierBlock, num) @@ -456,6 +502,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {"petersburgBlock", c.PetersburgBlock}, {"istanbulBlock", c.IstanbulBlock}, {"muirGlacierBlock", c.MuirGlacierBlock}, + {"ramanujanBlock", c.RamanujanBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -515,6 +562,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) } + if isForkIncompatible(c.RamanujanBlock, newcfg.RamanujanBlock, head) { + return newCompatError("ramanujan fork block", c.RamanujanBlock, newcfg.RamanujanBlock) + } return nil }