enforce backoff time for out-turn validator (#23)

This commit is contained in:
zjubfd 2020-08-07 17:06:29 +08:00 committed by GitHub
parent 83d72b8e91
commit a8c9e53eb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 188 additions and 41 deletions

@ -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: &params.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),

@ -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
@ -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
}

@ -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 {

@ -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
}

@ -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))

@ -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
}

@ -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
}

@ -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
}