From 5b12f194db46e02aebe75229f67f77ad548fdcfa Mon Sep 17 00:00:00 2001 From: buddh0 Date: Fri, 23 Aug 2024 11:11:09 +0800 Subject: [PATCH] core/vote: skip voting as VoteInterval consensus/parlia: adapt updateAttestation consensus/parlia: adapt assembleVoteAttestation consensus/parlia: modify delay core/vote: fix vote stability check consensus/parlia: define API GetVoteInterval cmd/jsutils: adapt voteInterval params: define IsPauli consensus/parlia: adapt verifyVoteAttestation consensus/parlia: define snapshot.VoteInterval --- cmd/geth/chaincmd.go | 5 ++ cmd/geth/config.go | 7 ++ cmd/geth/main.go | 2 + cmd/jsutils/get_perf.js | 45 ++++++---- cmd/utils/flags.go | 11 +++ consensus/parlia/api.go | 19 ++++- consensus/parlia/bohrFork.go | 34 ++++++++ consensus/parlia/parlia.go | 154 +++++++++++++++++++++++++++-------- consensus/parlia/snapshot.go | 58 ++++++++----- core/genesis.go | 4 + core/vote/vote_manager.go | 19 +++-- eth/backend.go | 4 + eth/ethconfig/config.go | 3 + eth/ethconfig/gen_config.go | 10 ++- params/config.go | 30 ++++++- params/protocol_params.go | 3 + 16 files changed, 327 insertions(+), 81 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 870527434d..ac7f252da6 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -64,6 +64,7 @@ var ( utils.CachePreimagesFlag, utils.OverridePassedForkTime, utils.OverrideBohr, + utils.OverridePauli, utils.OverrideVerkle, utils.MultiDataBaseFlag, }, utils.DatabaseFlags), @@ -262,6 +263,10 @@ func initGenesis(ctx *cli.Context) error { v := ctx.Uint64(utils.OverrideBohr.Name) overrides.OverrideBohr = &v } + if ctx.IsSet(utils.OverridePauli.Name) { + v := ctx.Uint64(utils.OverridePauli.Name) + overrides.OverridePauli = &v + } if ctx.IsSet(utils.OverrideVerkle.Name) { v := ctx.Uint64(utils.OverrideVerkle.Name) overrides.OverrideVerkle = &v diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 5c829a2f76..d59c40e8ac 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -193,6 +193,10 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Uint64(utils.OverrideBohr.Name) cfg.Eth.OverrideBohr = &v } + if ctx.IsSet(utils.OverridePauli.Name) { + v := ctx.Uint64(utils.OverridePauli.Name) + cfg.Eth.OverridePauli = &v + } if ctx.IsSet(utils.OverrideVerkle.Name) { v := ctx.Uint64(utils.OverrideVerkle.Name) cfg.Eth.OverrideVerkle = &v @@ -213,6 +217,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { if ctx.IsSet(utils.OverrideFixedTurnLength.Name) { params.FixedTurnLength = ctx.Uint64(utils.OverrideFixedTurnLength.Name) } + if ctx.IsSet(utils.OverrideFixedVoteInterval.Name) { + params.FixedVoteInterval = ctx.Uint64(utils.OverrideFixedVoteInterval.Name) + } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d4ac750d5b..3ddb8e3570 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -74,12 +74,14 @@ var ( utils.RialtoHash, utils.OverridePassedForkTime, utils.OverrideBohr, + utils.OverridePauli, utils.OverrideVerkle, utils.OverrideFullImmutabilityThreshold, utils.OverrideMinBlocksForBlobRequests, utils.OverrideDefaultExtraReserveForBlobRequests, utils.OverrideBreatheBlockInterval, utils.OverrideFixedTurnLength, + utils.OverrideFixedVoteInterval, utils.EnablePersonal, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, diff --git a/cmd/jsutils/get_perf.js b/cmd/jsutils/get_perf.js index cec035f09f..002b12689b 100644 --- a/cmd/jsutils/get_perf.js +++ b/cmd/jsutils/get_perf.js @@ -1,4 +1,6 @@ -import { ethers } from "ethers"; +import { + ethers +} from "ethers"; import program from "commander"; program.option("--rpc ", "Rpc"); @@ -14,14 +16,20 @@ const main = async () => { let inturnBlocks = 0; let justifiedBlocks = 0; let turnLength = await provider.send("parlia_getTurnLength", [ - ethers.toQuantity(program.startNum)]); + ethers.toQuantity(program.startNum) + ]); + let voteInterval = await provider.send("parlia_getVoteInterval", [ + ethers.toQuantity(program.startNum) + ]); for (let i = program.startNum; i < program.endNum; i++) { let txCount = await provider.send("eth_getBlockTransactionCountByNumber", [ - ethers.toQuantity(i)]); + ethers.toQuantity(i) + ]); txCountTotal += ethers.toNumber(txCount) let header = await provider.send("eth_getHeaderByNumber", [ - ethers.toQuantity(i)]); + ethers.toQuantity(i) + ]); let gasUsed = eval(eval(header.gasUsed).toString(10)) gasUsedTotal += gasUsed let difficulty = eval(eval(header.difficulty).toString(10)) @@ -31,32 +39,35 @@ const main = async () => { let timestamp = eval(eval(header.timestamp).toString(10)) let justifiedNumber = await provider.send("parlia_getJustifiedNumber", [ - ethers.toQuantity(i)]); - if (justifiedNumber + 1 == i) { + ethers.toQuantity(i) + ]); + if (justifiedNumber + 2 * voteInterval > i) { justifiedBlocks += 1 } else { - console.log("justified unexpected", "BlockNumber =", i,"justifiedNumber",justifiedNumber) + console.log("justified unexpected", "BlockNumber =", i, "justifiedNumber", justifiedNumber) } - console.log("BlockNumber =", i, "mod =", i%turnLength, "miner =", header.miner , "difficulty =", difficulty, "txCount =", ethers.toNumber(txCount), "gasUsed", gasUsed, "timestamp", timestamp) + console.log("BlockNumber =", i, "mod =", i % turnLength, "miner =", header.miner, "difficulty =", difficulty, "txCount =", ethers.toNumber(txCount), "gasUsed", gasUsed, "timestamp", timestamp) } let blockCount = program.endNum - program.startNum - let txCountPerBlock = txCountTotal/blockCount + let txCountPerBlock = txCountTotal / blockCount let startHeader = await provider.send("eth_getHeaderByNumber", [ - ethers.toQuantity(program.startNum)]); + ethers.toQuantity(program.startNum) + ]); let startTime = eval(eval(startHeader.timestamp).toString(10)) let endHeader = await provider.send("eth_getHeaderByNumber", [ - ethers.toQuantity(program.endNum)]); + ethers.toQuantity(program.endNum) + ]); let endTime = eval(eval(endHeader.timestamp).toString(10)) let timeCost = endTime - startTime - let avgBlockTime = timeCost/blockCount - let inturnBlocksRatio = inturnBlocks/blockCount - let justifiedBlocksRatio = justifiedBlocks/blockCount - let tps = txCountTotal/timeCost + let avgBlockTime = timeCost / blockCount + let inturnBlocksRatio = inturnBlocks / blockCount + let justifiedBlocksRatio = justifiedBlocks / blockCount + let tps = txCountTotal / timeCost let M = 1000000 - let avgGasUsedPerBlock = gasUsedTotal/blockCount/M - let avgGasUsedPerSecond = gasUsedTotal/timeCost/M + let avgGasUsedPerBlock = gasUsedTotal / blockCount / M + let avgGasUsedPerSecond = gasUsedTotal / timeCost / M console.log("Get the performance between [", program.startNum, ",", program.endNum, ")"); console.log("txCountPerBlock =", txCountPerBlock, "txCountTotal =", txCountTotal, "BlockCount =", blockCount, "avgBlockTime =", avgBlockTime, "inturnBlocksRatio =", inturnBlocksRatio, "justifiedBlocksRatio =", justifiedBlocksRatio); diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 049908857f..c86bc723f5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -315,6 +315,11 @@ var ( Usage: "Manually specify the Bohr fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverridePauli = &cli.Uint64Flag{ + Name: "override.pauli", + Usage: "Manually specify the Pauli fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } OverrideVerkle = &cli.Uint64Flag{ Name: "override.verkle", Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", @@ -350,6 +355,12 @@ var ( Value: params.FixedTurnLength, Category: flags.EthCategory, } + OverrideFixedVoteInterval = &cli.Uint64Flag{ + Name: "override.fixedvoteinterval", + Usage: "It use fixed values for voting interval, only for testing purpose", + Value: params.FixedVoteInterval, + Category: flags.EthCategory, + } SyncModeFlag = &flags.TextMarshalerFlag{ Name: "syncmode", Usage: `Blockchain sync mode ("snap" or "full")`, diff --git a/consensus/parlia/api.go b/consensus/parlia/api.go index 5929e94d1e..1d558136f4 100644 --- a/consensus/parlia/api.go +++ b/consensus/parlia/api.go @@ -77,7 +77,7 @@ func (api *API) GetValidatorsAtHash(hash common.Hash) ([]common.Address, error) func (api *API) GetJustifiedNumber(number *rpc.BlockNumber) (uint64, error) { header := api.getHeader(number) - // Ensure we have an actually valid block and return the validators from its snapshot + // Ensure we have an actually valid block and return the justifiedNumber from its snapshot if header == nil { return 0, errUnknownBlock } @@ -90,7 +90,7 @@ func (api *API) GetJustifiedNumber(number *rpc.BlockNumber) (uint64, error) { func (api *API) GetTurnLength(number *rpc.BlockNumber) (uint8, error) { header := api.getHeader(number) - // Ensure we have an actually valid block and return the validators from its snapshot + // Ensure we have an actually valid block and return the turnLength from its snapshot if header == nil { return 0, errUnknownBlock } @@ -101,9 +101,22 @@ func (api *API) GetTurnLength(number *rpc.BlockNumber) (uint8, error) { return snap.TurnLength, nil } +func (api *API) GetVoteInterval(number *rpc.BlockNumber) (uint8, error) { + header := api.getHeader(number) + // Ensure we have an actually valid block and return the voteInterval from its snapshot + if header == nil { + return 0, errUnknownBlock + } + snap, err := api.parlia.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil) + if err != nil || snap.VoteInterval == 0 { + return 0, err + } + return snap.VoteInterval, nil +} + func (api *API) GetFinalizedNumber(number *rpc.BlockNumber) (uint64, error) { header := api.getHeader(number) - // Ensure we have an actually valid block and return the validators from its snapshot + // Ensure we have an actually valid block and return the finalizedNumber from its snapshot if header == nil { return 0, errUnknownBlock } diff --git a/consensus/parlia/bohrFork.go b/consensus/parlia/bohrFork.go index 7cfe3c2f18..8935d90b17 100644 --- a/consensus/parlia/bohrFork.go +++ b/consensus/parlia/bohrFork.go @@ -89,3 +89,37 @@ func (p *Parlia) getRandTurnLength(header *types.Header) (turnLength *big.Int, e lengthIndex := int(r.Int31n(int32(len(turnLengths)))) return big.NewInt(int64(turnLengths[lengthIndex])), nil } + +func (p *Parlia) getVoteInterval(chain consensus.ChainHeaderReader, header *types.Header) (*uint8, error) { + parent := chain.GetHeaderByHash(header.ParentHash) + if parent == nil { + return nil, errors.New("parent not found") + } + + var voteInterval uint8 + if p.chainConfig.IsPauli(parent.Number, parent.Time) { + voteIntervalFromContract, err := p.getVoteIntervalFromContract(parent) + if err != nil { + return nil, err + } + if voteIntervalFromContract == nil { + return nil, errors.New("unexpected error when getVoteIntervalFromContract") + } + voteInterval = uint8(voteIntervalFromContract.Int64()) + } else { + voteInterval = defaultVoteInterval + } + log.Debug("getVoteInterval", "voteInterval", voteInterval) + + return &voteInterval, nil +} + +func (p *Parlia) getVoteIntervalFromContract(header *types.Header) (voteInterval *big.Int, err error) { + // mock to get voteInterval from the contract + if params.FixedVoteInterval >= 1 && params.FixedVoteInterval <= 3 { + return big.NewInt(int64(params.FixedVoteInterval)), nil + } + + // TODO: read from contract + return big.NewInt(int64(defaultVoteInterval)), nil +} diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 4a79ec344b..322c590c95 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -53,12 +53,14 @@ const ( inMemorySignatures = 4096 // Number of recent block signatures to keep in memory inMemoryHeaders = 86400 // Number of recent headers to keep in memory for double sign detection, - checkpointInterval = 1024 // Number of blocks after which to save the snapshot to the database - defaultEpochLength = uint64(200) // 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 + checkpointInterval = 1024 // Number of blocks after which to save the snapshot to the database + defaultEpochLength = uint64(200) // 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 + defaultVoteInterval = uint8(1) // Default number of blocks between two voting rounds for Fast Finality 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 + voteIntervalSize = 1 // Fixed number of extra-data suffix bytes reserved for voteInterval nextForkHashSize = 4 // Fixed number of extra-data suffix bytes reserved for nextForkHash. turnLengthSize = 1 // Fixed number of extra-data suffix bytes reserved for turnLength @@ -82,7 +84,6 @@ var ( // 100 native token maxSystemBalance = new(uint256.Int).Mul(uint256.NewInt(100), uint256.NewInt(params.Ether)) verifyVoteAttestationErrorCounter = metrics.NewRegisteredCounter("parlia/verifyVoteAttestation/error", nil) - updateAttestationErrorCounter = metrics.NewRegisteredCounter("parlia/updateAttestation/error", nil) validVotesfromSelfCounter = metrics.NewRegisteredCounter("parlia/VerifyVote/self", nil) doubleSignCounter = metrics.NewRegisteredCounter("parlia/doublesign", nil) @@ -147,6 +148,10 @@ var ( // turn length different than the one the local node calculated. errMismatchingEpochTurnLength = errors.New("mismatching turn length on epoch block") + // errMismatchingEpochVoteInterval is returned if a sprint block contains a + // vote interval different than the one the local node calculated. + errMismatchingEpochVoteInterval = errors.New("mismatching vote interval on epoch block") + // errInvalidDifficulty is returned if the difficulty of a block is missing. errInvalidDifficulty = errors.New("invalid difficulty") @@ -318,6 +323,18 @@ func New( return c } +func (p *Parlia) VoteInterval(chain consensus.ChainHeaderReader, header *types.Header) (uint8, error) { + expectedHeader := header + if expectedHeader == nil { + expectedHeader = chain.CurrentHeader() + } + snap, err := p.snapshot(chain, expectedHeader.Number.Uint64()-1, expectedHeader.ParentHash, nil) + if err != nil { + return 0, err + } + return snap.VoteInterval, nil +} + func (p *Parlia) Period() uint64 { return p.config.Period } @@ -467,6 +484,13 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header if attestation == nil { return nil } + voteInterval, err := p.VoteInterval(chain, nil) + if err != nil { + return err + } + if header.Number.Uint64()%uint64(voteInterval) != 0 { + return errors.New("invalid attestation, nil expected to align with the voting interval.") + } if attestation.Data == nil { return errors.New("invalid attestation, vote data is nil") } @@ -474,24 +498,27 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header return fmt.Errorf("invalid attestation, too large extra length: %d", len(attestation.Extra)) } - // Get parent block - parent, err := p.getParent(chain, header, parents) - if err != nil { - return err + // Get target block + targetBlock := header + for i := voteInterval; i > 0; i-- { + targetBlock = chain.GetHeaderByHash(targetBlock.ParentHash) + if targetBlock == nil { + return errors.New("parent not found") + } } // The target block should be direct parent. targetNumber := attestation.Data.TargetNumber targetHash := attestation.Data.TargetHash - if targetNumber != parent.Number.Uint64() || targetHash != parent.Hash() { + if targetNumber != targetBlock.Number.Uint64() || targetHash != targetBlock.Hash() { return fmt.Errorf("invalid attestation, target mismatch, expected block: %d, hash: %s; real block: %d, hash: %s", - parent.Number.Uint64(), parent.Hash(), targetNumber, targetHash) + targetBlock.Number.Uint64(), targetBlock.Hash(), targetNumber, targetHash) } // The source block should be the highest justified block. sourceNumber := attestation.Data.SourceNumber sourceHash := attestation.Data.SourceHash - headers := []*types.Header{parent} + headers := []*types.Header{targetBlock} if len(parents) > 0 { headers = parents } @@ -504,13 +531,13 @@ func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header justifiedBlockNumber, justifiedBlockHash, sourceNumber, sourceHash) } - // The snapshot should be the targetNumber-1 block's snapshot. - if len(parents) > 1 { - parents = parents[:len(parents)-1] + if len(parents) > int(voteInterval) { + parents = parents[:len(parents)-int(voteInterval)] } else { parents = nil } - snap, err := p.snapshot(chain, parent.Number.Uint64()-1, parent.ParentHash, parents) + // The snapshot should be the targetNumber-1 block's snapshot. + snap, err := p.snapshot(chain, targetBlock.Number.Uint64()-1, targetBlock.ParentHash, parents) if err != nil { return err } @@ -770,6 +797,15 @@ func (p *Parlia) snapshot(chain consensus.ChainHeaderReader, number uint64, hash // new snapshot snap = newSnapshot(p.config, p.signatures, number, blockHash, validators, voteAddrs, p.ethAPI) + // get voteInterval from headers and use that for new voteInterval + voteInterval, err := parseVoteInterval(checkpoint, p.chainConfig, p.config) + if err != nil { + return nil, err + } + if voteInterval != nil { + snap.VoteInterval = *voteInterval + } + // get turnLength from headers and use that for new turnLength turnLength, err := parseTurnLength(checkpoint, p.chainConfig, p.config) if err != nil { @@ -956,15 +992,33 @@ func (p *Parlia) prepareTurnLength(chain consensus.ChainHeaderReader, header *ty return err } - if turnLength != nil { - header.Extra = append(header.Extra, *turnLength) + header.Extra = append(header.Extra, *turnLength) + + return nil +} + +func (p *Parlia) prepareVoteInterval(chain consensus.ChainHeaderReader, header *types.Header) error { + if header.Number.Uint64()%p.config.Epoch != 0 || + !p.chainConfig.IsPauli(header.Number, header.Time) { + return nil } + voteInterval, err := p.getVoteInterval(chain, header) + if err != nil { + return err + } + + header.Extra[extraVanity-nextForkHashSize-voteIntervalSize] = *voteInterval + return nil } func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, header *types.Header) error { - if !p.chainConfig.IsLuban(header.Number) || header.Number.Uint64() < 2 { + voteInterval, err := p.VoteInterval(chain, nil) + if err != nil { + return err + } + if !p.chainConfig.IsLuban(header.Number) || header.Number.Uint64() < 2 || header.Number.Uint64()%uint64(voteInterval) != 0 { return nil } @@ -972,23 +1026,26 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head return nil } - // Fetch direct parent's votes - parent := chain.GetHeaderByHash(header.ParentHash) - if parent == nil { - return errors.New("parent not found") + // Fetch votes + targetBlock := header + for i := voteInterval; i > 0; i-- { + targetBlock = chain.GetHeaderByHash(targetBlock.ParentHash) + if targetBlock == nil { + return errors.New("parent not found") + } } - snap, err := p.snapshot(chain, parent.Number.Uint64()-1, parent.ParentHash, nil) + snap, err := p.snapshot(chain, targetBlock.Number.Uint64()-1, targetBlock.ParentHash, nil) if err != nil { return err } - votes := p.VotePool.FetchVoteByBlockHash(parent.Hash()) + votes := p.VotePool.FetchVoteByBlockHash(targetBlock.Hash()) if len(votes) < cmath.CeilDiv(len(snap.Validators)*2, 3) { return nil } // Prepare vote attestation // Prepare vote data - justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{parent}) + justifiedBlockNumber, justifiedBlockHash, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{targetBlock}) if err != nil { return errors.New("unexpected error when getting the highest justified number and hash") } @@ -996,8 +1053,8 @@ func (p *Parlia) assembleVoteAttestation(chain consensus.ChainHeaderReader, head Data: &types.VoteData{ SourceNumber: justifiedBlockNumber, SourceHash: justifiedBlockHash, - TargetNumber: parent.Number.Uint64(), - TargetHash: parent.Hash(), + TargetNumber: targetBlock.Number.Uint64(), + TargetHash: targetBlock.Hash(), }, } // Check vote data from votes @@ -1071,11 +1128,6 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header // Set the correct difficulty header.Difficulty = CalcDifficulty(snap, p.val) - // Ensure the extra data has all it's components - if len(header.Extra) < extraVanity-nextForkHashSize { - header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-nextForkHashSize-len(header.Extra))...) - } - // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, number-1) if parent == nil { @@ -1086,6 +1138,13 @@ func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header header.Time = uint64(time.Now().Unix()) } + // Ensure the extra data has all it's components + if len(header.Extra) < extraVanity-nextForkHashSize { + header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-nextForkHashSize-len(header.Extra))...) + } + if err := p.prepareVoteInterval(chain, header); err != nil { + return err + } header.Extra = header.Extra[:extraVanity-nextForkHashSize] nextForkHash := forkid.NextForkHash(p.chainConfig, p.genesisHash, chain.GenesisHeader().Time, number, header.Time) header.Extra = append(header.Extra, nextForkHash[:]...) @@ -1171,6 +1230,30 @@ func (p *Parlia) verifyTurnLength(chain consensus.ChainHeaderReader, header *typ return errMismatchingEpochTurnLength } +func (p *Parlia) verifyVoteInterval(chain consensus.ChainHeaderReader, header *types.Header) error { + if header.Number.Uint64()%p.config.Epoch != 0 || + !p.chainConfig.IsPauli(header.Number, header.Time) { + return nil + } + + voteIntervalFromHeader, err := parseVoteInterval(header, p.chainConfig, p.config) + if err != nil { + return err + } + if voteIntervalFromHeader != nil { + voteInterval, err := p.getVoteInterval(chain, header) + if err != nil { + return err + } + if voteInterval != nil && *voteInterval == *voteIntervalFromHeader { + log.Debug("verifyVoteInterval", "voteInterval", *voteInterval) + return nil + } + } + + return errMismatchingEpochVoteInterval +} + func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header, cx core.ChainContext, txs *[]*types.Transaction, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, usedGas *uint64, mining bool) error { @@ -1255,6 +1338,11 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade if err != nil { return err } + + if err := p.verifyVoteInterval(chain, header); err != nil { + return err + } + nextForkHash := forkid.NextForkHash(p.chainConfig, p.genesisHash, chain.GenesisHeader().Time, number, header.Time) if !snap.isMajorityFork(hex.EncodeToString(nextForkHash[:])) { log.Debug("there is a possible fork, and your client is not the majority. Please check...", "nextForkHash", hex.EncodeToString(nextForkHash[:])) @@ -1542,7 +1630,7 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv // The blocking time should be no more than half of period when snap.TurnLength == 1 timeForMining := time.Duration(p.config.Period) * time.Second / 2 if !snap.lastBlockInOneTurn(header.Number.Uint64()) { - timeForMining = time.Duration(p.config.Period) * time.Second * 2 / 3 + timeForMining = time.Duration(p.config.Period) * time.Second } if delay > timeForMining { delay = timeForMining diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go index 339736771d..47da6156e8 100644 --- a/consensus/parlia/snapshot.go +++ b/consensus/parlia/snapshot.go @@ -21,7 +21,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "fmt" "math" "sort" @@ -44,6 +43,7 @@ type Snapshot struct { Number uint64 `json:"number"` // Block number where the snapshot was created Hash common.Hash `json:"hash"` // Block hash where the snapshot was created + VoteInterval uint8 `json:"vote_interval"` // Number of blocks between two voting rounds for Fast Finality TurnLength uint8 `json:"turn_length"` // Length of `turn`, meaning the consecutive number of blocks a validator receives priority for block production Validators map[common.Address]*ValidatorInfo `json:"validators"` // Set of authorized validators at this moment Recents map[uint64]common.Address `json:"recents"` // Set of recent validators for spam protections @@ -74,6 +74,7 @@ func newSnapshot( sigCache: sigCache, Number: number, Hash: hash, + VoteInterval: defaultVoteInterval, TurnLength: defaultTurnLength, Recents: make(map[uint64]common.Address), RecentForkHashes: make(map[uint64]string), @@ -117,6 +118,9 @@ func loadSnapshot(config *params.ParliaConfig, sigCache *lru.ARCCache, db ethdb. if err := json.Unmarshal(blob, snap); err != nil { return nil, err } + if snap.VoteInterval == 0 { // no VoteInterval field in old snapshots + snap.VoteInterval = defaultVoteInterval + } if snap.TurnLength == 0 { // no TurnLength field in old snapshots snap.TurnLength = defaultTurnLength } @@ -145,6 +149,7 @@ func (s *Snapshot) copy() *Snapshot { sigCache: s.sigCache, Number: s.Number, Hash: s.Hash, + VoteInterval: s.VoteInterval, TurnLength: s.TurnLength, Validators: make(map[common.Address]*ValidatorInfo), Recents: make(map[uint64]common.Address), @@ -184,33 +189,19 @@ func (s *Snapshot) isMajorityFork(forkHash string) bool { return ally > len(s.RecentForkHashes)/2 } -func (s *Snapshot) updateAttestation(header *types.Header, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) { - if !chainConfig.IsLuban(header.Number) { - return - } - +func (s *Snapshot) updateAttestation(header *types.Header, chain consensus.ChainHeaderReader, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) { // The attestation should have been checked in verify header, update directly attestation, _ := getVoteAttestationFromHeader(header, chainConfig, parliaConfig) if attestation == nil { return } - // Headers with bad attestation are accepted before Plato upgrade, - // but Attestation of snapshot is only updated when the target block is direct parent of the header - targetNumber := attestation.Data.TargetNumber - targetHash := attestation.Data.TargetHash - if targetHash != header.ParentHash || targetNumber+1 != header.Number.Uint64() { - log.Warn("updateAttestation failed", "error", fmt.Errorf("invalid attestation, target mismatch, expected block: %d, hash: %s; real block: %d, hash: %s", - header.Number.Uint64()-1, header.ParentHash, targetNumber, targetHash)) - updateAttestationErrorCounter.Inc(1) - return - } - // Update attestation // Two scenarios for s.Attestation being nil: // 1) The first attestation is assembled. // 2) The snapshot on disk is missing, prompting the creation of a new snapshot using `newSnapshot`. - if s.Attestation != nil && attestation.Data.SourceNumber+1 != attestation.Data.TargetNumber { + lastVoteInterval := s.VoteInterval // use the value before the switch at each validator set change block + if s.Attestation != nil && attestation.Data.SourceNumber+uint64(lastVoteInterval) != attestation.Data.TargetNumber { s.Attestation.TargetNumber = attestation.Data.TargetNumber s.Attestation.TargetHash = attestation.Data.TargetHash } else { @@ -310,7 +301,7 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea } snap.Recents[number] = validator snap.RecentForkHashes[number] = hex.EncodeToString(header.Extra[extraVanity-nextForkHashSize : extraVanity]) - snap.updateAttestation(header, chainConfig, s.config) + snap.updateAttestation(header, chain, chainConfig, s.config) // change validator set if number > 0 && number%s.config.Epoch == snap.minerHistoryCheckLen() { epochKey := math.MaxUint64 - header.Number.Uint64()/s.config.Epoch // impossible used as a block number @@ -327,6 +318,16 @@ func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderRea return nil, consensus.ErrUnknownAncestor } + // get voteInterval from headers and use that for new voteInterval + voteInterval, err := parseVoteInterval(checkpointHeader, chainConfig, s.config) + if err != nil { + return nil, err + } + if voteInterval != nil { + snap.VoteInterval = *voteInterval + log.Debug("validator set switch", "voteInterval", *voteInterval) + } + oldVersionsLen := snap.versionHistoryCheckLen() // get turnLength from headers and use that for new turnLength turnLength, err := parseTurnLength(checkpointHeader, chainConfig, s.config) @@ -488,6 +489,25 @@ func parseTurnLength(header *types.Header, chainConfig *params.ChainConfig, parl return &turnLength, nil } +func parseVoteInterval(header *types.Header, chainConfig *params.ChainConfig, parliaConfig *params.ParliaConfig) (*uint8, error) { + if header.Number.Uint64()%parliaConfig.Epoch != 0 { + return nil, nil + } + + var voteInterval uint8 + if !chainConfig.IsPauli(header.Number, header.Time) { + voteInterval = defaultVoteInterval + } else { + if len(header.Extra) <= extraVanity+extraSeal { + return nil, errInvalidSpanValidators + } + pos := extraVanity - nextForkHashSize - voteIntervalSize + voteInterval = header.Extra[pos] + } + + return &voteInterval, 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/core/genesis.go b/core/genesis.go index 60d45f86ae..d6ac5b0ba2 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -218,6 +218,7 @@ func (e *GenesisMismatchError) Error() string { type ChainOverrides struct { OverridePassedForkTime *uint64 OverrideBohr *uint64 + OverridePauli *uint64 OverrideVerkle *uint64 } @@ -256,6 +257,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g if overrides != nil && overrides.OverrideBohr != nil { config.BohrTime = overrides.OverrideBohr } + if overrides != nil && overrides.OverridePauli != nil { + config.PauliTime = overrides.OverridePauli + } if overrides != nil && overrides.OverrideVerkle != nil { config.VerkleTime = overrides.OverrideVerkle } diff --git a/core/vote/vote_manager.go b/core/vote/vote_manager.go index 891785482b..c45700c718 100644 --- a/core/vote/vote_manager.go +++ b/core/vote/vote_manager.go @@ -148,10 +148,20 @@ func (voteManager *VoteManager) loop() { } curHead := cHead.Header + voteInterval := uint64(1) // equal to parlia.defaultVoteInterval if p, ok := voteManager.engine.(*parlia.Parlia); ok { - nextBlockMinedTime := time.Unix(int64((curHead.Time + p.Period())), 0) + voteInterval, err := p.VoteInterval(voteManager.chain, nil) + if err != nil { + log.Debug("fail to vote", "err", err) + continue + } + if curHead.Number.Uint64()%uint64(voteInterval) != 0 { + log.Debug("skip voting for aligning with the voting interval", "Number", curHead.Number.Uint64(), "voteInterval", voteInterval) + continue + } + nextVotableBlockMinedTime := time.Unix(int64((curHead.Time + p.Period()*uint64(voteInterval))), 0) timeForBroadcast := 50 * time.Millisecond // enough to broadcast a vote - if time.Now().Add(timeForBroadcast).After(nextBlockMinedTime) { + if time.Now().Add(timeForBroadcast).After(nextVotableBlockMinedTime) { log.Warn("too late to vote", "Head.Time(Second)", curHead.Time, "Now(Millisecond)", time.Now().UnixMilli()) continue } @@ -213,7 +223,7 @@ func (voteManager *VoteManager) loop() { // check the latest justified block, which indicating the stability of the network curJustifiedNumber, _, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{curHead}) if err == nil && curJustifiedNumber != 0 { - if curJustifiedNumber+1 != curHead.Number.Uint64() { + if curJustifiedNumber+voteInterval != curHead.Number.Uint64() { log.Debug("not justified", "blockNumber", curHead.Number.Uint64()-1) notJustified.Inc(1) } else { @@ -228,7 +238,7 @@ func (voteManager *VoteManager) loop() { lastJustifiedNumber, _, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{parent}) if err == nil { - if lastJustifiedNumber == 0 || lastJustifiedNumber+1 == curJustifiedNumber { + if lastJustifiedNumber == 0 || lastJustifiedNumber+voteInterval == curJustifiedNumber { continuousJustified.Inc(1) } else { log.Debug("not continuous block justified", "lastJustified", lastJustifiedNumber, "curJustified", curJustifiedNumber) @@ -238,7 +248,6 @@ func (voteManager *VoteManager) loop() { } } } - case event := <-voteManager.syncVoteCh: voteMessage := event.Vote if voteManager.eth.IsMining() || !bytes.Equal(voteManager.signer.PubKey[:], voteMessage.VoteAddress[:]) { diff --git a/eth/backend.go b/eth/backend.go index 0d434a9130..bed891ef75 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -199,6 +199,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { chainConfig.BohrTime = config.OverrideBohr overrides.OverrideBohr = config.OverrideBohr } + if config.OverrideBohr != nil { + chainConfig.PauliTime = config.OverridePauli + overrides.OverridePauli = config.OverridePauli + } if config.OverrideVerkle != nil { chainConfig.VerkleTime = config.OverrideVerkle overrides.OverrideVerkle = config.OverrideVerkle diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 4bda880fce..f4f8657665 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -194,6 +194,9 @@ type Config struct { // OverrideBohr (TODO: remove after the fork) OverrideBohr *uint64 `toml:",omitempty"` + // OverridePauli (TODO: remove after the fork) + OverridePauli *uint64 `toml:",omitempty"` + // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index f23df3aefe..289c40b8d6 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -70,8 +70,9 @@ func (c Config) MarshalTOML() (interface{}, error) { RPCGasCap uint64 RPCEVMTimeout time.Duration RPCTxFeeCap float64 - OverridePassedForkTime *uint64 `toml:",omitempty"` + OverridePassedForkTime *uint64 `toml:",omitempty"` OverrideBohr *uint64 `toml:",omitempty"` + OverridePauli *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"` BlobExtraReserve uint64 } @@ -131,6 +132,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RPCTxFeeCap = c.RPCTxFeeCap enc.OverridePassedForkTime = c.OverridePassedForkTime enc.OverrideBohr = c.OverrideBohr + enc.OverridePauli = c.OverridePauli enc.OverrideVerkle = c.OverrideVerkle enc.BlobExtraReserve = c.BlobExtraReserve return &enc, nil @@ -192,8 +194,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RPCGasCap *uint64 RPCEVMTimeout *time.Duration RPCTxFeeCap *float64 - OverridePassedForkTime *uint64 `toml:",omitempty"` + OverridePassedForkTime *uint64 `toml:",omitempty"` OverrideBohr *uint64 `toml:",omitempty"` + OverridePauli *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"` BlobExtraReserve *uint64 } @@ -366,6 +369,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideBohr != nil { c.OverrideBohr = dec.OverrideBohr } + if dec.OverridePauli != nil { + c.OverridePauli = dec.OverridePauli + } if dec.OverrideVerkle != nil { c.OverrideVerkle = dec.OverrideVerkle } diff --git a/params/config.go b/params/config.go index 2c73e8dfe7..0824879d8f 100644 --- a/params/config.go +++ b/params/config.go @@ -517,6 +517,7 @@ type ChainConfig struct { HaberTime *uint64 `json:"haberTime,omitempty"` // Haber switch time (nil = no fork, 0 = already on haber) HaberFixTime *uint64 `json:"haberFixTime,omitempty"` // HaberFix switch time (nil = no fork, 0 = already on haberFix) BohrTime *uint64 `json:"bohrTime,omitempty"` // Bohr switch time (nil = no fork, 0 = already on bohr) + PauliTime *uint64 `json:"pauliTime,omitempty"` // Pauli switch time (nil = no fork, 0 = already on pauli) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) @@ -637,7 +638,12 @@ func (c *ChainConfig) String() string { BohrTime = big.NewInt(0).SetUint64(*c.BohrTime) } - 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, Niels: %v, MirrorSync: %v, Bruno: %v, Berlin: %v, YOLO v3: %v, CatalystBlock: %v, London: %v, ArrowGlacier: %v, MergeFork:%v, Euler: %v, Gibbs: %v, Nano: %v, Moran: %v, Planck: %v,Luban: %v, Plato: %v, Hertz: %v, Hertzfix: %v, ShanghaiTime: %v, KeplerTime: %v, FeynmanTime: %v, FeynmanFixTime: %v, CancunTime: %v, HaberTime: %v, HaberFixTime: %v, BohrTime: %v, Engine: %v}", + var PauliTime *big.Int + if c.PauliTime != nil { + PauliTime = big.NewInt(0).SetUint64(*c.PauliTime) + } + + 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, Niels: %v, MirrorSync: %v, Bruno: %v, Berlin: %v, YOLO v3: %v, CatalystBlock: %v, London: %v, ArrowGlacier: %v, MergeFork:%v, Euler: %v, Gibbs: %v, Nano: %v, Moran: %v, Planck: %v,Luban: %v, Plato: %v, Hertz: %v, Hertzfix: %v, ShanghaiTime: %v, KeplerTime: %v, FeynmanTime: %v, FeynmanFixTime: %v, CancunTime: %v, HaberTime: %v, HaberFixTime: %v, BohrTime: %v, PauliTime: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -677,6 +683,7 @@ func (c *ChainConfig) String() string { HaberTime, HaberFixTime, BohrTime, + PauliTime, engine, ) } @@ -977,6 +984,20 @@ func (c *ChainConfig) IsOnBohr(currentBlockNumber *big.Int, lastBlockTime uint64 return !c.IsBohr(lastBlockNumber, lastBlockTime) && c.IsBohr(currentBlockNumber, currentBlockTime) } +// IsPauli returns whether time is either equal to the Pauli fork time or greater. +func (c *ChainConfig) IsPauli(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.PauliTime, time) +} + +// IsOnPauli returns whether currentBlockTime is either equal to the Pauli fork time or greater firstly. +func (c *ChainConfig) IsOnPauli(currentBlockNumber *big.Int, lastBlockTime uint64, currentBlockTime uint64) bool { + lastBlockNumber := new(big.Int) + if currentBlockNumber.Cmp(big.NewInt(1)) >= 0 { + lastBlockNumber.Sub(currentBlockNumber, big.NewInt(1)) + } + return !c.IsPauli(lastBlockNumber, lastBlockTime) && c.IsPauli(currentBlockNumber, currentBlockTime) +} + // IsPrague returns whether num is either equal to the Prague fork time or greater. func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) @@ -1043,6 +1064,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "haberTime", timestamp: c.HaberTime}, {name: "haberFixTime", timestamp: c.HaberFixTime}, {name: "bohrTime", timestamp: c.BohrTime}, + {name: "pauliTime", timestamp: c.PauliTime}, {name: "pragueTime", timestamp: c.PragueTime, optional: true}, {name: "verkleTime", timestamp: c.VerkleTime, optional: true}, } { @@ -1199,6 +1221,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.BohrTime, newcfg.BohrTime, headTimestamp) { return newTimestampCompatError("Bohr fork timestamp", c.BohrTime, newcfg.BohrTime) } + if isForkTimestampIncompatible(c.PauliTime, newcfg.PauliTime, headTimestamp) { + return newTimestampCompatError("Pauli fork timestamp", c.PauliTime, newcfg.PauliTime) + } if isForkTimestampIncompatible(c.PragueTime, newcfg.PragueTime, headTimestamp) { return newTimestampCompatError("Prague fork timestamp", c.PragueTime, newcfg.PragueTime) } @@ -1382,7 +1407,7 @@ type Rules struct { IsHertz bool IsHertzfix bool IsShanghai, IsKepler, IsFeynman, IsCancun, IsHaber bool - IsBohr, IsPrague, IsVerkle bool + IsBohr, IsPauli, IsPrague, IsVerkle bool } // Rules ensures c's ChainID is not nil. @@ -1419,6 +1444,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsCancun: c.IsCancun(num, timestamp), IsHaber: c.IsHaber(num, timestamp), IsBohr: c.IsBohr(num, timestamp), + IsPauli: c.IsPauli(num, timestamp), IsPrague: c.IsPrague(num, timestamp), IsVerkle: c.IsVerkle(num, timestamp), } diff --git a/params/protocol_params.go b/params/protocol_params.go index 65b2d942c1..4413fe305d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -197,6 +197,9 @@ var ( // 2 --> use random values to test switching turn length // 0 and other values --> get turn length from contract FixedTurnLength uint64 = 0 + // used for testing: + //. [1,3] --> used as vote interval directly + FixedVoteInterval uint64 = 0 ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations