From bd3e2600fdfebdbdfc1762e63d90641c3b931693 Mon Sep 17 00:00:00 2001 From: Daniel Liu Date: Mon, 18 Dec 2023 10:49:40 +0800 Subject: [PATCH 1/9] eth/downloader: make Broadcast run after Wait (#264) --- eth/downloader/queue.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index fca2e54875..2a093f075b 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -146,6 +146,9 @@ func (q *queue) Reset() { // Close marks the end of the sync, unblocking WaitResults. // It may be called even if the queue is already closed. func (q *queue) Close() { + q.lock.Lock() + defer q.lock.Unlock() + q.closed = true q.active.Broadcast() } From e30d909643db94f7bb84cfed477285a486c52f0c Mon Sep 17 00:00:00 2001 From: Banana-J Date: Tue, 19 Dec 2023 19:02:32 +1100 Subject: [PATCH 2/9] Add config into network information API (#381) --- consensus/XDPoS/api.go | 4 ++++ consensus/tests/api_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 consensus/tests/api_test.go diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 0228c5ce14..768e8b9a5c 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -23,6 +23,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rpc" ) @@ -53,6 +54,7 @@ type NetworkInformation struct { XDCXListingAddress common.Address XDCZAddress common.Address LendingAddress common.Address + ConsensusConfigs params.XDPoSConfig } type SignerTypes struct { @@ -278,6 +280,8 @@ func (api *API) NetworkInformation() NetworkInformation { info.XDCXListingAddress = common.XDCXListingSMC info.XDCZAddress = common.TRC21IssuerSMC } + info.ConsensusConfigs = *api.XDPoS.config + return info } diff --git a/consensus/tests/api_test.go b/consensus/tests/api_test.go new file mode 100644 index 0000000000..3d6318151c --- /dev/null +++ b/consensus/tests/api_test.go @@ -0,0 +1,35 @@ +package tests + +import ( + "math/big" + "testing" + + "github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/stretchr/testify/assert" +) + +var ( + voterKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee04aefe388d1e14474d32c45c72ce7b7a") + voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434 +) + +func TestConfigApi(t *testing.T) { + + bc := backends.NewXDCSimulatedBackend(core.GenesisAlloc{ + voterAddr: {Balance: new(big.Int).SetUint64(10000000000)}, + }, 10000000, params.TestXDPoSMockChainConfig) + + engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) + + info := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).NetworkInformation() + + assert.Equal(t, info.NetworkId, big.NewInt(1337)) + assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.MaxMasternodes, 18) + assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.CertThreshold, 0.667) + assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.MinePeriod, 2) + assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.TimeoutSyncThreshold, 2) +} From c7a42fd7c94196f3013ca370be59d30e304dd024 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 27 Dec 2023 22:14:13 +1100 Subject: [PATCH 3/9] resolve sync issue by passing right round number (#384) --- consensus/XDPoS/engines/engine_v2/engine.go | 8 ++-- consensus/XDPoS/engines/engine_v2/mining.go | 2 +- .../XDPoS/engines/engine_v2/verifyHeader.go | 2 +- .../engine_v2_tests/verify_header_test.go | 44 +++++++++++++++++++ eth/downloader/queue.go | 1 + eth/hooks/engine_v2_hooks.go | 4 +- params/version.go | 2 +- 7 files changed, 54 insertions(+), 9 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 62a34d1a09..3e7eabd8df 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -336,7 +336,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er return err } if isEpochSwitchBlock { - masterNodes, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash) + masterNodes, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash, currentRound) if err != nil { return err } @@ -997,9 +997,9 @@ func (x *XDPoS_v2) GetStandbynodes(chain consensus.ChainReader, header *types.He } // Calculate masternodes for a block number and parent hash. In V2, truncating candidates[:MaxMasternodes] is done in this function. -func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash) ([]common.Address, []common.Address, error) { +func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash, round types.Round) ([]common.Address, []common.Address, error) { // using new max masterndoes - maxMasternodes := x.config.V2.Config(uint64(x.currentRound)).MaxMasternodes + maxMasternodes := x.config.V2.Config(uint64(round)).MaxMasternodes snap, err := x.getSnapshot(chain, blockNum.Uint64(), false) if err != nil { log.Error("[calcMasternodes] Adaptor v2 getSnapshot has error", "err", err) @@ -1081,7 +1081,7 @@ func (x *XDPoS_v2) allowedToSend(chain consensus.ChainReader, blockHeader *types for _, mn := range masterNodes { log.Debug("[allowedToSend] Master node list", "masterNodeAddress", mn.Hash()) } - log.Info("[allowedToSend] Not in the Masternode list, not suppose to send message", "sendType", sendType, "MyAddress", signer.Hex()) + log.Debug("[allowedToSend] Not in the Masternode list, not suppose to send message", "sendType", sendType, "MyAddress", signer.Hex()) return false } diff --git a/consensus/XDPoS/engines/engine_v2/mining.go b/consensus/XDPoS/engines/engine_v2/mining.go index 72722de75e..69c861acc7 100644 --- a/consensus/XDPoS/engines/engine_v2/mining.go +++ b/consensus/XDPoS/engines/engine_v2/mining.go @@ -25,7 +25,7 @@ func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, pare } var masterNodes []common.Address if isEpochSwitch { - masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash()) + masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash(), round) if err != nil { log.Error("[yourturn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number) return false, err diff --git a/consensus/XDPoS/engines/engine_v2/verifyHeader.go b/consensus/XDPoS/engines/engine_v2/verifyHeader.go index 1655a34d94..a759d7455f 100644 --- a/consensus/XDPoS/engines/engine_v2/verifyHeader.go +++ b/consensus/XDPoS/engines/engine_v2/verifyHeader.go @@ -115,7 +115,7 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade return utils.ErrInvalidCheckpointSigners } - localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash) + localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash, round) masterNodes = localMasterNodes if err != nil { log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash()) diff --git a/consensus/tests/engine_v2_tests/verify_header_test.go b/consensus/tests/engine_v2_tests/verify_header_test.go index a1936d44a1..a8b2eaec30 100644 --- a/consensus/tests/engine_v2_tests/verify_header_test.go +++ b/consensus/tests/engine_v2_tests/verify_header_test.go @@ -260,6 +260,50 @@ func TestConfigSwitchOnDifferentCertThreshold(t *testing.T) { assert.Equal(t, utils.ErrValidatorNotWithinMasternodes, err) } +/* + 1. Insert 20 masternode before gap block + 2. Prepare 20 masternode block header with round 9000 + 3. verify this header while node is on round 899, + This is to simulate node is syncing from remote during config switch +*/ +func TestConfigSwitchOnDifferentMasternodeCount(t *testing.T) { + b, err := json.Marshal(params.TestXDPoSMockChainConfig) + assert.Nil(t, err) + configString := string(b) + + var config params.ChainConfig + err = json.Unmarshal([]byte(configString), &config) + assert.Nil(t, err) + // Enable verify + config.XDPoS.V2.SkipV2Validation = false + // Block 901 is the first v2 block with round of 1 + blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*2, &config, nil) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + x := adaptor.EngineV2 + + // Generate round 900 header, num 1800 + header1800 := blockchain.GetBlockByNumber(1800).Header() + + snap, err := x.GetSnapshot(blockchain, currentBlock.Header()) + assert.Nil(t, err) + assert.Equal(t, len(snap.NextEpochMasterNodes), 20) + header1800.Validators = []byte{} + for i := 0; i < 20; i++ { + header1800.Validators = append(header1800.Validators, snap.NextEpochMasterNodes[i].Bytes()...) + } + + round, err := x.GetRoundNumber(header1800) + assert.Nil(t, err) + assert.Equal(t, round, types.Round(900)) + + adaptor.EngineV2.SetNewRoundFaker(blockchain, 899, false) + + err = adaptor.VerifyHeader(blockchain, header1800, true) + + // error ErrValidatorNotWithinMasternodes means verifyQC is passed and move to next verification process + assert.Equal(t, utils.ErrValidatorNotWithinMasternodes, err) +} + func TestConfigSwitchOnDifferentMindPeriod(t *testing.T) { b, err := json.Marshal(params.TestXDPoSMockChainConfig) assert.Nil(t, err) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 2a093f075b..a67f12c477 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -510,6 +510,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common // If we're the first to request this task, initialise the result container index := int(header.Number.Int64() - int64(q.resultOffset)) if index >= len(q.resultCache) || index < 0 { + log.Error("index allocation went beyond available resultCache space", "index", index, "len.resultCache", len(q.resultCache), "blockNum", header.Number.Int64(), "resultOffset", q.resultOffset) common.Report("index allocation went beyond available resultCache space") return nil, false, errInvalidChain } diff --git a/eth/hooks/engine_v2_hooks.go b/eth/hooks/engine_v2_hooks.go index 599a3af98d..4047014a4d 100644 --- a/eth/hooks/engine_v2_hooks.go +++ b/eth/hooks/engine_v2_hooks.go @@ -37,9 +37,9 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf break } log.Info("[V2 Hook Penalty] parentHeader is nil, wait block to be writen in disk", "parentNumber", parentNumber) - time.Sleep(200 * time.Millisecond) // 0.2s + time.Sleep(time.Second) // 1s - if timeout > 50 { // wait over 10s + if timeout > 30 { // wait over 30s log.Error("[V2 Hook Penalty] parentHeader is nil, wait too long not writen in to disk", "parentNumber", parentNumber) return []common.Address{}, fmt.Errorf("parentHeader is nil") } diff --git a/params/version.go b/params/version.go index b31c431d86..6bb11eab6f 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 = 0 // 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 = "beta1" // Version metadata to append to the version string ) From e28b550a242633e4393359d0ec0effc78e4a4821 Mon Sep 17 00:00:00 2001 From: Jianrong Date: Thu, 21 Dec 2023 23:34:47 +1100 Subject: [PATCH 4/9] Add a new API to help debug when there are missed rounds --- consensus/XDPoS/XDPoS.go | 10 +++++ consensus/XDPoS/api.go | 7 +++ consensus/XDPoS/engines/engine_v2/engine.go | 2 +- consensus/XDPoS/engines/engine_v2/mining.go | 1 + consensus/XDPoS/engines/engine_v2/utils.go | 49 +++++++++++++++++++++ consensus/XDPoS/utils/types.go | 12 +++++ internal/web3ext/web3ext.go | 5 +++ 7 files changed, 85 insertions(+), 1 deletion(-) diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index d03f324d4c..c06f3c53ff 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -433,6 +433,16 @@ func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum } } +func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, hash common.Hash) (*utils.PublicApiMissedRoundsMetadata, error) { + header := chain.GetHeaderByHash(hash) + switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) { + case params.ConsensusEngineVersion2: + return x.EngineV2.CalculateMissingRounds(chain, header) + default: // Default "v1" + return nil, fmt.Errorf("Not supported in the v1 consensus") + } +} + // Same DB across all consensus engines func (x *XDPoS) GetDb() ethdb.Database { return x.db diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 768e8b9a5c..aec4fc4f6c 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -285,6 +285,13 @@ func (api *API) NetworkInformation() NetworkInformation { return info } +/* +An API exclusively for V2 consensus, designed to assist in troubleshooting miners by identifying who mined during their allocated term. +*/ +func (api *API) GetMissiedRoundsInEpochByBlockHash(hash common.Hash) (*utils.PublicApiMissedRoundsMetadata, error) { + return api.XDPoS.CalculateMissingRounds(api.chain, hash) +} + func calculateSigners(message map[string]SignerTypes, pool map[string]map[common.Hash]utils.PoolObj, masternodes []common.Address) { for name, objs := range pool { var currentSigners []common.Address diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 3e7eabd8df..cedeaa2f0b 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -360,7 +360,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er } if header.Coinbase != signer { - log.Error("[Prepare] The mined blocker header coinbase address mismatch with waller address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex()) + log.Error("[Prepare] The mined blocker header coinbase address mismatch with wallet address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex()) return consensus.ErrCoinbaseMismatch } diff --git a/consensus/XDPoS/engines/engine_v2/mining.go b/consensus/XDPoS/engines/engine_v2/mining.go index 69c861acc7..546de844a8 100644 --- a/consensus/XDPoS/engines/engine_v2/mining.go +++ b/consensus/XDPoS/engines/engine_v2/mining.go @@ -50,6 +50,7 @@ func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, pare return false, nil } + // TODO: USE BELOW FOR THE NEW API leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) x.whosTurn = masterNodes[leaderIndex] if x.whosTurn != signer { diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 1c3497ad60..395493f7e5 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -163,3 +163,52 @@ func (x *XDPoS_v2) GetSignersFromSnapshot(chain consensus.ChainReader, header *t snap, err := x.getSnapshot(chain, header.Number.Uint64(), false) return snap.NextEpochMasterNodes, err } + +func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) { + var missedRounds []utils.MissedRoundInfo + switchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash()) + if err != nil { + return nil, err + } + masternodes := switchInfo.Masternodes + + // Loop through from the epoch switch block to the current "header" block + nextHeader := header + for nextHeader.Number.Cmp(switchInfo.EpochSwitchBlockInfo.Number) > 0 { + parentHeader := chain.GetHeaderByHash(nextHeader.ParentHash) + parentRound, err := x.GetRoundNumber(parentHeader) + if err != nil { + return nil, err + } + currRound, err := x.GetRoundNumber(nextHeader) + if err != nil { + return nil, err + } + // This means there is a missing round incrementation when producing blocks + if parentRound+1 != currRound { + // We need to loop through between parentRound and the currRound to assess which miner did not mine + for i := parentRound; i < currRound; i++ { + leaderIndex := uint64(i) % x.config.Epoch % uint64(len(masternodes)) + whosTerm := masternodes[leaderIndex] + missedRounds = append( + missedRounds, + utils.MissedRoundInfo{ + Round: i, + Miner: whosTerm, + CurrentBlockHash: nextHeader.Hash(), + ParentBlockHash: parentHeader.Hash(), + }, + ) + } + } + // Assign the pointer to the next one + nextHeader = parentHeader + } + missedRoundsMetadata := &utils.PublicApiMissedRoundsMetadata{ + EpochRound: switchInfo.EpochSwitchBlockInfo.Round, + EpochBlockNumber: switchInfo.EpochSwitchBlockInfo.Number, + MissedRounds: missedRounds, + } + + return missedRoundsMetadata, nil +} diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 4f55973cfd..3b60688c36 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -57,3 +57,15 @@ type PublicApiSnapshot struct { Votes []*clique.Vote `json:"votes"` // List of votes cast in chronological order Tally map[common.Address]clique.Tally `json:"tally"` // Current vote tally to avoid recalculating } + +type MissedRoundInfo struct { + Round types.Round + Miner common.Address + CurrentBlockHash common.Hash + ParentBlockHash common.Hash +} +type PublicApiMissedRoundsMetadata struct { + EpochRound types.Round + EpochBlockNumber *big.Int + MissedRounds []MissedRoundInfo +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index f80b2b6e0a..e479fea342 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -156,6 +156,11 @@ web3._extend({ name: 'getLatestPoolStatus', call: 'XDPoS_getLatestPoolStatus' }), + new web3._extend.Method({ + name: 'GetMissiedRoundsInEpochByBlockHash', + call: 'XDPoS_GetMissiedRoundsInEpochByBlockHash', + params: 1 + }), ], properties: [ new web3._extend.Property({ From 6740545358bafd4a75e6dc5369b1e0c38396648e Mon Sep 17 00:00:00 2001 From: Jianrong Date: Fri, 22 Dec 2023 10:37:32 +1100 Subject: [PATCH 5/9] change the API to be block num based --- consensus/XDPoS/XDPoS.go | 3 +-- consensus/XDPoS/api.go | 28 ++++++++++++--------- consensus/XDPoS/engines/engine_v2/mining.go | 1 - consensus/XDPoS/engines/engine_v2/utils.go | 12 +++++---- consensus/XDPoS/utils/types.go | 2 ++ internal/web3ext/web3ext.go | 7 +++--- 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index c06f3c53ff..26cadc3555 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -433,8 +433,7 @@ func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum } } -func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, hash common.Hash) (*utils.PublicApiMissedRoundsMetadata, error) { - header := chain.GetHeaderByHash(hash) +func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) { switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) { case params.ConsensusEngineVersion2: return x.EngineV2.CalculateMissingRounds(chain, header) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index aec4fc4f6c..17f939faae 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -224,16 +224,7 @@ func (api *API) GetV2BlockByHeader(header *types.Header, uncle bool) *V2BlockInf } func (api *API) GetV2BlockByNumber(number *rpc.BlockNumber) *V2BlockInfo { - var header *types.Header - if number == nil || *number == rpc.LatestBlockNumber { - header = api.chain.CurrentHeader() - } else if *number == rpc.CommittedBlockNumber { - hash := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash - header = api.chain.GetHeaderByHash(hash) - } else { - header = api.chain.GetHeaderByNumber(uint64(number.Int64())) - } - + header := api.getHeaderFromApiBlockNum(number) if header == nil { return &V2BlockInfo{ Number: big.NewInt(number.Int64()), @@ -288,8 +279,21 @@ func (api *API) NetworkInformation() NetworkInformation { /* An API exclusively for V2 consensus, designed to assist in troubleshooting miners by identifying who mined during their allocated term. */ -func (api *API) GetMissiedRoundsInEpochByBlockHash(hash common.Hash) (*utils.PublicApiMissedRoundsMetadata, error) { - return api.XDPoS.CalculateMissingRounds(api.chain, hash) +func (api *API) GetMissiedRoundsInEpochByBlockNum(number *rpc.BlockNumber) (*utils.PublicApiMissedRoundsMetadata, error) { + return api.XDPoS.CalculateMissingRounds(api.chain, api.getHeaderFromApiBlockNum(number)) +} + +func (api *API) getHeaderFromApiBlockNum(number *rpc.BlockNumber) *types.Header { + var header *types.Header + if number == nil || *number == rpc.LatestBlockNumber { + header = api.chain.CurrentHeader() + } else if *number == rpc.CommittedBlockNumber { + hash := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash + header = api.chain.GetHeaderByHash(hash) + } else { + header = api.chain.GetHeaderByNumber(uint64(number.Int64())) + } + return header } func calculateSigners(message map[string]SignerTypes, pool map[string]map[common.Hash]utils.PoolObj, masternodes []common.Address) { diff --git a/consensus/XDPoS/engines/engine_v2/mining.go b/consensus/XDPoS/engines/engine_v2/mining.go index 546de844a8..69c861acc7 100644 --- a/consensus/XDPoS/engines/engine_v2/mining.go +++ b/consensus/XDPoS/engines/engine_v2/mining.go @@ -50,7 +50,6 @@ func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, pare return false, nil } - // TODO: USE BELOW FOR THE NEW API leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) x.whosTurn = masterNodes[leaderIndex] if x.whosTurn != signer { diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 395493f7e5..e54b2b59b0 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -184,19 +184,21 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t if err != nil { return nil, err } - // This means there is a missing round incrementation when producing blocks + // This indicates that an increment in the round number is missing during the block production process. if parentRound+1 != currRound { - // We need to loop through between parentRound and the currRound to assess which miner did not mine - for i := parentRound; i < currRound; i++ { + // We need to iterate from the parentRound to the currRound to determine which miner did not perform mining. + for i := parentRound + 1; i < currRound; i++ { leaderIndex := uint64(i) % x.config.Epoch % uint64(len(masternodes)) - whosTerm := masternodes[leaderIndex] + whosTurn := masternodes[leaderIndex] missedRounds = append( missedRounds, utils.MissedRoundInfo{ Round: i, - Miner: whosTerm, + Miner: whosTurn, CurrentBlockHash: nextHeader.Hash(), + CurrentBlockNum: nextHeader.Number, ParentBlockHash: parentHeader.Hash(), + ParentBlockNum: parentHeader.Number, }, ) } diff --git a/consensus/XDPoS/utils/types.go b/consensus/XDPoS/utils/types.go index 3b60688c36..4073fb522b 100644 --- a/consensus/XDPoS/utils/types.go +++ b/consensus/XDPoS/utils/types.go @@ -62,7 +62,9 @@ type MissedRoundInfo struct { Round types.Round Miner common.Address CurrentBlockHash common.Hash + CurrentBlockNum *big.Int ParentBlockHash common.Hash + ParentBlockNum *big.Int } type PublicApiMissedRoundsMetadata struct { EpochRound types.Round diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index e479fea342..e0b0018b78 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -157,9 +157,10 @@ web3._extend({ call: 'XDPoS_getLatestPoolStatus' }), new web3._extend.Method({ - name: 'GetMissiedRoundsInEpochByBlockHash', - call: 'XDPoS_GetMissiedRoundsInEpochByBlockHash', - params: 1 + name: 'GetMissiedRoundsInEpochByBlockNum', + call: 'XDPoS_GetMissiedRoundsInEpochByBlockNum', + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), ], properties: [ From 14f6b267c4293a3655cdfc0a410ca010b7f82a45 Mon Sep 17 00:00:00 2001 From: Jianrong Date: Thu, 28 Dec 2023 15:12:17 +1100 Subject: [PATCH 6/9] Make getMissiedRoundsInEpochByBlockNum API method lower case --- internal/web3ext/web3ext.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index e0b0018b78..8cbaf04e19 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -157,8 +157,8 @@ web3._extend({ call: 'XDPoS_getLatestPoolStatus' }), new web3._extend.Method({ - name: 'GetMissiedRoundsInEpochByBlockNum', - call: 'XDPoS_GetMissiedRoundsInEpochByBlockNum', + name: 'getMissiedRoundsInEpochByBlockNum', + call: 'XDPoS_getMissiedRoundsInEpochByBlockNum', params: 1, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), From e83c0a09540b5f7b2161707b37d9bc85131e5aa9 Mon Sep 17 00:00:00 2001 From: Jianrong Date: Thu, 28 Dec 2023 17:37:30 +1100 Subject: [PATCH 7/9] Add tests to cover the new GetMissiedRoundsInEpochByBlockNum API --- consensus/tests/api_test.go | 1 - consensus/tests/engine_v2_tests/api_test.go | 111 ++++++++++++++++++++ consensus/tests/engine_v2_tests/helper.go | 12 +++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 consensus/tests/engine_v2_tests/api_test.go diff --git a/consensus/tests/api_test.go b/consensus/tests/api_test.go index 3d6318151c..ed3e0897d0 100644 --- a/consensus/tests/api_test.go +++ b/consensus/tests/api_test.go @@ -18,7 +18,6 @@ var ( ) func TestConfigApi(t *testing.T) { - bc := backends.NewXDCSimulatedBackend(core.GenesisAlloc{ voterAddr: {Balance: new(big.Int).SetUint64(10000000000)}, }, 10000000, params.TestXDPoSMockChainConfig) diff --git a/consensus/tests/engine_v2_tests/api_test.go b/consensus/tests/engine_v2_tests/api_test.go new file mode 100644 index 0000000000..e626281fdc --- /dev/null +++ b/consensus/tests/engine_v2_tests/api_test.go @@ -0,0 +1,111 @@ +package engine_v2_tests + +import ( + "math/big" + "testing" + + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/rpc" + "github.com/stretchr/testify/assert" +) + +func TestGetMissiedRoundsInEpochByBlockNumOnlyForV2Consensus(t *testing.T) { + _, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) + + engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) + blockNum := rpc.BlockNumber(123) + + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + + assert.EqualError(t, err, "Not supported in the v1 consensus") + assert.Nil(t, data) +} + +func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) { + _, bc, cb, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) + + engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) + blockNum := rpc.BlockNumber(cb.NumberU64()) + + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + + assert.Nil(t, err) + assert.Equal(t, types.Round(900), data.EpochRound) + assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber) + assert.Equal(t, 0, len(data.MissedRounds)) + + blockNum = rpc.BlockNumber(1800) + + data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + + assert.Nil(t, err) + assert.Equal(t, types.Round(900), data.EpochRound) + assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber) + assert.Equal(t, 0, len(data.MissedRounds)) + + blockNum = rpc.BlockNumber(1801) + + data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + + assert.Nil(t, err) + assert.Equal(t, types.Round(900), data.EpochRound) + assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber) + assert.Equal(t, 0, len(data.MissedRounds)) +} + +func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2FistEpoch(t *testing.T) { + _, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) + + engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) + blockNum := rpc.BlockNumber(901) + + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + + assert.Nil(t, err) + assert.Equal(t, types.Round(1), data.EpochRound) + assert.Equal(t, big.NewInt(901), data.EpochBlockNumber) + assert.Equal(t, 0, len(data.MissedRounds)) +} + +func TestGetMissiedRoundsInEpochByBlockNum(t *testing.T) { + blockchain, bc, currentBlock, signer, signFn := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) + chainConfig := params.TestXDPoSMockChainConfig + engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) + blockCoinBase := signer.Hex() + + startingBlockNum := currentBlock.Number().Int64() + 1 + // Skipped the round + roundNumber := startingBlockNum - chainConfig.XDPoS.V2.SwitchBlock.Int64() + 2 + block := CreateBlock(blockchain, chainConfig, currentBlock, int(startingBlockNum), roundNumber, blockCoinBase, signer, signFn, nil, nil, "b345a8560bd51926803dd17677c9f0751193914a851a4ec13063d6bf50220b53") + err := blockchain.InsertBlock(block) + if err != nil { + t.Fatal(err) + } + + // Update Signer as there is no previous signer assigned + err = UpdateSigner(blockchain) + if err != nil { + t.Fatal(err) + } + + blockNum := rpc.BlockNumber(1803) + + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + + assert.Nil(t, err) + assert.Equal(t, types.Round(900), data.EpochRound) + assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber) + assert.Equal(t, 2, len(data.MissedRounds)) + assert.NotEmpty(t, data.MissedRounds[0].Miner) + assert.Equal(t, data.MissedRounds[0].Round, types.Round(903)) + assert.Equal(t, data.MissedRounds[0].CurrentBlockNum, big.NewInt(1803)) + assert.Equal(t, data.MissedRounds[0].ParentBlockNum, big.NewInt(1802)) + assert.NotEmpty(t, data.MissedRounds[1].Miner) + assert.Equal(t, data.MissedRounds[1].Round, types.Round(904)) + assert.Equal(t, data.MissedRounds[0].CurrentBlockNum, big.NewInt(1803)) + assert.Equal(t, data.MissedRounds[0].ParentBlockNum, big.NewInt(1802)) + + assert.NotEqual(t, data.MissedRounds[0].Miner, data.MissedRounds[1].Miner) +} diff --git a/consensus/tests/engine_v2_tests/helper.go b/consensus/tests/engine_v2_tests/helper.go index 854eebd124..9b86fbcda4 100644 --- a/consensus/tests/engine_v2_tests/helper.go +++ b/consensus/tests/engine_v2_tests/helper.go @@ -545,6 +545,18 @@ func PrepareXDCTestBlockChainWith128Candidates(t *testing.T, numOfBlocks int, ch if err != nil { t.Fatal(err) } + + // First v2 block + if (int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()) == 1 { + lastv1BlockNumber := block.Header().Number.Uint64() - 1 + checkpointBlockNumber := lastv1BlockNumber - lastv1BlockNumber%chainConfig.XDPoS.Epoch + checkpointHeader := blockchain.GetHeaderByNumber(checkpointBlockNumber) + err := engine.EngineV2.Initial(blockchain, checkpointHeader) + if err != nil { + panic(err) + } + } + currentBlock = block } From 8e1f71e86ee3b3cfad572318e8c884544722ab62 Mon Sep 17 00:00:00 2001 From: Jianrong Date: Thu, 28 Dec 2023 23:03:53 +1100 Subject: [PATCH 8/9] Fix the typo missied to missed --- consensus/XDPoS/api.go | 2 +- consensus/tests/engine_v2_tests/api_test.go | 20 ++++++++++---------- internal/web3ext/web3ext.go | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 17f939faae..a922e57f78 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -279,7 +279,7 @@ func (api *API) NetworkInformation() NetworkInformation { /* An API exclusively for V2 consensus, designed to assist in troubleshooting miners by identifying who mined during their allocated term. */ -func (api *API) GetMissiedRoundsInEpochByBlockNum(number *rpc.BlockNumber) (*utils.PublicApiMissedRoundsMetadata, error) { +func (api *API) GetMissedRoundsInEpochByBlockNum(number *rpc.BlockNumber) (*utils.PublicApiMissedRoundsMetadata, error) { return api.XDPoS.CalculateMissingRounds(api.chain, api.getHeaderFromApiBlockNum(number)) } diff --git a/consensus/tests/engine_v2_tests/api_test.go b/consensus/tests/engine_v2_tests/api_test.go index e626281fdc..fb99f6f879 100644 --- a/consensus/tests/engine_v2_tests/api_test.go +++ b/consensus/tests/engine_v2_tests/api_test.go @@ -11,25 +11,25 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetMissiedRoundsInEpochByBlockNumOnlyForV2Consensus(t *testing.T) { +func TestGetMissedRoundsInEpochByBlockNumOnlyForV2Consensus(t *testing.T) { _, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) blockNum := rpc.BlockNumber(123) - data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum) assert.EqualError(t, err, "Not supported in the v1 consensus") assert.Nil(t, data) } -func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) { +func TestGetMissedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) { _, bc, cb, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) blockNum := rpc.BlockNumber(cb.NumberU64()) - data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum) assert.Nil(t, err) assert.Equal(t, types.Round(900), data.EpochRound) @@ -38,7 +38,7 @@ func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) { blockNum = rpc.BlockNumber(1800) - data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum) assert.Nil(t, err) assert.Equal(t, types.Round(900), data.EpochRound) @@ -47,7 +47,7 @@ func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) { blockNum = rpc.BlockNumber(1801) - data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum) assert.Nil(t, err) assert.Equal(t, types.Round(900), data.EpochRound) @@ -55,13 +55,13 @@ func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) { assert.Equal(t, 0, len(data.MissedRounds)) } -func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2FistEpoch(t *testing.T) { +func TestGetMissedRoundsInEpochByBlockNumReturnEmptyForV2FistEpoch(t *testing.T) { _, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) blockNum := rpc.BlockNumber(901) - data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum) assert.Nil(t, err) assert.Equal(t, types.Round(1), data.EpochRound) @@ -69,7 +69,7 @@ func TestGetMissiedRoundsInEpochByBlockNumReturnEmptyForV2FistEpoch(t *testing.T assert.Equal(t, 0, len(data.MissedRounds)) } -func TestGetMissiedRoundsInEpochByBlockNum(t *testing.T) { +func TestGetMissedRoundsInEpochByBlockNum(t *testing.T) { blockchain, bc, currentBlock, signer, signFn := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig) chainConfig := params.TestXDPoSMockChainConfig engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS) @@ -92,7 +92,7 @@ func TestGetMissiedRoundsInEpochByBlockNum(t *testing.T) { blockNum := rpc.BlockNumber(1803) - data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissiedRoundsInEpochByBlockNum(&blockNum) + data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum) assert.Nil(t, err) assert.Equal(t, types.Round(900), data.EpochRound) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 8cbaf04e19..c07cf978c8 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -157,8 +157,8 @@ web3._extend({ call: 'XDPoS_getLatestPoolStatus' }), new web3._extend.Method({ - name: 'getMissiedRoundsInEpochByBlockNum', - call: 'XDPoS_getMissiedRoundsInEpochByBlockNum', + name: 'getMissedRoundsInEpochByBlockNum', + call: 'XDPoS_getMissedRoundsInEpochByBlockNum', params: 1, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), From 4446931e9eba91aee3ee65e7e1665cde21de14c2 Mon Sep 17 00:00:00 2001 From: Liam Lai Date: Fri, 29 Dec 2023 22:49:52 +1100 Subject: [PATCH 9/9] change vote log level --- consensus/XDPoS/engines/engine_v2/vote.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/vote.go b/consensus/XDPoS/engines/engine_v2/vote.go index 2f759bb728..dd4680ab0e 100644 --- a/consensus/XDPoS/engines/engine_v2/vote.go +++ b/consensus/XDPoS/engines/engine_v2/vote.go @@ -193,7 +193,7 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, poole func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *types.BlockInfo, quorumCert *types.QuorumCert) (bool, error) { // Make sure this node has not voted for this round. if x.currentRound <= x.highestVotedRound { - log.Warn("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound) + log.Info("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound) return false, nil } /* @@ -203,7 +203,7 @@ func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, bloc header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round */ if blockInfo.Round != x.currentRound { - log.Warn("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round) + log.Info("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round) return false, nil } // XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule