From 99bbb60b9bda5703912e34381d0b4ae8cc30fbfc Mon Sep 17 00:00:00 2001 From: "liam.lai" Date: Mon, 23 Mar 2026 01:19:13 -0700 Subject: [PATCH 1/6] feat(consensus): add RPC endpoint to query signing tx count by epoch --- consensus/XDPoS/api.go | 72 ++++++++++++++++++++ consensus/XDPoS/engines/engine_v2/timeout.go | 4 +- internal/web3ext/web3ext.go | 6 ++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 3bace2c902b6..935d4bddeeb0 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -769,3 +769,75 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, } return api.GetBlockInfoByV2EpochNum(epochNumber) } + +// GetSigningTxCountByEpoch returns the signing transaction count for ALL masternodes +// (including non-active ones) in the epoch that ends at epochBlockNum. +// epochBlockNum must be an epoch-switch block number. +func (api *API) GetSigningTxCountByEpoch(epochBlockNum rpc.BlockNumber) (map[common.Address]uint64, error) { + header := api.chain.GetHeaderByNumber(uint64(epochBlockNum.Int64())) + if header == nil { + return nil, fmt.Errorf("block %d not found", epochBlockNum) + } + + isEpochSwitch, _, err := api.XDPoS.IsEpochSwitch(header) + if err != nil { + return nil, err + } + if !isEpochSwitch { + return nil, fmt.Errorf("block %d is not an epoch switch block", epochBlockNum) + } + + // Walk backwards from epochBlockNum-1 to the previous epoch switch block, + // collecting signing txs from every block. + mapBlkHash := map[uint64]common.Hash{} + // sigData maps blockHash -> list of signers who signed for that block + sigData := make(map[common.Hash][]common.Address) + + h := header + for i := header.Number.Uint64() - 1; ; i-- { + parentHash := h.ParentHash + h = api.chain.GetHeader(parentHash, i) + if h == nil { + return nil, fmt.Errorf("failed to get header at number %d hash %s", i, parentHash.Hex()) + } + + mapBlkHash[i] = h.Hash() + + signingTxs, ok := api.XDPoS.GetCachedSigningTxs(h.Hash()) + if !ok { + block := api.chain.GetBlock(h.Hash(), i) + if block != nil { + signingTxs = api.XDPoS.CacheSigningTxs(h.Hash(), block.Transactions()) + } + } + for _, tx := range signingTxs { + blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:]) + from := *tx.From() + sigData[blkHash] = append(sigData[blkHash], from) + } + + prevIsEpochSwitch, _, err := api.XDPoS.IsEpochSwitch(h) + if err != nil { + return nil, err + } + if prevIsEpochSwitch || i == 0 { + break + } + } + + // Count signings: for each block at MergeSignRange boundary, tally unique signers. + result := make(map[common.Address]uint64) + for blockNum, blkHash := range mapBlkHash { + if blockNum%common.MergeSignRange != 0 { + continue + } + seen := make(map[common.Address]bool) + for _, addr := range sigData[blkHash] { + if !seen[addr] { + seen[addr] = true + result[addr]++ + } + } + } + return result, nil +} diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go index 474831fb3fd7..394d5e1eb08f 100644 --- a/consensus/XDPoS/engines/engine_v2/timeout.go +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -124,7 +124,7 @@ func (x *XDPoS_v2) getTCEpochInfo(chain consensus.ChainReader, timeoutRound type Round: epochRound, Number: epochSwitchInfo.EpochSwitchBlockInfo.Number, } - log.Info("[getTCEpochInfo] Init epochInfo", "number", epochBlockInfo.Number, "round", epochRound, "tcRound", timeoutRound, "tcEpoch", tempTCEpoch) + log.Debug("[getTCEpochInfo] Init epochInfo", "number", epochBlockInfo.Number, "round", epochRound, "tcRound", timeoutRound, "tcEpoch", tempTCEpoch) for epochBlockInfo.Round > timeoutRound && tempTCEpoch > 0 { tempTCEpoch-- epochBlockInfo, err = x.GetBlockByEpochNumber(chain, tempTCEpoch) @@ -135,7 +135,7 @@ func (x *XDPoS_v2) getTCEpochInfo(chain consensus.ChainReader, timeoutRound type log.Debug("[getTCEpochInfo] Loop to get right epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutRound, "tcEpoch", tempTCEpoch) } tcEpoch := tempTCEpoch - log.Info("[getTCEpochInfo] Final TC epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutRound, "tcEpoch", tcEpoch) + log.Debug("[getTCEpochInfo] Final TC epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutRound, "tcEpoch", tcEpoch) epochInfo, err := x.getEpochSwitchInfo(chain, nil, epochBlockInfo.Hash) if err != nil { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 51d79f531d52..c1961d03dab1 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -142,6 +142,12 @@ web3._extend({ params: 3, inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter] }), + new web3._extend.Method({ + name: 'getSigningTxCountByEpoch', + call: 'XDPoS_getSigningTxCountByEpoch', + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + }), ], properties: [ new web3._extend.Property({ From a0c9da0b0d8f3cb9a6fe8743ea8de48c10234034 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Mon, 23 Mar 2026 23:38:05 -0700 Subject: [PATCH 2/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- consensus/XDPoS/api.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 935d4bddeeb0..e0ab42fc4b09 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -771,8 +771,10 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, } // GetSigningTxCountByEpoch returns the signing transaction count for ALL masternodes -// (including non-active ones) in the epoch that ends at epochBlockNum. -// epochBlockNum must be an epoch-switch block number. +// (including non-active ones) in the epoch that immediately precedes the epoch +// that starts at epochBlockNum. In other words, it walks blocks from +// epochBlockNum-1 backwards to the previous epoch-switch block (inclusive). +// epochBlockNum must be an epoch-switch block number that marks the start of an epoch. func (api *API) GetSigningTxCountByEpoch(epochBlockNum rpc.BlockNumber) (map[common.Address]uint64, error) { header := api.chain.GetHeaderByNumber(uint64(epochBlockNum.Int64())) if header == nil { From d8d5811a3b1bd71bd30864393df64c6931c95844 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Mon, 23 Mar 2026 23:39:08 -0700 Subject: [PATCH 3/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- consensus/XDPoS/api.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index e0ab42fc4b09..9c82d5e398af 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -776,7 +776,10 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, // epochBlockNum-1 backwards to the previous epoch-switch block (inclusive). // epochBlockNum must be an epoch-switch block number that marks the start of an epoch. func (api *API) GetSigningTxCountByEpoch(epochBlockNum rpc.BlockNumber) (map[common.Address]uint64, error) { - header := api.chain.GetHeaderByNumber(uint64(epochBlockNum.Int64())) + header, err := api.getHeaderFromApiBlockNum(epochBlockNum) + if err != nil { + return nil, err + } if header == nil { return nil, fmt.Errorf("block %d not found", epochBlockNum) } From 01cce2f20d6f5c95c7c10b26b0df035abac6b168 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Mon, 23 Mar 2026 23:42:53 -0700 Subject: [PATCH 4/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- consensus/XDPoS/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 9c82d5e398af..ba57f78242b4 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -812,7 +812,7 @@ func (api *API) GetSigningTxCountByEpoch(epochBlockNum rpc.BlockNumber) (map[com if !ok { block := api.chain.GetBlock(h.Hash(), i) if block != nil { - signingTxs = api.XDPoS.CacheSigningTxs(h.Hash(), block.Transactions()) + signingTxs = api.XDPoS.CacheNoneTIPSigningTxs(h.Hash(), block.Transactions()) } } for _, tx := range signingTxs { From dd726bb9010cd43a700f2ad9569118ca95e9dc22 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Mon, 23 Mar 2026 23:43:25 -0700 Subject: [PATCH 5/6] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- consensus/XDPoS/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index ba57f78242b4..d21bda58efc9 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -811,9 +811,10 @@ func (api *API) GetSigningTxCountByEpoch(epochBlockNum rpc.BlockNumber) (map[com signingTxs, ok := api.XDPoS.GetCachedSigningTxs(h.Hash()) if !ok { block := api.chain.GetBlock(h.Hash(), i) - if block != nil { - signingTxs = api.XDPoS.CacheNoneTIPSigningTxs(h.Hash(), block.Transactions()) + if block == nil { + return nil, fmt.Errorf("failed to get block at number %d hash %s", i, h.Hash().Hex()) } + signingTxs = api.XDPoS.CacheSigningTxs(h.Hash(), block.Transactions()) } for _, tx := range signingTxs { blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:]) From eaf849a7568b607661cce1ba66a0820dd6e60115 Mon Sep 17 00:00:00 2001 From: "liam.lai" Date: Tue, 24 Mar 2026 01:09:57 -0700 Subject: [PATCH 6/6] fix build issue --- consensus/XDPoS/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index d21bda58efc9..ef07ea8a30ca 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -776,7 +776,7 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, // epochBlockNum-1 backwards to the previous epoch-switch block (inclusive). // epochBlockNum must be an epoch-switch block number that marks the start of an epoch. func (api *API) GetSigningTxCountByEpoch(epochBlockNum rpc.BlockNumber) (map[common.Address]uint64, error) { - header, err := api.getHeaderFromApiBlockNum(epochBlockNum) + header, err := api.getHeaderFromApiBlockNum(&epochBlockNum) if err != nil { return nil, err }