From c826e03915c7bea0e8f4a39281af487112fed24d Mon Sep 17 00:00:00 2001 From: Matus Kysel Date: Wed, 24 Jul 2024 08:52:47 +0200 Subject: [PATCH] BEP-341: Validators can produce consecutive blocks --- consensus/parlia/abi.go | 13 ++ consensus/parlia/api.go | 19 +++ consensus/parlia/bohrFork.go | 74 +++++++++++ consensus/parlia/parlia.go | 86 +++++++++++-- consensus/parlia/snapshot.go | 121 ++++++++++++++---- .../{snaoshot_test.go => snapshot_test.go} | 0 consensus/parlia/util.go | 28 ++-- params/protocol_params.go | 5 + 8 files changed, 298 insertions(+), 48 deletions(-) create mode 100644 consensus/parlia/bohrFork.go rename consensus/parlia/{snaoshot_test.go => snapshot_test.go} (100%) diff --git a/consensus/parlia/abi.go b/consensus/parlia/abi.go index 0d9ab54cd59..804dcdf7e00 100644 --- a/consensus/parlia/abi.go +++ b/consensus/parlia/abi.go @@ -2306,6 +2306,19 @@ const validatorSetABI = ` ], "stateMutability": "view" }, + { + "inputs": [], + "name": "getTurnLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "type": "function", "name": "getValidators", diff --git a/consensus/parlia/api.go b/consensus/parlia/api.go index 066414abd0e..8103556c109 100644 --- a/consensus/parlia/api.go +++ b/consensus/parlia/api.go @@ -87,3 +87,22 @@ func (api *API) GetValidatorsAtHash(hash libcommon.Hash) ([]libcommon.Address, e } return snap.validators(), nil } + +func (api *API) GetTurnLength(number *rpc.BlockNumber) (uint8, error) { + // Retrieve the requested block number (or current if none requested) + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + // Ensure we have an actually valid block and return the validators from its snapshot + if header == nil { + return 0, errUnknownBlock + } + snap, err := api.parlia.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil, false /* verify */) + if err != nil || snap.TurnLength == 0 { + return 0, err + } + return snap.TurnLength, nil +} diff --git a/consensus/parlia/bohrFork.go b/consensus/parlia/bohrFork.go new file mode 100644 index 00000000000..db70349dc1e --- /dev/null +++ b/consensus/parlia/bohrFork.go @@ -0,0 +1,74 @@ +package parlia + +import ( + "errors" + "math/big" + mrand "math/rand" + + "github.com/ledgerwatch/erigon/common/u256" + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/systemcontracts" + "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/log/v3" +) + +func (p *Parlia) getTurnLength(chain consensus.ChainHeaderReader, header *types.Header, ibs *state.IntraBlockState) (*uint8, error) { + parent := chain.GetHeaderByHash(header.ParentHash) + if parent == nil { + return nil, errors.New("parent not found") + } + + var turnLength uint8 + if p.chainConfig.IsBohr(parent.Number.Uint64(), parent.Time) { + turnLengthFromContract, err := p.getTurnLengthFromContract(parent, ibs) + if err != nil { + return nil, err + } + if turnLengthFromContract == nil { + return nil, errors.New("unexpected error when getTurnLengthFromContract") + } + turnLength = uint8(turnLengthFromContract.Int64()) + } else { + turnLength = defaultTurnLength + } + log.Debug("getTurnLength", "turnLength", turnLength) + + return &turnLength, nil +} + +func (p *Parlia) getTurnLengthFromContract(header *types.Header, ibs *state.IntraBlockState) (turnLength *big.Int, err error) { + // mock to get turnLength from the contract + if params.FixedTurnLength >= 1 && params.FixedTurnLength <= 9 { + if params.FixedTurnLength == 2 { + return p.getRandTurnLength(header) + } + return big.NewInt(int64(params.FixedTurnLength)), nil + } + + method := "getTurnLength" + data, err := p.validatorSetABI.Pack(method) + if err != nil { + log.Error("Unable to pack tx for getTurnLength", "error", err) + return nil, err + } + + // do smart contract call + msgData := Bytes(data) + _, returnData, err := p.systemCall(header.Coinbase, systemcontracts.ValidatorContract, msgData[:], ibs, header, u256.Num0) + if err != nil { + return nil, err + } + + err = p.validatorSetABI.UnpackIntoInterface(&turnLength, method, returnData) + return turnLength, err +} + +// getRandTurnLength returns a random valid value, used to test switching turn length +func (p *Parlia) getRandTurnLength(header *types.Header) (turnLength *big.Int, err error) { + turnLengths := [8]uint8{1, 3, 4, 5, 6, 7, 8, 9} + r := mrand.New(mrand.NewSource(int64(header.Time))) + lengthIndex := int(r.Int31n(int32(len(turnLengths)))) + return big.NewInt(int64(turnLengths[lengthIndex])), nil +} diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 2ba388b0513..e09c5b0f8f3 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -56,10 +56,12 @@ const ( CheckpointInterval = 1024 // Number of blocks after which to save the snapshot to the database defaultEpochLength = uint64(100) // Default number of blocks of checkpoint to update validatorSet from contract + defaultTurnLength = uint8(1) // Default consecutive number of blocks a validator receives priority for block production extraVanity = 32 // Fixed number of extra-data prefix bytes reserved for signer vanity extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal nextForkHashSize = 4 // Fixed number of extra-data suffix bytes reserved for nextForkHash. + turnLengthSize = 1 // Fixed number of extra-data suffix bytes reserved for turnLength validatorBytesLengthBeforeLuban = length.Addr validatorBytesLength = length.Addr + types.BLSPublicKeyLength @@ -124,6 +126,10 @@ var ( // invalid list of validators (i.e. non divisible by 20 bytes). errInvalidSpanValidators = errors.New("invalid validator list on sprint end block") + // errInvalidTurnLength is returned if a block contains an + // invalid length of turn (i.e. no data left after parsing validators). + errInvalidTurnLength = errors.New("invalid turnLength") + // errInvalidMixDigest is returned if a block's mix digest is non-zero. errInvalidMixDigest = errors.New("non-zero mix digest") @@ -134,6 +140,10 @@ var ( // list of validators different than the one the local node calculated. errMismatchingEpochValidators = errors.New("mismatching validator list on epoch block") + // errMismatchingEpochTurnLength is returned if a sprint block contains a + // turn length different than the one the local node calculated. + errMismatchingEpochTurnLength = errors.New("mismatching turn length on epoch block") + // errInvalidDifficulty is returned if the difficulty of a block is missing. errInvalidDifficulty = errors.New("invalid difficulty") @@ -335,6 +345,7 @@ func (p *Parlia) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*typ // On Luban fork, we introduce vote attestation into the header's extra field, so extra format is different from before. // Before Luban fork: |---Extra Vanity---|---Validators Bytes (or Empty)---|---Extra Seal---| // After Luban fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---| +// After bohr fork: |---Extra Vanity---|---Validators Number and Validators Bytes (or Empty)---|---Turn Length (or Empty)---|---Vote Attestation (or Empty)---|---Extra Seal---| func getValidatorBytesFromHeader(header *types.Header, chainConfig *chain.Config, parliaConfig *chain.ParliaConfig) []byte { if len(header.Extra) <= extraVanity+extraSeal { return nil @@ -351,11 +362,15 @@ func getValidatorBytesFromHeader(header *types.Header, chainConfig *chain.Config return nil } num := int(header.Extra[extraVanity]) - if num == 0 || len(header.Extra) <= extraVanity+extraSeal+num*validatorBytesLength { - return nil - } start := extraVanity + validatorNumberSize end := start + num*validatorBytesLength + extraMinLen := end + extraSeal + if chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + extraMinLen += turnLengthSize + } + if num == 0 || len(header.Extra) < extraMinLen { + return nil + } return header.Extra[start:end] } @@ -374,11 +389,14 @@ func getVoteAttestationFromHeader(header *types.Header, chainConfig *chain.Confi attestationBytes = header.Extra[extraVanity : len(header.Extra)-extraSeal] } else { num := int(header.Extra[extraVanity]) - if len(header.Extra) <= extraVanity+extraSeal+validatorNumberSize+num*validatorBytesLength { - return nil, nil - } start := extraVanity + validatorNumberSize + num*validatorBytesLength + if chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + start += turnLengthSize + } end := len(header.Extra) - extraSeal + if end <= start { + return nil, nil + } attestationBytes = header.Extra[start:end] } @@ -672,7 +690,7 @@ func (p *Parlia) verifySeal(chain consensus.ChainHeaderReader, header *types.Hea return fmt.Errorf("parlia.verifySeal: headerNum=%d, validator=%x, %w", header.Number.Uint64(), signer.Bytes(), errUnauthorizedValidator) } - if snap.signedRecently(signer) { + if snap.SignRecently(signer) { return errRecentlySigned } @@ -723,6 +741,24 @@ func (p *Parlia) prepareValidators(header *types.Header, chain consensus.ChainHe return nil } +func (p *Parlia) prepareTurnLength(chain consensus.ChainHeaderReader, header *types.Header, ibs *state.IntraBlockState) error { + if header.Number.Uint64()%p.config.Epoch != 0 || + !p.chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + return nil + } + + turnLength, err := p.getTurnLength(chain, header, ibs) + if err != nil { + return err + } + + if turnLength != nil { + header.Extra = append(header.Extra, *turnLength) + } + + return nil +} + // snapshot retrieves the authorization snapshot at a given point in time. // !!! be careful // the block with `number` and `hash` is just the last element of `parents`, @@ -874,6 +910,10 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header return err } + if err := p.prepareTurnLength(chain, header, ibs); err != nil { + return err + } + // add extra seal space header.Extra = append(header.Extra, make([]byte, extraSeal)...) @@ -927,6 +967,30 @@ func (p *Parlia) verifyValidators(header, parentHeader *types.Header, state *sta return nil } +func (p *Parlia) verifyTurnLength(chain consensus.ChainHeaderReader, header *types.Header, ibs *state.IntraBlockState) error { + if header.Number.Uint64()%p.config.Epoch != 0 || + !p.chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + return nil + } + + turnLengthFromHeader, err := parseTurnLength(header, p.chainConfig, p.config) + if err != nil { + return err + } + if turnLengthFromHeader != nil { + turnLength, err := p.getTurnLength(chain, header, ibs) + if err != nil { + return err + } + if turnLength != nil && *turnLength == *turnLengthFromHeader { + log.Debug("verifyTurnLength", "turnLength", *turnLength) + return nil + } + } + + return errMismatchingEpochTurnLength +} + // Initialize runs any pre-transaction state modifications (e.g. epoch start) func (p *Parlia) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger) { @@ -991,6 +1055,10 @@ func (p *Parlia) finalize(header *types.Header, state *state.IntraBlockState, tx return nil, nil, err } + if err := p.verifyTurnLength(chain, header, state); err != nil { + return nil, nil, err + } + if p.chainConfig.IsFeynman(header.Number.Uint64(), header.Time) { systemcontracts.UpgradeBuildInSystemContract(p.chainConfig, header.Number, parentHeader.Time, header.Time, state, logger) } @@ -1009,10 +1077,10 @@ func (p *Parlia) finalize(header *types.Header, state *state.IntraBlockState, tx } } if header.Difficulty.Cmp(diffInTurn) != 0 { - spoiledVal := snap.supposeValidator() + spoiledVal := snap.inturnValidator() signedRecently := false if p.chainConfig.IsPlato(header.Number.Uint64()) { - if snap.signedRecently(spoiledVal) { + if snap.SignRecently(spoiledVal) { signedRecently = true } } else { diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index 1cd1415f5f0..6c8a7c2ddbc 100644 --- a/consensus/parlia/snapshot.go +++ b/consensus/parlia/snapshot.go @@ -46,6 +46,7 @@ type Snapshot struct { Number uint64 `json:"number"` // Block number where the snapshot was created Hash libcommon.Hash `json:"hash"` // Block hash where the snapshot was created + TurnLength uint8 `json:"turn_length"` // Length of `turn`, meaning the consecutive number of blocks a validator receives priority for block production Validators map[libcommon.Address]*ValidatorInfo `json:"validators"` // Set of authorized validators at this moment Recents map[uint64]libcommon.Address `json:"recents"` // Set of recent validators for spam protections RecentForkHashes map[uint64]string `json:"recent_fork_hashes"` // Set of recent forkHash @@ -73,6 +74,7 @@ func newSnapshot( sigCache: sigCache, Number: number, Hash: hash, + TurnLength: defaultTurnLength, Recents: make(map[uint64]common.Address), RecentForkHashes: make(map[uint64]string), Validators: make(map[libcommon.Address]*ValidatorInfo), @@ -132,6 +134,11 @@ func loadSnapshot(config *chain.ParliaConfig, sigCache *lru.ARCCache[common.Hash if err := json.Unmarshal(blob, snap); err != nil { return nil, err } + + if snap.TurnLength == 0 { // no TurnLength field in old snapshots + snap.TurnLength = defaultTurnLength + } + snap.config = config snap.sigCache = sigCache return snap, nil @@ -155,6 +162,7 @@ func (s *Snapshot) copy() *Snapshot { sigCache: s.sigCache, Number: s.Number, Hash: s.Hash, + TurnLength: s.TurnLength, Validators: make(map[libcommon.Address]*ValidatorInfo), Recents: make(map[uint64]libcommon.Address), RecentForkHashes: make(map[uint64]string), @@ -256,10 +264,10 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea log.Info("[parlia] snapshots build, recover from headers", "block", number) } // Delete the oldest validator from the recent list to allow it signing again - if limit := uint64(len(snap.Validators)/2 + 1); number >= limit { + if limit := snap.minerHistoryCheckLen() + 1; number >= limit { delete(snap.Recents, number-limit) } - if limit := uint64(len(snap.Validators)); number >= limit { + if limit := snap.versionHistoryCheckLen(); number >= limit { delete(snap.RecentForkHashes, number-limit) } // Resolve the authorization key and check against signers @@ -274,16 +282,22 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea return nil, errUnauthorizedValidator } */ - for _, recent := range snap.Recents { - if recent == validator { + if chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + if snap.SignRecently(validator) { return nil, errRecentlySigned } + } else { + for _, recent := range snap.Recents { + if recent == validator { + return nil, errRecentlySigned + } + } } snap.Recents[number] = validator snap.RecentForkHashes[number] = hex.EncodeToString(header.Extra[extraVanity-nextForkHashSize : extraVanity]) snap.updateAttestation(header, chainConfig, s.config) // change validator set - if number > 0 && number%s.config.Epoch == uint64(len(snap.Validators)/2) { + if number > 0 && number%s.config.Epoch == snap.minerHistoryCheckLen() { epochKey := math.MaxUint64 - number/s.config.Epoch // impossible used as a block number if chainConfig.IsBohr(number, header.Time) { // after switching the validator set, snap.Validators may become larger, @@ -292,11 +306,22 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea continue } } - checkpointHeader := FindAncientHeader(header, uint64(len(snap.Validators)/2), chain, parents) + checkpointHeader := FindAncientHeader(header, snap.minerHistoryCheckLen(), chain, parents) if checkpointHeader == nil { return nil, consensus.ErrUnknownAncestor } + oldVersionsLen := snap.versionHistoryCheckLen() + // get turnLength from headers and use that for new turnLength + turnLength, err := parseTurnLength(checkpointHeader, chainConfig, s.config) + if err != nil { + return nil, err + } + if turnLength != nil { + snap.TurnLength = *turnLength + log.Debug("validator set switch", "turnLength", *turnLength) + } + // get validators from headers and use that for new validator set newValArr, voteAddrs, err := parseValidators(checkpointHeader, chainConfig, s.config) if err != nil { @@ -327,13 +352,7 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea } } } - oldLimit := len(snap.Validators) - newLimit := len(newVals) - if newLimit < oldLimit { - for i := 0; i < oldLimit-newLimit; i++ { - delete(snap.RecentForkHashes, number-uint64(newLimit)-uint64(i)) - } - } + snap.Validators = newVals if chainConfig.IsLuban(header.Number.Uint64()) { validators := snap.validators() @@ -341,6 +360,10 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea snap.Validators[val].Index = idx + 1 // offset by 1 } } + + for i := snap.versionHistoryCheckLen(); i < oldVersionsLen; i++ { + delete(snap.RecentForkHashes, number-i) + } } } snap.Number += uint64(len(headers)) @@ -358,11 +381,21 @@ func (s *Snapshot) validators() []common.Address { return validators } +// lastBlockInOneTurn returns if the block at height `blockNumber` is the last block in current turn. +func (s *Snapshot) lastBlockInOneTurn(blockNumber uint64) bool { + return (blockNumber+1)%uint64(s.TurnLength) == 0 +} + // inturn returns if a validator at a given block height is in-turn or not. func (s *Snapshot) inturn(validator common.Address) bool { + return s.inturnValidator() == validator +} + +// inturnValidator returns the validator for the following block height. +func (s *Snapshot) inturnValidator() common.Address { validators := s.validators() - offset := (s.Number + 1) % uint64(len(validators)) - return validators[offset] == validator + offset := (s.Number + 1) / uint64(s.TurnLength) % uint64(len(validators)) + return validators[offset] } func (s *Snapshot) enoughDistance(validator common.Address, header *types.Header) bool { @@ -399,23 +432,45 @@ func (s *Snapshot) indexOfVal(validator libcommon.Address) int { return -1 } -func (s *Snapshot) supposeValidator() common.Address { - validators := s.validators() - index := (s.Number + 1) % uint64(len(validators)) - return validators[index] +func (s *Snapshot) versionHistoryCheckLen() uint64 { + return uint64(len(s.Validators)) * uint64(s.TurnLength) +} + +func (s *Snapshot) minerHistoryCheckLen() uint64 { + return (uint64(len(s.Validators))/2+1)*uint64(s.TurnLength) - 1 } -func (s *Snapshot) signedRecently(validator common.Address) bool { +func (s *Snapshot) countRecents() map[common.Address]uint8 { + leftHistoryBound := uint64(0) // the bound is excluded + checkHistoryLength := s.minerHistoryCheckLen() + if s.Number > checkHistoryLength { + leftHistoryBound = s.Number - checkHistoryLength + } + counts := make(map[common.Address]uint8, len(s.Validators)) for seen, recent := range s.Recents { - if recent == validator { - if limit := uint64(len(s.Validators)/2 + 1); s.Number+1 < limit || seen > s.Number+1-limit { - return true - } + if seen <= leftHistoryBound || recent == (common.Address{}) /*when seen == `epochKey`*/ { + continue } + counts[recent] += 1 + } + return counts +} + +func (s *Snapshot) signRecentlyByCounts(validator common.Address, counts map[common.Address]uint8) bool { + if seenTimes, ok := counts[validator]; ok && seenTimes >= s.TurnLength { + if seenTimes > s.TurnLength { + log.Warn("produce more blocks than expected!", "validator", validator, "seenTimes", seenTimes) + } + return true } + return false } +func (s *Snapshot) SignRecently(validator common.Address) bool { + return s.signRecentlyByCounts(validator, s.countRecents()) +} + func parseValidators(header *types.Header, chainConfig *chain.Config, parliaConfig *chain.ParliaConfig) ([]libcommon.Address, []types.BLSPublicKey, error) { validatorsBytes := getValidatorBytesFromHeader(header, chainConfig, parliaConfig) if len(validatorsBytes) == 0 { @@ -441,6 +496,24 @@ func parseValidators(header *types.Header, chainConfig *chain.Config, parliaConf return cnsAddrs, voteAddrs, nil } +func parseTurnLength(header *types.Header, chainConfig *chain.Config, parliaConfig *chain.ParliaConfig) (*uint8, error) { + if header.Number.Uint64()%parliaConfig.Epoch != 0 || + !chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + return nil, nil + } + + if len(header.Extra) <= extraVanity+extraSeal { + return nil, errInvalidSpanValidators + } + num := int(header.Extra[extraVanity]) + pos := extraVanity + validatorNumberSize + num*validatorBytesLength + if len(header.Extra) <= pos { + return nil, errInvalidTurnLength + } + turnLength := header.Extra[pos] + return &turnLength, nil +} + func FindAncientHeader(header *types.Header, ite uint64, chain consensus.ChainHeaderReader, candidateParents []*types.Header) *types.Header { ancient := header for i := uint64(1); i <= ite; i++ { diff --git a/consensus/parlia/snaoshot_test.go b/consensus/parlia/snapshot_test.go similarity index 100% rename from consensus/parlia/snaoshot_test.go rename to consensus/parlia/snapshot_test.go diff --git a/consensus/parlia/util.go b/consensus/parlia/util.go index 26cd522c3ba..0d859443db2 100644 --- a/consensus/parlia/util.go +++ b/consensus/parlia/util.go @@ -11,31 +11,25 @@ import ( func backOffTime(snap *Snapshot, header *types.Header, val libcommon.Address, chainConfig *chain.Config) uint64 { if snap.inturn(val) { + log.Debug("backOffTime", "blockNumber", header.Number, "in turn validator", val) return 0 } else { delay := initialBackOffTime validators := snap.validators() if chainConfig.IsPlanck(header.Number.Uint64()) { // reverse the key/value of snap.Recents to get recentsMap - recentsMap := make(map[libcommon.Address]uint64, len(snap.Recents)) - bound := uint64(0) - if n, limit := header.Number.Uint64(), uint64(len(validators)/2+1); n > limit { - bound = n - limit - } - for seen, recent := range snap.Recents { - if seen <= bound { - continue - } - recentsMap[recent] = seen + counts := snap.countRecents() + for addr, seenTimes := range counts { + log.Debug("backOffTime", "blockNumber", header.Number, "validator", addr, "seenTimes", seenTimes) } // The backOffTime does not matter when a validator has signed recently. - if _, ok := recentsMap[val]; ok { + if snap.signRecentlyByCounts(val, counts) { return 0 } - inTurnAddr := validators[(snap.Number+1)%uint64(len(validators))] - if _, ok := recentsMap[inTurnAddr]; ok { + inTurnAddr := snap.inturnValidator() + if snap.signRecentlyByCounts(inTurnAddr, counts) { log.Debug("in turn validator has recently signed, skip initialBackOffTime", "inTurnAddr", inTurnAddr) delay = 0 @@ -44,7 +38,7 @@ func backOffTime(snap *Snapshot, header *types.Header, val libcommon.Address, ch // Exclude the recently signed validators temp := make([]libcommon.Address, 0, len(validators)) for _, addr := range validators { - if _, ok := recentsMap[addr]; ok { + if snap.signRecentlyByCounts(addr, counts) { continue } temp = append(temp, addr) @@ -64,7 +58,11 @@ func backOffTime(snap *Snapshot, header *types.Header, val libcommon.Address, ch return 0 } - s := rand.NewSource(int64(snap.Number)) + randSeed := snap.Number + if chainConfig.IsBohr(header.Number.Uint64(), header.Time) { + randSeed = header.Number.Uint64() / uint64(snap.TurnLength) + } + s := rand.NewSource(int64(randSeed)) r := rand.New(s) n := len(validators) backOffSteps := make([]uint64, 0, n) diff --git a/params/protocol_params.go b/params/protocol_params.go index 500e69bcc7a..18db6ff5641 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -194,6 +194,11 @@ const ( MinBlocksForBlobRequests uint64 = 524288 // it keeps blob data available for ~18.2 days in local, ref: https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP-336.md#51-parameters. DefaultExtraReserveForBlobRequests uint64 = 1 * (24 * 3600) / 3 // it adds more time for expired blobs for some request cases, like expiry blob when remote peer is syncing, default 1 day. + // used for testing: + // [1,9] except 2 --> used as turn length directly + // 2 --> use random values to test switching turn length + // 0 and other values --> get turn length from contract + FixedTurnLength uint64 = 0 ) // EIP-4788: Beacon block root in the EVM