From 450a63fc8c933ade5a10a23f3c5505585405e6b4 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 30 Sep 2024 16:48:38 -0700 Subject: [PATCH 01/17] intro new timeout (#651) * intro new timeout * correct comment --- eth/peer.go | 2 +- params/config.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/eth/peer.go b/eth/peer.go index aa846a797e..8035f2a9f0 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -77,7 +77,7 @@ type peer struct { knownVote mapset.Set // Set of BFT Vote known to be known by this peer knownTimeout mapset.Set // Set of BFT timeout known to be known by this peer - knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer` + knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer } func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { diff --git a/params/config.go b/params/config.go index 6335310a8a..a4dcdbe750 100644 --- a/params/config.go +++ b/params/config.go @@ -48,6 +48,14 @@ var ( TimeoutPeriod: 30, MinePeriod: 2, }, + 2000: { + MaxMasternodes: 108, + SwitchRound: 2000, + CertThreshold: 0.667, + TimeoutSyncThreshold: 2, + TimeoutPeriod: 600, + MinePeriod: 2, + }, } TestnetV2Configs = map[uint64]*V2Config{ From 9751e41dd50d5866c68a1162d82d066b9fec8a28 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 30 Sep 2024 20:05:14 -0700 Subject: [PATCH 02/17] Mainnet debug (#655) * intro new timeout (#651) * intro new timeout * correct comment * disable ProcessForensics * disable ProcessForensics * change version * enable periodicProfilingFlag * fix: ignore old timeout msg * fix: ignore old timeout msg including equal to the current round * udpate version file --- consensus/XDPoS/engines/engine_v2/engine.go | 4 ++++ consensus/XDPoS/engines/engine_v2/forensics.go | 3 +++ eth/peer.go | 2 +- internal/debug/flags.go | 2 +- params/config.go | 8 ++++++++ params/version.go | 2 +- 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 9cc25d7fab..0b05d7e8c1 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -654,6 +654,10 @@ func (x *XDPoS_v2) VoteHandler(chain consensus.ChainReader, voteMsg *types.Vote) 3. Broadcast(Not part of consensus) */ func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *types.Timeout) (bool, error) { + if timeoutMsg.Round <= x.currentRound { + log.Debug("[VerifyTimeoutMessage] Disqualified timeout message as the proposed round does not match currentRound", "timeoutHash", timeoutMsg.Hash(), "timeoutRound", timeoutMsg.Round, "currentRound", x.currentRound) + return false, nil + } snap, err := x.getSnapshot(chain, timeoutMsg.GapNumber, true) if err != nil || snap == nil { log.Error("[VerifyTimeoutMessage] Fail to get snapshot when verifying timeout message!", "messageGapNumber", timeoutMsg.GapNumber, "err", err) diff --git a/consensus/XDPoS/engines/engine_v2/forensics.go b/consensus/XDPoS/engines/engine_v2/forensics.go index 1c4542ab0e..df551a498f 100644 --- a/consensus/XDPoS/engines/engine_v2/forensics.go +++ b/consensus/XDPoS/engines/engine_v2/forensics.go @@ -86,6 +86,7 @@ Forensics runs in a seperate go routine as its no system critical Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow */ func (f *Forensics) ProcessForensics(chain consensus.ChainReader, engine *XDPoS_v2, incomingQC types.QuorumCert) error { + return nil log.Debug("Received a QC in forensics", "QC", incomingQC) // Clone the values to a temporary variable highestCommittedQCs := f.HighestCommittedQCs @@ -393,6 +394,7 @@ Forensics runs in a seperate go routine as its no system critical Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/99516417/Vote+Equivocation+detection+specification */ func (f *Forensics) ProcessVoteEquivocation(chain consensus.ChainReader, engine *XDPoS_v2, incomingVote *types.Vote) error { + return nil log.Debug("Received a vote in forensics", "vote", incomingVote) // Clone the values to a temporary variable highestCommittedQCs := f.HighestCommittedQCs @@ -483,6 +485,7 @@ func (f *Forensics) isVoteBlamed(chain consensus.ChainReader, highestCommittedQC } func (f *Forensics) DetectEquivocationInVotePool(vote *types.Vote, votePool *utils.Pool) { + return poolKey := vote.PoolKey() votePoolKeys := votePool.PoolObjKeysList() signer, err := GetVoteSignerAddresses(vote) diff --git a/eth/peer.go b/eth/peer.go index aa846a797e..8035f2a9f0 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -77,7 +77,7 @@ type peer struct { knownVote mapset.Set // Set of BFT Vote known to be known by this peer knownTimeout mapset.Set // Set of BFT timeout known to be known by this peer - knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer` + knownSyncInfo mapset.Set // Set of BFT Sync Info known to be known by this peer } func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 3b4ff04122..16a87c9810 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -106,7 +106,7 @@ var Flags = []cli.Flag{ //blockprofilerateFlag, cpuprofileFlag, //traceFlag, - //periodicProfilingFlag, + periodicProfilingFlag, debugDataDirFlag, } diff --git a/params/config.go b/params/config.go index 650722c4af..a68b80a74d 100644 --- a/params/config.go +++ b/params/config.go @@ -48,6 +48,14 @@ var ( TimeoutPeriod: 30, MinePeriod: 2, }, + 2000: { + MaxMasternodes: 108, + SwitchRound: 2000, + CertThreshold: 0.667, + TimeoutSyncThreshold: 2, + TimeoutPeriod: 600, + MinePeriod: 2, + }, } TestnetV2Configs = map[uint64]*V2Config{ diff --git a/params/version.go b/params/version.go index 1b5dea34b2..7cfed1a11a 100644 --- a/params/version.go +++ b/params/version.go @@ -22,7 +22,7 @@ import ( const ( VersionMajor = 2 // Major version component of the current release - VersionMinor = 0 // Minor version component of the current release + VersionMinor = 2 // Minor version component of the current release VersionPatch = 0 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) From ccbedce27bfa840f87aef1c2351d9b02ce3c95c3 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 30 Sep 2024 20:20:23 -0700 Subject: [PATCH 03/17] relocate broadcast logic (#656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 賴怡誠 --- eth/bft/bft_handler.go | 6 +++--- params/version.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/bft/bft_handler.go b/eth/bft/bft_handler.go index cb39dc31a1..22c8c14ea0 100644 --- a/eth/bft/bft_handler.go +++ b/eth/bft/bft_handler.go @@ -11,7 +11,7 @@ import ( const maxBlockDist = 7 // Maximum allowed backward distance from the chain head, 7 is just a magic number indicate very close block -//Define Boradcast Group functions +// Define Boradcast Group functions type broadcastVoteFn func(*types.Vote) type broadcastTimeoutFn func(*types.Timeout) type broadcastSyncInfoFn func(*types.SyncInfo) @@ -126,8 +126,8 @@ func (b *Bfter) Timeout(peer string, timeout *types.Timeout) error { return err } - b.broadcastCh <- timeout if verified { + b.broadcastCh <- timeout err = b.consensus.timeoutHandler(b.blockChainReader, timeout) if err != nil { if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok { @@ -156,9 +156,9 @@ func (b *Bfter) SyncInfo(peer string, syncInfo *types.SyncInfo) error { return err } - b.broadcastCh <- syncInfo // Process only if verified and qualified if verified { + b.broadcastCh <- syncInfo err = b.consensus.syncInfoHandler(b.blockChainReader, syncInfo) if err != nil { log.Error("handle BFT SyncInfo", "error", err) diff --git a/params/version.go b/params/version.go index 7cfed1a11a..6d5ad0874e 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 2 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionPatch = 1 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) From 37f2320af699a3616d5c8b58bf1e892cd3c6c0ba Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 30 Sep 2024 21:13:49 -0700 Subject: [PATCH 04/17] increase message buffer to keep message (#657) * increase message buffer to keep message * update version --- eth/peer.go | 14 +++++++------- params/version.go | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eth/peer.go b/eth/peer.go index 8035f2a9f0..eb7730b99a 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -37,13 +37,13 @@ var ( ) const ( - maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownOrderTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownLendingTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS) - maxKnownVote = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownTimeout = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownSyncInfo = 1024 // Maximum transactions hashes to keep in the known list (prevent DOS) + maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) + maxKnownOrderTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) + maxKnownLendingTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) + maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS) + maxKnownVote = 131072 // Maximum transactions hashes to keep in the known list (prevent DOS) + maxKnownTimeout = 131072 // Maximum transactions hashes to keep in the known list (prevent DOS) + maxKnownSyncInfo = 131072 // Maximum transactions hashes to keep in the known list (prevent DOS) handshakeTimeout = 5 * time.Second ) diff --git a/params/version.go b/params/version.go index 6d5ad0874e..a83b28c4e9 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 2 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionPatch = 2 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) From e6190dd25ffffb78aba3c18568f4e31eb6ab997e Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 30 Sep 2024 21:32:22 -0700 Subject: [PATCH 05/17] fix timeout skip condition (#659) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 賴怡誠 --- consensus/XDPoS/engines/engine_v2/engine.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 0b05d7e8c1..c1c75f2abf 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -654,7 +654,7 @@ func (x *XDPoS_v2) VoteHandler(chain consensus.ChainReader, voteMsg *types.Vote) 3. Broadcast(Not part of consensus) */ func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *types.Timeout) (bool, error) { - if timeoutMsg.Round <= x.currentRound { + if timeoutMsg.Round < x.currentRound { log.Debug("[VerifyTimeoutMessage] Disqualified timeout message as the proposed round does not match currentRound", "timeoutHash", timeoutMsg.Hash(), "timeoutRound", timeoutMsg.Round, "currentRound", x.currentRound) return false, nil } From 64d6c30249315beaf83c772490f6338f6cdc5dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B3=B4=E6=80=A1=E8=AA=A0?= Date: Mon, 30 Sep 2024 21:43:56 -0700 Subject: [PATCH 06/17] bump version --- params/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index a83b28c4e9..c631d85ebe 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 2 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release + VersionPatch = 3 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) From 528f10b422974b4ebf460b333730a6b658d84248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B3=B4=E6=80=A1=E8=AA=A0?= Date: Tue, 1 Oct 2024 18:18:45 -0700 Subject: [PATCH 07/17] update to 1 min timeout --- params/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/params/config.go b/params/config.go index a68b80a74d..cd0a9eca03 100644 --- a/params/config.go +++ b/params/config.go @@ -56,6 +56,14 @@ var ( TimeoutPeriod: 600, MinePeriod: 2, }, + 8000: { + MaxMasternodes: 108, + SwitchRound: 8000, + CertThreshold: 0.667, + TimeoutSyncThreshold: 2, + TimeoutPeriod: 60, + MinePeriod: 2, + }, } TestnetV2Configs = map[uint64]*V2Config{ From 7c9ee337af663bf46c491dd21e03ed92001688db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B3=B4=E6=80=A1=E8=AA=A0?= Date: Tue, 1 Oct 2024 18:31:00 -0700 Subject: [PATCH 08/17] update version --- params/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index c631d85ebe..b203e806ab 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 2 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 3 // Patch version component of the current release + VersionPatch = 4 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) From 861c7102a26b8f5281bf876898a39390208e4e7f Mon Sep 17 00:00:00 2001 From: "Mr.P" Date: Wed, 9 Oct 2024 04:28:24 +0300 Subject: [PATCH 09/17] cherry pick from #664 --- cmd/XDC/main.go | 1 + cmd/XDC/usage.go | 1 + cmd/utils/flags.go | 5 ++++- eth/config.go | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 419978ba6e..2a425d21aa 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -137,6 +137,7 @@ var ( rpcFlags = []cli.Flag{ utils.RPCEnabledFlag, + utils.RPCGlobalGasCapFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, utils.RPCHttpWriteTimeoutFlag, diff --git a/cmd/XDC/usage.go b/cmd/XDC/usage.go index e6b2fc961f..8a4abadab0 100644 --- a/cmd/XDC/usage.go +++ b/cmd/XDC/usage.go @@ -143,6 +143,7 @@ var AppHelpFlagGroups = []flagGroup{ Name: "API AND CONSOLE", Flags: []cli.Flag{ utils.RPCEnabledFlag, + utils.RPCGlobalGasCapFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, utils.RPCHttpWriteTimeoutFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a9fd25c492..a195cfa9c2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -359,7 +359,7 @@ var ( Usage: "Record information useful for VM and contract debugging", } RPCGlobalGasCapFlag = cli.Uint64Flag{ - Name: "rpc.gascap", + Name: "rpc-gascap", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", Value: eth.DefaultConfig.RPCGasCap, } @@ -1171,6 +1171,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(DocRootFlag.Name) { cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) } + if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) { + cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name) + } if ctx.GlobalIsSet(ExtraDataFlag.Name) { cfg.ExtraData = []byte(ctx.GlobalString(ExtraDataFlag.Name)) } diff --git a/eth/config.go b/eth/config.go index 73a95aa675..e29332f210 100644 --- a/eth/config.go +++ b/eth/config.go @@ -51,7 +51,7 @@ var DefaultConfig = Config{ GasPrice: big.NewInt(0.25 * params.Shannon), TxPool: core.DefaultTxPoolConfig, - RPCGasCap: 25000000, + RPCGasCap: 50000000, GPO: gasprice.Config{ Blocks: 20, Percentile: 60, From eb6d53adf67582017d000d50c8a2c181355d3f9a Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Wed, 9 Oct 2024 22:05:14 -0700 Subject: [PATCH 10/17] Fix getCandidateStatus API (#663) * Fix getCandidateStatus API * sync vote message and fix test * update function name --------- Co-authored-by: Liam Lai --- .../tests/engine_v2_tests/forensics_test.go | 15 ++++++++++ eth/api_backend.go | 4 +-- eth/bft/bft_handler.go | 3 +- eth/bft/bft_handler_test.go | 6 ++-- internal/ethapi/api.go | 28 +++++++++++-------- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/consensus/tests/engine_v2_tests/forensics_test.go b/consensus/tests/engine_v2_tests/forensics_test.go index 10f031aa89..dd9ad913db 100644 --- a/consensus/tests/engine_v2_tests/forensics_test.go +++ b/consensus/tests/engine_v2_tests/forensics_test.go @@ -17,6 +17,8 @@ import ( ) func TestProcessQcShallSetForensicsCommittedQc(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 @@ -92,6 +94,8 @@ func TestProcessQcShallSetForensicsCommittedQc(t *testing.T) { } func TestSetCommittedQCsInOrder(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil) forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker() @@ -118,6 +122,8 @@ func TestSetCommittedQCsInOrder(t *testing.T) { // Happty path func TestForensicsMonitoring(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil) forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker() var decodedCurrentblockExtraField types.ExtraFields_v2 @@ -140,6 +146,7 @@ func TestForensicsMonitoring(t *testing.T) { } func TestForensicsMonitoringNotOnSameChainButHaveSameRoundQC(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") var numOfForks = new(int) *numOfForks = 10 var forkRoundDifference = new(int) @@ -199,6 +206,8 @@ func TestForensicsMonitoringNotOnSameChainButHaveSameRoundQC(t *testing.T) { } func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + var numOfForks = new(int) *numOfForks = 10 var forkRoundDifference = new(int) @@ -260,6 +269,8 @@ func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) { // "prone to attack" test where the "across epoch" field is true func TestForensicsAcrossEpoch(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + var numOfForks = new(int) *numOfForks = 10 var forkRoundDifference = new(int) @@ -322,6 +333,8 @@ func TestForensicsAcrossEpoch(t *testing.T) { } func TestVoteEquivocationSameRound(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + var numOfForks = new(int) *numOfForks = 1 blockchain, _, currentBlock, signer, signFn, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks}) @@ -388,6 +401,8 @@ func TestVoteEquivocationSameRound(t *testing.T) { } func TestVoteEquivocationDifferentRound(t *testing.T) { + t.Skip("Skipping this test for now as we disable forensics") + var numOfForks = new(int) *numOfForks = 10 var forkRoundDifference = new(int) diff --git a/eth/api_backend.go b/eth/api_backend.go index c087ec2d38..4e4f36eebd 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -427,11 +427,11 @@ func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[comm // calculate for 2 epochs ago currentCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, block.Number()) if err != nil { - log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", block) + log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", block.Number(), "err", err) } lastCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, big.NewInt(int64(currentCheckpointNumber-1))) if err != nil { - log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for last checkpoint block", "block", block) + log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for last checkpoint block", "block", block.Number(), "err", err) } lastCheckpointBlock := chain.GetBlockByNumber(lastCheckpointNumber) diff --git a/eth/bft/bft_handler.go b/eth/bft/bft_handler.go index 22c8c14ea0..44d7c9b423 100644 --- a/eth/bft/bft_handler.go +++ b/eth/bft/bft_handler.go @@ -93,9 +93,8 @@ func (b *Bfter) Vote(peer string, vote *types.Vote) error { return err } - b.broadcastCh <- vote - if verified { + b.broadcastCh <- vote err = b.consensus.voteHandler(b.blockChainReader, vote) if err != nil { if _, ok := err.(*utils.ErrIncomingMessageRoundTooFarFromCurrentRound); ok { diff --git a/eth/bft/bft_handler_test.go b/eth/bft/bft_handler_test.go index 5426fc5699..79321ea056 100644 --- a/eth/bft/bft_handler_test.go +++ b/eth/bft/bft_handler_test.go @@ -144,7 +144,7 @@ func TestBoardcastButNotProcessDisqualifiedVotes(t *testing.T) { tester.bfter.Vote(peerID, &vote) time.Sleep(50 * time.Millisecond) - if int(handlerCounter) != targetVotes || int(broadcastCounter) != 1 { + if int(handlerCounter) != targetVotes || int(broadcastCounter) != 0 { t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetVotes) } } @@ -171,7 +171,7 @@ func TestBoardcastButNotProcessDisqualifiedTimeout(t *testing.T) { tester.bfter.Timeout(peerID, &timeout) time.Sleep(50 * time.Millisecond) - if int(handlerCounter) != targetTimeout || int(broadcastCounter) != 1 { + if int(handlerCounter) != targetTimeout || int(broadcastCounter) != 0 { t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetTimeout) } } @@ -198,7 +198,7 @@ func TestBoardcastButNotProcessDisqualifiedSyncInfo(t *testing.T) { tester.bfter.SyncInfo(peerID, &syncInfo) time.Sleep(50 * time.Millisecond) - if int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != 1 { + if int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != 0 { t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetSyncInfo) } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 50c614a98a..696e9c1722 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -869,7 +869,7 @@ func (s *PublicBlockChainAPI) GetCandidateStatus(ctx context.Context, coinbaseAd epochConfig := s.b.ChainConfig().XDPoS.Epoch // checkpoint block - checkpointNumber, epochNumber = s.GetPreviousCheckpointFromEpoch(ctx, epoch) + checkpointNumber, epochNumber = s.GetCheckpointFromEpoch(ctx, epoch) result[fieldEpoch] = epochNumber.Int64() block, err = s.b.BlockByNumber(ctx, checkpointNumber) @@ -1024,7 +1024,7 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch } epochConfig := s.b.ChainConfig().XDPoS.Epoch - checkpointNumber, epochNumber = s.GetPreviousCheckpointFromEpoch(ctx, epoch) + checkpointNumber, epochNumber = s.GetCheckpointFromEpoch(ctx, epoch) result[fieldEpoch] = epochNumber.Int64() block, err = s.b.BlockByNumber(ctx, checkpointNumber) @@ -1168,25 +1168,31 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch return result, nil } -// GetPreviousCheckpointFromEpoch returns header of the previous checkpoint -func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) { +// GetCheckpointFromEpoch returns header of the previous checkpoint +func (s *PublicBlockChainAPI) GetCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) { var checkpointNumber uint64 epoch := s.b.ChainConfig().XDPoS.Epoch if epochNum == rpc.LatestEpochNumber { - blockNumer := s.b.CurrentBlock().Number().Uint64() - diff := blockNumer % epoch - // checkpoint number - checkpointNumber = blockNumer - diff - epochNum = rpc.EpochNumber(checkpointNumber / epoch) - if diff > 0 { - epochNum += 1 + blockNumer := s.b.CurrentBlock().Number() + if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok { + var err error + var currentEpoch uint64 + checkpointNumber, currentEpoch, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, blockNumer) + if err != nil { + log.Error("[GetCheckpointFromEpoch] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", blockNumer, "err", err) + return 0, epochNum + } + + epochNum = rpc.EpochNumber(currentEpoch) } } else if epochNum < 2 { checkpointNumber = 0 } else { + // TODO this checkpointNumber needs to be recalculated for v2 blocks checkpointNumber = epoch * (uint64(epochNum) - 1) } + return rpc.BlockNumber(checkpointNumber), epochNum } From f6afd24de70dab402727becfc9f556e85b435954 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Wed, 9 Oct 2024 23:46:02 -0700 Subject: [PATCH 11/17] update timeout to 30s (#669) --- params/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/params/config.go b/params/config.go index cd0a9eca03..673bd02e22 100644 --- a/params/config.go +++ b/params/config.go @@ -64,6 +64,14 @@ var ( TimeoutPeriod: 60, MinePeriod: 2, }, + 220000: { + MaxMasternodes: 108, + SwitchRound: 220000, + CertThreshold: 0.667, + TimeoutSyncThreshold: 2, + TimeoutPeriod: 30, + MinePeriod: 2, + }, } TestnetV2Configs = map[uint64]*V2Config{ From ec92add42869dad0ca70ec4014b09cb7f196de41 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 10 Oct 2024 00:02:04 -0700 Subject: [PATCH 12/17] Update to version 2.2.5 (#670) --- params/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index b203e806ab..cb23e731de 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 2 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release + VersionPatch = 5 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) From b43bb5ed1fdf08a1d57a6a8448adedf4221528bc Mon Sep 17 00:00:00 2001 From: Liam Lai Date: Tue, 15 Oct 2024 19:20:13 -0700 Subject: [PATCH 13/17] api epoch --- consensus/XDPoS/api.go | 40 ++++++ .../XDPoS/engines/engine_v2/epochSwitch.go | 35 ++++++ consensus/XDPoS/engines/engine_v2/utils.go | 38 ++++++ consensus/XDPoS/utils/types.go | 7 ++ consensus/tests/engine_v2_tests/api_test.go | 118 ++++++++++++++++++ internal/web3ext/web3ext.go | 11 ++ 6 files changed, 249 insertions(+) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index a922e57f78..12405c4624 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -17,6 +17,7 @@ package XDPoS import ( "encoding/base64" + "errors" "math/big" "github.com/XinFinOrg/XDPoSChain/common" @@ -320,3 +321,42 @@ func calculateSigners(message map[string]SignerTypes, pool map[string]map[common } } } + +func (api *API) GetEpochNumbersBetween(begin, end *rpc.BlockNumber) ([]uint64, error) { + beginHeader := api.getHeaderFromApiBlockNum(begin) + if beginHeader == nil { + return nil, errors.New("illegal begin block number") + } + endHeader := api.getHeaderFromApiBlockNum(end) + if endHeader == nil { + return nil, errors.New("illegal end block number") + } + if beginHeader.Number.Cmp(endHeader.Number) > 0 { + return nil, errors.New("illegal begin and end block number, begin > end") + } + epochSwitchInfos, err := api.XDPoS.GetEpochSwitchInfoBetween(api.chain, beginHeader, endHeader) + if err != nil { + return nil, err + } + epochSwitchNumbers := make([]uint64, len(epochSwitchInfos)) + for i, info := range epochSwitchInfos { + epochSwitchNumbers[i] = info.EpochSwitchBlockInfo.Number.Uint64() + } + return epochSwitchNumbers, nil +} + +/* +An API exclusively for V2 consensus, designed to assist in getting rewards of the epoch number. +Given the epoch number, search the epoch switch block. +*/ +func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, error) { + result, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber) + if err != nil { + return nil, err + } + return &utils.EpochNumInfo{ + EpochBlockHash: result.Hash, + EpochRound: result.Round, + EpochBlockNumber: result.Number, + }, nil +} diff --git a/consensus/XDPoS/engines/engine_v2/epochSwitch.go b/consensus/XDPoS/engines/engine_v2/epochSwitch.go index 981c46ff9a..2df90ab1a0 100644 --- a/consensus/XDPoS/engines/engine_v2/epochSwitch.go +++ b/consensus/XDPoS/engines/engine_v2/epochSwitch.go @@ -157,3 +157,38 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash()) return parentRound < epochStartRound, epochNum, nil } + +// GetEpochSwitchInfoBetween get epoch switch between begin and end headers +// Search backwardly from end number to begin number +func (x *XDPoS_v2) GetEpochSwitchInfoBetween(chain consensus.ChainReader, begin, end *types.Header) ([]*types.EpochSwitchInfo, error) { + infos := make([]*types.EpochSwitchInfo, 0) + // after the first iteration, it becomes nil since epoch switch info does not have header info + iteratorHeader := end + // after the first iteration, it becomes the parent hash of the epoch switch block + iteratorHash := end.Hash() + iteratorNum := end.Number + // when iterator is strictly > begin number, do the search + for iteratorNum.Cmp(begin.Number) > 0 { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, iteratorHeader, iteratorHash) + if err != nil { + log.Error("[GetEpochSwitchInfoBetween] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return nil, err + } + iteratorHeader = nil + // V2 switch epoch switch info has nil parent + if epochSwitchInfo.EpochSwitchParentBlockInfo == nil { + break + } + iteratorHash = epochSwitchInfo.EpochSwitchParentBlockInfo.Hash + iteratorNum = epochSwitchInfo.EpochSwitchBlockInfo.Number + if iteratorNum.Cmp(begin.Number) >= 0 { + infos = append(infos, epochSwitchInfo) + } + + } + // reverse the array + for i := 0; i < len(infos)/2; i++ { + infos[i], infos[len(infos)-1-i] = infos[len(infos)-1-i], infos[i] + } + return infos, nil +} diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index e8006951ea..86804ded3d 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -2,6 +2,7 @@ package engine_v2 import ( "fmt" + "math/big" "github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/common" @@ -217,3 +218,40 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t return missedRoundsMetadata, nil } + +func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) { + currentHeader := chain.CurrentHeader() + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash()) + if err != nil { + return nil, err + } + epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch + // since below function GetEpochSwitchInfoBetween(chain, start, end) return nil if start == end, we early return the result + if targetEpochNum == epochNum { + return epochSwitchInfo.EpochSwitchBlockInfo, nil + } + if targetEpochNum > epochNum { + return nil, errors.New("input epoch number > current epoch number") + } + if targetEpochNum < x.config.V2.SwitchBlock.Uint64()/x.config.Epoch { + return nil, errors.New("input epoch number < v2 begin epoch number") + } + epoch := big.NewInt(int64(x.config.Epoch)) + estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum))) + estBlockNum := new(big.Int).Sub(epochSwitchInfo.EpochSwitchBlockInfo.Number, estblockNumDiff) + if estBlockNum.Cmp(x.config.V2.SwitchBlock) == -1 { + estBlockNum.Set(x.config.V2.SwitchBlock) + } + estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64()) + epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader) + if err != nil { + return nil, err + } + for _, info := range epochSwitchInfos { + epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch + if epochNum == targetEpochNum { + return info.EpochSwitchBlockInfo, nil + } + } + return nil, errors.New("input epoch number not found (all rounds in this epoch are missed, which is very rare)") +} diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 897e984b48..96817c294e 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -71,3 +71,10 @@ type PublicApiMissedRoundsMetadata struct { EpochBlockNumber *big.Int MissedRounds []MissedRoundInfo } + +// Given an epoch number, this struct records the epoch switch block (first block in epoch) infos such as block number +type EpochNumInfo struct { + EpochBlockHash common.Hash `json:"hash"` + EpochRound types.Round `json:"round"` + EpochBlockNumber *big.Int `json:"number"` +} diff --git a/consensus/tests/engine_v2_tests/api_test.go b/consensus/tests/engine_v2_tests/api_test.go index fb99f6f879..f12558b7d4 100644 --- a/consensus/tests/engine_v2_tests/api_test.go +++ b/consensus/tests/engine_v2_tests/api_test.go @@ -2,6 +2,7 @@ package engine_v2_tests import ( "math/big" + "reflect" "testing" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" @@ -109,3 +110,120 @@ func TestGetMissedRoundsInEpochByBlockNum(t *testing.T) { assert.NotEqual(t, data.MissedRounds[0].Miner, data.MissedRounds[1].Miner) } + +func TestGetEpochNumbersBetween(t *testing.T) { + _, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) + + engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) + + begin := rpc.BlockNumber(1800) + end := rpc.BlockNumber(1802) + numbers, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.True(t, reflect.DeepEqual([]uint64{1800}, numbers)) + assert.Nil(t, err) + + begin = rpc.BlockNumber(1799) + end = rpc.BlockNumber(1802) + numbers, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.True(t, reflect.DeepEqual([]uint64{1800}, numbers)) + assert.Nil(t, err) + + begin = rpc.BlockNumber(1799) + end = rpc.BlockNumber(1802) + numbers, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.True(t, reflect.DeepEqual([]uint64{1800}, numbers)) + assert.Nil(t, err) + + begin = rpc.BlockNumber(901) + end = rpc.BlockNumber(1802) + numbers, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.True(t, reflect.DeepEqual([]uint64{901, 1800}, numbers)) + assert.Nil(t, err) + + // 900 is V1, not V2, so error + begin = rpc.BlockNumber(900) + end = rpc.BlockNumber(1802) + numbers, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.Nil(t, numbers) + assert.EqualError(t, err, "not supported in the v1 consensus") + + // 1803 not exist + begin = rpc.BlockNumber(901) + end = rpc.BlockNumber(1803) + numbers, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.Nil(t, numbers) + assert.EqualError(t, err, "illegal end block number") + + // 1803 not exist + begin = rpc.BlockNumber(1803) + end = rpc.BlockNumber(1803) + numbers, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetEpochNumbersBetween(&begin, &end) + + assert.Nil(t, numbers) + assert.EqualError(t, err, "illegal begin block number") +} +func TestGetBlockByEpochNumber(t *testing.T) { + blockchain, _, currentBlock, signer, signFn := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, 1802, params.TestXDPoSMockChainConfig) + + blockCoinBase := "0x111000000000000000000000000000000123" + largeRound := int64(1802) + newBlock := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, int(currentBlock.NumberU64())+1, largeRound, blockCoinBase, signer, signFn, nil, nil, currentBlock.Header().Root.Hex()) + err := blockchain.InsertBlock(newBlock) + assert.Nil(t, err) + largeRound2 := int64(3603) + newBlock2 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, newBlock, int(newBlock.NumberU64())+1, largeRound2, blockCoinBase, signer, signFn, nil, nil, newBlock.Header().Root.Hex()) + err = blockchain.InsertBlock(newBlock2) + assert.Nil(t, err) + + // block num, round, epoch is as follows + // 900,0,1 (v2 switch block, not v2 epoch switch block) + // 901,1,1 (1st epoch switch block) + // 902,2,1 + // ... + // 1800,900,2 (2nd epoch switch block) + // 1801,901,2 + // 1802,902,2 + // 1803,1802,3 (epoch switch) + // epoch 4 has no block + // 1804,3603,5 (epoch switch) + engine := blockchain.Engine().(*XDPoS.XDPoS) + + // init the snapshot, otherwise getEpochSwitchInfo would return error + checkpointHeader := blockchain.GetHeaderByNumber(blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() + 1) + err = engine.Initial(blockchain, checkpointHeader) + assert.Nil(t, err) + + info, err := engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(0) + assert.NotNil(t, err) + assert.Nil(t, info) + + info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(1) + assert.Equal(t, info.EpochRound, types.Round(1)) + assert.Nil(t, err) + + info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(2) + assert.Equal(t, info.EpochRound, types.Round(900)) + assert.Nil(t, err) + + info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3) + assert.Equal(t, info.EpochRound, types.Round(largeRound)) + assert.Nil(t, err) + + info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(4) + assert.NotNil(t, err) + assert.Nil(t, info) + + info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(5) + assert.Equal(t, info.EpochRound, types.Round(largeRound2)) + assert.Nil(t, err) + + info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(6) + assert.NotNil(t, err) + assert.Nil(t, info) +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index dbb3f41df4..1921ad6099 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -162,6 +162,17 @@ web3._extend({ params: 1, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), + new web3._extend.Method({ + name: 'getEpochNumbersBetween', + call: 'XDPoS_getEpochNumbersBetween', + params: 2, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter] + }), + new web3._extend.Method({ + name: 'getBlockInfoByEpochNum', + call: 'XDPoS_getBlockInfoByEpochNum', + params: 1, + }), ], properties: [ new web3._extend.Property({ From 5f1fb22e512a6cfefc9a6fe800ea548519265ab9 Mon Sep 17 00:00:00 2001 From: Liam Lai Date: Tue, 15 Oct 2024 19:51:57 -0700 Subject: [PATCH 14/17] fix conflict --- consensus/XDPoS/XDPoS.go | 11 +++++++++++ consensus/XDPoS/engines/engine_v2/utils.go | 1 + 2 files changed, 12 insertions(+) diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index 6d5477a1ee..5c24543b58 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -17,6 +17,7 @@ package XDPoS import ( + "errors" "fmt" "math/big" @@ -549,3 +550,13 @@ func (x *XDPoS) CacheSigningTxs(hash common.Hash, txs []*types.Transaction) []*t func (x *XDPoS) GetCachedSigningTxs(hash common.Hash) (interface{}, bool) { return x.signingTxsCache.Get(hash) } + +func (x *XDPoS) GetEpochSwitchInfoBetween(chain consensus.ChainReader, begin, end *types.Header) ([]*types.EpochSwitchInfo, error) { + beginBlockVersion := x.config.BlockConsensusVersion(begin.Number, begin.Extra, ExtraFieldCheck) + endBlockVersion := x.config.BlockConsensusVersion(end.Number, end.Extra, ExtraFieldCheck) + if beginBlockVersion == params.ConsensusEngineVersion2 && endBlockVersion == params.ConsensusEngineVersion2 { + return x.EngineV2.GetEpochSwitchInfoBetween(chain, begin, end) + } + // Default "v1" + return nil, errors.New("not supported in the v1 consensus") +} diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 86804ded3d..4619ecc14a 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -1,6 +1,7 @@ package engine_v2 import ( + "errors" "fmt" "math/big" From 3dac4dc01441d7e25e7733a4d9b2914cb75ebc00 Mon Sep 17 00:00:00 2001 From: Liam Lai Date: Thu, 17 Oct 2024 18:59:38 -0700 Subject: [PATCH 15/17] reduce to 20s --- params/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/params/config.go b/params/config.go index 89de1be2b6..a7ac5ff3a4 100644 --- a/params/config.go +++ b/params/config.go @@ -72,6 +72,14 @@ var ( TimeoutPeriod: 30, MinePeriod: 2, }, + 460000: { + MaxMasternodes: 108, + SwitchRound: 460000, + CertThreshold: 0.667, + TimeoutSyncThreshold: 2, + TimeoutPeriod: 20, + MinePeriod: 2, + }, } TestnetV2Configs = map[uint64]*V2Config{ From 042c02ba8c74e91cca1253aaae4c70767beede5d Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Sun, 20 Oct 2024 23:43:23 -0700 Subject: [PATCH 16/17] add last block number for epoch api (#681) Co-authored-by: Liam Lai --- consensus/XDPoS/api.go | 16 +++++++++------ consensus/XDPoS/engines/engine_v2/utils.go | 22 ++++++++++++--------- consensus/XDPoS/utils/types.go | 7 ++++--- consensus/tests/engine_v2_tests/api_test.go | 8 ++++++++ 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 287e37b7f4..af8ab44ee2 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -350,13 +350,17 @@ An API exclusively for V2 consensus, designed to assist in getting rewards of th Given the epoch number, search the epoch switch block. */ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, error) { - result, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber) + thisEpoch, nextEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber) if err != nil { return nil, err } - return &utils.EpochNumInfo{ - EpochBlockHash: result.Hash, - EpochRound: result.Round, - EpochBlockNumber: result.Number, - }, nil + info := &utils.EpochNumInfo{ + EpochBlockHash: thisEpoch.Hash, + EpochRound: thisEpoch.Round, + EpochFirstBlockNumber: thisEpoch.Number, + } + if nextEpoch != nil { + info.EpochLastBlockNumber = new(big.Int).Sub(nextEpoch.Number, big.NewInt(1)) + } + return info, nil } diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index a82de0f663..c2e17b7e71 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -220,22 +220,22 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t return missedRoundsMetadata, nil } -func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) { +func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, *types.BlockInfo, error) { currentHeader := chain.CurrentHeader() epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash()) if err != nil { - return nil, err + return nil, nil, err } epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch // since below function GetEpochSwitchInfoBetween(chain, start, end) return nil if start == end, we early return the result if targetEpochNum == epochNum { - return epochSwitchInfo.EpochSwitchBlockInfo, nil + return epochSwitchInfo.EpochSwitchBlockInfo, nil, nil } if targetEpochNum > epochNum { - return nil, errors.New("input epoch number > current epoch number") + return nil, nil, errors.New("input epoch number > current epoch number") } if targetEpochNum < x.config.V2.SwitchBlock.Uint64()/x.config.Epoch { - return nil, errors.New("input epoch number < v2 begin epoch number") + return nil, nil, errors.New("input epoch number < v2 begin epoch number") } epoch := big.NewInt(int64(x.config.Epoch)) estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum))) @@ -246,13 +246,17 @@ func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpoc estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64()) epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader) if err != nil { - return nil, err + return nil, nil, err } - for _, info := range epochSwitchInfos { + for i, info := range epochSwitchInfos { epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch if epochNum == targetEpochNum { - return info.EpochSwitchBlockInfo, nil + if i < len(epochSwitchInfos)-1 { + nextEpoch := epochSwitchInfos[i+1].EpochSwitchBlockInfo + return info.EpochSwitchBlockInfo, nextEpoch, nil + } + return info.EpochSwitchBlockInfo, nil, nil } } - return nil, errors.New("input epoch number not found (all rounds in this epoch are missed, which is very rare)") + return nil, nil, errors.New("input epoch number not found (all rounds in this epoch are missed, which is very rare)") } diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 96817c294e..b5d1168341 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -74,7 +74,8 @@ type PublicApiMissedRoundsMetadata struct { // Given an epoch number, this struct records the epoch switch block (first block in epoch) infos such as block number type EpochNumInfo struct { - EpochBlockHash common.Hash `json:"hash"` - EpochRound types.Round `json:"round"` - EpochBlockNumber *big.Int `json:"number"` + EpochBlockHash common.Hash `json:"hash"` + EpochRound types.Round `json:"round"` + EpochFirstBlockNumber *big.Int `json:"firstBlock"` + EpochLastBlockNumber *big.Int `json:"lastBlock"` } diff --git a/consensus/tests/engine_v2_tests/api_test.go b/consensus/tests/engine_v2_tests/api_test.go index f12558b7d4..723ed33090 100644 --- a/consensus/tests/engine_v2_tests/api_test.go +++ b/consensus/tests/engine_v2_tests/api_test.go @@ -204,14 +204,20 @@ func TestGetBlockByEpochNumber(t *testing.T) { assert.Nil(t, info) info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(1) + assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(901)) + assert.Equal(t, info.EpochLastBlockNumber.Int64(), int64(1799)) assert.Equal(t, info.EpochRound, types.Round(1)) assert.Nil(t, err) info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(2) + assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(1800)) + assert.Equal(t, info.EpochLastBlockNumber.Int64(), int64(1802)) assert.Equal(t, info.EpochRound, types.Round(900)) assert.Nil(t, err) info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3) + assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(1803)) + assert.Equal(t, info.EpochLastBlockNumber.Int64(), int64(1803)) assert.Equal(t, info.EpochRound, types.Round(largeRound)) assert.Nil(t, err) @@ -221,6 +227,8 @@ func TestGetBlockByEpochNumber(t *testing.T) { info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(5) assert.Equal(t, info.EpochRound, types.Round(largeRound2)) + assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(1804)) + assert.Nil(t, info.EpochLastBlockNumber) assert.Nil(t, err) info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(6) From 118ccd08d5a744229f27250f2566af111f415d03 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Tue, 29 Oct 2024 01:36:22 -0700 Subject: [PATCH 17/17] cherry pick epoch api from dev-upgrade (#699) * cherry-pick-epoch-api --- consensus/XDPoS/api.go | 6 +- consensus/XDPoS/engines/engine_v2/engine.go | 7 ++ .../XDPoS/engines/engine_v2/epochSwitch.go | 9 +- consensus/XDPoS/engines/engine_v2/utils.go | 118 +++++++++++++++--- consensus/XDPoS/utils/constants.go | 2 + consensus/tests/engine_v2_tests/api_test.go | 2 +- 6 files changed, 121 insertions(+), 23 deletions(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index af8ab44ee2..13fb11e09a 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -350,7 +350,7 @@ An API exclusively for V2 consensus, designed to assist in getting rewards of th Given the epoch number, search the epoch switch block. */ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, error) { - thisEpoch, nextEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber) + thisEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber) if err != nil { return nil, err } @@ -359,7 +359,9 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, EpochRound: thisEpoch.Round, EpochFirstBlockNumber: thisEpoch.Number, } - if nextEpoch != nil { + nextEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber+1) + + if err == nil { info.EpochLastBlockNumber = new(big.Int).Sub(nextEpoch.Number, big.NewInt(1)) } return info, nil diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 5646465e5d..6d7331025e 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -37,6 +37,10 @@ type XDPoS_v2 struct { epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info verifiedHeaders *lru.ARCCache + // only contains epoch switch block info + // input: round, output: infos of epoch switch block and next epoch switch block info + round2epochBlockInfo *lru.ARCCache + signer common.Address // Ethereum address of the signing key signFn clique.SignerFn // Signer function to authorize hashes with lock sync.RWMutex // Protects the signer fields @@ -77,6 +81,7 @@ func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan i signatures, _ := lru.NewARC(utils.InmemorySnapshots) epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs)) verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots) + round2epochBlockInfo, _ := lru.NewARC(utils.InmemoryRound2Epochs) timeoutPool := utils.NewPool() votePool := utils.NewPool() @@ -96,6 +101,8 @@ func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan i BroadcastCh: make(chan interface{}), minePeriodCh: minePeriodCh, + round2epochBlockInfo: round2epochBlockInfo, + timeoutPool: timeoutPool, votePool: votePool, diff --git a/consensus/XDPoS/engines/engine_v2/epochSwitch.go b/consensus/XDPoS/engines/engine_v2/epochSwitch.go index 2df90ab1a0..e9d76daa62 100644 --- a/consensus/XDPoS/engines/engine_v2/epochSwitch.go +++ b/consensus/XDPoS/engines/engine_v2/epochSwitch.go @@ -56,7 +56,6 @@ func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types log.Error("[getEpochSwitchInfo] get extra field", "err", err, "number", h.Number.Uint64()) return nil, err } - snap, err := x.getSnapshot(chain, h.Number.Uint64(), false) if err != nil { log.Error("[getEpochSwitchInfo] Adaptor v2 getSnapshot has error", "err", err) @@ -155,6 +154,14 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { return true, epochNum, nil } log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash()) + // if isEpochSwitch, add to cache + if parentRound < epochStartRound { + x.round2epochBlockInfo.Add(round, &types.BlockInfo{ + Hash: header.Hash(), + Number: header.Number, + Round: round, + }) + } return parentRound < epochStartRound, epochNum, nil } diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index c2e17b7e71..0c4d6d2220 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -220,43 +220,123 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t return missedRoundsMetadata, nil } -func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, *types.BlockInfo, error) { +func (x *XDPoS_v2) getBlockByEpochNumberInCache(chain consensus.ChainReader, estRound types.Round) *types.BlockInfo { + epochSwitchInCache := make([]*types.BlockInfo, 0) + for r := estRound; r < estRound+types.Round(x.config.Epoch); r++ { + info, ok := x.round2epochBlockInfo.Get(r) + if ok { + blockInfo := info.(*types.BlockInfo) + epochSwitchInCache = append(epochSwitchInCache, blockInfo) + } + } + if len(epochSwitchInCache) == 1 { + return epochSwitchInCache[0] + } else if len(epochSwitchInCache) == 0 { + return nil + } + // when multiple cache hits, need to find the one in main chain + for _, blockInfo := range epochSwitchInCache { + header := chain.GetHeaderByNumber(blockInfo.Number.Uint64()) + if header == nil { + continue + } + if header.Hash() == blockInfo.Hash { + return blockInfo + } + } + return nil +} + +func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64, start, end uint64) (*types.BlockInfo, error) { + // `end` must be larger than the target and `start` could be the target + for start < end { + header := chain.GetHeaderByNumber((start + end) / 2) + if header == nil { + return nil, errors.New("header nil in binary search") + } + isEpochSwitch, epochNum, err := x.IsEpochSwitch(header) + if err != nil { + return nil, err + } + if epochNum == targetEpochNum { + _, round, _, err := x.getExtraFields(header) + if err != nil { + return nil, err + } + if isEpochSwitch { + return &types.BlockInfo{ + Hash: header.Hash(), + Round: round, + Number: header.Number, + }, nil + } else { + end = header.Number.Uint64() + // trick to shorten the search + estStart := end - uint64(round)%x.config.Epoch + if start < estStart { + start = estStart + } + } + } else if epochNum > targetEpochNum { + end = header.Number.Uint64() + } else if epochNum < targetEpochNum { + // if start keeps the same, means no result and the search is over + nextStart := header.Number.Uint64() + if nextStart == start { + break + } + start = nextStart + } + } + return nil, errors.New("no epoch switch header in binary search (all rounds in this epoch are missed, which is very rare)") +} + +func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) { currentHeader := chain.CurrentHeader() epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash()) if err != nil { - return nil, nil, err + return nil, err } epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch - // since below function GetEpochSwitchInfoBetween(chain, start, end) return nil if start == end, we early return the result + // if current epoch is this epoch, we early return the result if targetEpochNum == epochNum { - return epochSwitchInfo.EpochSwitchBlockInfo, nil, nil + return epochSwitchInfo.EpochSwitchBlockInfo, nil } if targetEpochNum > epochNum { - return nil, nil, errors.New("input epoch number > current epoch number") + return nil, errors.New("input epoch number > current epoch number") } if targetEpochNum < x.config.V2.SwitchBlock.Uint64()/x.config.Epoch { - return nil, nil, errors.New("input epoch number < v2 begin epoch number") + return nil, errors.New("input epoch number < v2 begin epoch number") } + // the block's round should be in [estRound,estRound+Epoch-1] + estRound := types.Round((targetEpochNum - x.config.V2.SwitchBlock.Uint64()/x.config.Epoch) * x.config.Epoch) + // check the round2epochBlockInfo cache + blockInfo := x.getBlockByEpochNumberInCache(chain, estRound) + if blockInfo != nil { + return blockInfo, nil + } + // if cache miss, we do search epoch := big.NewInt(int64(x.config.Epoch)) estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum))) estBlockNum := new(big.Int).Sub(epochSwitchInfo.EpochSwitchBlockInfo.Number, estblockNumDiff) if estBlockNum.Cmp(x.config.V2.SwitchBlock) == -1 { estBlockNum.Set(x.config.V2.SwitchBlock) } - estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64()) - epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader) - if err != nil { - return nil, nil, err - } - for i, info := range epochSwitchInfos { - epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch - if epochNum == targetEpochNum { - if i < len(epochSwitchInfos)-1 { - nextEpoch := epochSwitchInfos[i+1].EpochSwitchBlockInfo - return info.EpochSwitchBlockInfo, nextEpoch, nil + // if the targrt is close, we search brute-forcily + closeEpochNum := uint64(2) + if closeEpochNum >= epochNum-targetEpochNum { + estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64()) + epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader) + if err != nil { + return nil, err + } + for _, info := range epochSwitchInfos { + epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch + if epochNum == targetEpochNum { + return info.EpochSwitchBlockInfo, nil } - return info.EpochSwitchBlockInfo, nil, nil } } - return nil, nil, errors.New("input epoch number not found (all rounds in this epoch are missed, which is very rare)") + // else, we use binary search + return x.binarySearchBlockByEpochNumber(chain, targetEpochNum, estBlockNum.Uint64(), epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()) } diff --git a/consensus/XDPoS/utils/constants.go b/consensus/XDPoS/utils/constants.go index c8df06cf46..6cef0034a0 100644 --- a/consensus/XDPoS/utils/constants.go +++ b/consensus/XDPoS/utils/constants.go @@ -17,6 +17,8 @@ var ( UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory + + InmemoryRound2Epochs = 65536 // Number of mapping of epoch switch blocks for quickly locating epoch switch block. One epoch ~ 0.5hours, so 65536 epochs ~ 3.7 years. And it uses ~ 10MB memory. ) const ( diff --git a/consensus/tests/engine_v2_tests/api_test.go b/consensus/tests/engine_v2_tests/api_test.go index 723ed33090..0c006d59cb 100644 --- a/consensus/tests/engine_v2_tests/api_test.go +++ b/consensus/tests/engine_v2_tests/api_test.go @@ -217,7 +217,7 @@ func TestGetBlockByEpochNumber(t *testing.T) { info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3) assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(1803)) - assert.Equal(t, info.EpochLastBlockNumber.Int64(), int64(1803)) + assert.Nil(t, info.EpochLastBlockNumber) assert.Equal(t, info.EpochRound, types.Round(largeRound)) assert.Nil(t, err)