diff --git a/Cargo.lock b/Cargo.lock index d517d8d08d..2f903a7fbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1508,6 +1508,7 @@ dependencies = [ "ckb-hash", "ckb-jsonrpc-types", "ckb-logger", + "ckb-metrics", "ckb-network", "ckb-reward-calculator", "ckb-snapshot", diff --git a/Makefile b/Makefile index 13f5f15557..32b03bbc36 100644 --- a/Makefile +++ b/Makefile @@ -180,6 +180,13 @@ clippy: setup-ckb-test ## Run linter to examine Rust source codes. cargo clippy ${VERBOSE} --all --all-targets --features ${ALL_FEATURES} -- ${CLIPPY_OPTS} -D missing_docs cd test && cargo clippy ${VERBOSE} --all --all-targets --all-features -- ${CLIPPY_OPTS} +.PHONY: bless +bless: setup-ckb-test + cargo clippy --fix --allow-dirty ${VERBOSE} --all --all-targets --features ${ALL_FEATURES} -- ${CLIPPY_OPTS} -D missing_docs + cd test && cargo clippy --fix --allow-dirty ${VERBOSE} --all --all-targets --all-features -- ${CLIPPY_OPTS} + cargo fmt ${VERBOSE} --all + cd test && cargo fmt ${VERBOSE} --all + .PHONY: security-audit security-audit: ## Use cargo-deny to audit Cargo.lock for crates with security vulnerabilities. cargo deny check --hide-inclusion-graph --show-stats advisories sources diff --git a/rpc/README.md b/rpc/README.md index 2bca859ea4..ef19194fcd 100644 --- a/rpc/README.md +++ b/rpc/README.md @@ -92,6 +92,7 @@ The crate `ckb-rpc`'s minimum supported rustc version is 1.71.1. * [Method `tx_pool_info`](#method-tx_pool_info) * [Method `clear_tx_pool`](#method-clear_tx_pool) * [Method `get_raw_tx_pool`](#method-get_raw_tx_pool) + * [Method `get_pool_tx_detail_info`](#method-get_pool_tx_detail_info) * [Method `tx_pool_ready`](#method-tx_pool_ready) * [Module Stats](#module-stats) * [Method `get_blockchain_info`](#method-get_blockchain_info) @@ -163,6 +164,7 @@ The crate `ckb-rpc`'s minimum supported rustc version is 1.71.1. * [Type `PeerSyncState`](#type-peersyncstate) * [Type `PoolTransactionEntry`](#type-pooltransactionentry) * [Type `PoolTransactionReject`](#type-pooltransactionreject) + * [Type `PoolTxDetailInfo`](#type-pooltxdetailinfo) * [Type `ProposalShortId`](#type-proposalshortid) * [Type `ProposalWindow`](#type-proposalwindow) * [Type `Ratio`](#type-ratio) @@ -4659,6 +4661,55 @@ Response ``` +#### Method `get_pool_tx_detail_info` +* `get_pool_tx_detail_info(tx_hash)` + * `tx_hash`: [`H256`](#type-h256) +* result: [`PoolTxDetailInfo`](#type-pooltxdetailinfo) + +Query and returns the details of a transaction in the pool, only for trouble shooting + +###### Params + +* `tx_hash` - Hash of a transaction + +###### Examples + +Request + + +``` +{ + "id": 42, + "jsonrpc": "2.0", + "method": "get_pool_tx_detail_info", + "params": [ + "0xa0ef4eb5f4ceeb08a4c8524d84c5da95dce2f608e0ca2ec8091191b0f330c6e3" + ] +} +``` + + +Response + + +``` +{ + "jsonrpc": "2.0", + "result": { + "ancestors_count": "0x0", + "descendants_count": "0x0", + "entry_status": "pending", + "pending_count": "0x1", + "proposed_count": "0x0", + "rank_in_pending": "0x1", + "score_sortkey": "fee: 0x16923F7DCF, ancestors_fee: 0x16923F7DCF, weight: 0x112, ancestors_weight: 0x112", + "timestamp": "0x18aa1baa54c" + }, + "id": 42 +} +``` + + #### Method `tx_pool_ready` * `tx_pool_ready()` * result: `boolean` @@ -6506,6 +6557,31 @@ Different reject types: * `RBFRejected`: RBF rejected +### Type `PoolTxDetailInfo` + +A Tx details info in tx-pool. + +#### Fields + +`PoolTxDetailInfo` is a JSON object with the following fields. + +* `timestamp`: [`Uint64`](#type-uint64) - The time added into tx-pool + +* `entry_status`: `string` - The detailed status in tx-pool, `pending`, `gap`, `proposed` + +* `rank_in_pending`: [`Uint64`](#type-uint64) - The rank in pending, starting from 0 + +* `pending_count`: [`Uint64`](#type-uint64) - The pending(`pending` and `gap`) count + +* `proposed_count`: [`Uint64`](#type-uint64) - The proposed count + +* `descendants_count`: [`Uint64`](#type-uint64) - The descendants count of tx + +* `ancestors_count`: [`Uint64`](#type-uint64) - The ancestors count of tx + +* `score_sortkey`: `string` - The score key details, useful to debug + + ### Type `ProposalShortId` The 10-byte fixed-length binary encoded as a 0x-prefixed hex string in JSON. diff --git a/rpc/src/module/pool.rs b/rpc/src/module/pool.rs index 8f30f9b571..9fb1c0555b 100644 --- a/rpc/src/module/pool.rs +++ b/rpc/src/module/pool.rs @@ -1,7 +1,9 @@ use crate::error::RPCError; use ckb_chain_spec::consensus::Consensus; use ckb_constant::hardfork::{mainnet, testnet}; -use ckb_jsonrpc_types::{OutputsValidator, RawTxPool, Script, Transaction, TxPoolInfo}; +use ckb_jsonrpc_types::{ + OutputsValidator, PoolTxDetailInfo, RawTxPool, Script, Transaction, TxPoolInfo, +}; use ckb_logger::error; use ckb_shared::shared::Shared; use ckb_types::{core, packed, prelude::*, H256}; @@ -253,6 +255,47 @@ pub trait PoolRpc { #[rpc(name = "get_raw_tx_pool")] fn get_raw_tx_pool(&self, verbose: Option) -> Result; + /// Query and returns the details of a transaction in the pool, only for trouble shooting + /// ## Params + /// + /// * `tx_hash` - Hash of a transaction + /// + /// ## Examples + /// + /// Request + /// + /// ```json + /// { + /// "id": 42, + /// "jsonrpc": "2.0", + /// "method": "get_pool_tx_detail_info", + /// "params": [ + /// "0xa0ef4eb5f4ceeb08a4c8524d84c5da95dce2f608e0ca2ec8091191b0f330c6e3" + /// ] + /// } + /// ``` + /// + /// Response + /// + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "result": { + /// "ancestors_count": "0x0", + /// "descendants_count": "0x0", + /// "entry_status": "pending", + /// "pending_count": "0x1", + /// "proposed_count": "0x0", + /// "rank_in_pending": "0x1", + /// "score_sortkey": "fee: 0x16923F7DCF, ancestors_fee: 0x16923F7DCF, weight: 0x112, ancestors_weight: 0x112", + /// "timestamp": "0x18aa1baa54c" + /// }, + /// "id": 42 + /// } + /// ``` + #[rpc(name = "get_pool_tx_detail_info")] + fn get_pool_tx_detail_info(&self, tx_hash: H256) -> Result; + /// Returns whether tx-pool service is started, ready for request. /// /// ## Examples @@ -482,6 +525,14 @@ impl PoolRpc for PoolRpcImpl { }; Ok(raw) } + + fn get_pool_tx_detail_info(&self, tx_hash: H256) -> Result { + let tx_pool = self.shared.tx_pool_controller(); + let tx_detail = tx_pool + .get_tx_detail(tx_hash.pack()) + .map_err(|err| RPCError::custom(RPCError::CKBInternalError, err.to_string()))?; + Ok(tx_detail.into()) + } } pub(crate) struct WellKnownScriptsOnlyValidator<'a> { diff --git a/rpc/src/tests/examples.rs b/rpc/src/tests/examples.rs index 4be46c567d..574b0634e1 100644 --- a/rpc/src/tests/examples.rs +++ b/rpc/src/tests/examples.rs @@ -647,6 +647,9 @@ fn mock_rpc_response(example: &RpcTestExample, response: &mut RpcTestResponse) { "generate_block" => replace_rpc_response::(example, response), "process_block_without_verify" => replace_rpc_response::(example, response), "notify_transaction" => replace_rpc_response::(example, response), + "get_pool_tx_detail_info" => { + response.result["timestamp"] = example.response.result["timestamp"].clone() + } _ => {} } } diff --git a/test/src/main.rs b/test/src/main.rs index 446321a972..5943863507 100644 --- a/test/src/main.rs +++ b/test/src/main.rs @@ -463,6 +463,7 @@ fn all_specs() -> Vec> { Box::new(TxsRelayOrder), Box::new(SendTxChain), Box::new(SendTxChainRevOrder), + Box::new(TxPoolEntryStatus), Box::new(DifferentTxsWithSameInputWithOutRBF), Box::new(RbfEnable), Box::new(RbfBasic), diff --git a/test/src/node.rs b/test/src/node.rs index 65bee5851a..595ec6347b 100644 --- a/test/src/node.rs +++ b/test/src/node.rs @@ -6,8 +6,8 @@ use ckb_app_config::CKBAppConfig; use ckb_chain_spec::consensus::Consensus; use ckb_chain_spec::ChainSpec; use ckb_error::AnyError; -use ckb_jsonrpc_types::TxStatus; use ckb_jsonrpc_types::{BlockFilter, BlockTemplate, TxPoolInfo}; +use ckb_jsonrpc_types::{PoolTxDetailInfo, TxStatus}; use ckb_logger::{debug, error}; use ckb_resource::Resource; use ckb_types::{ @@ -424,6 +424,10 @@ impl Node { .expect("block filter exists") } + pub fn get_pool_tx_detail_info(&self, hash: Byte32) -> PoolTxDetailInfo { + self.rpc_client().get_pool_tx_detail_info(hash) + } + /// The states of chain and txpool are updated asynchronously. Which means that the chain has /// updated to the newest tip but txpool not. /// get_tip_tx_pool_info wait to ensure the txpool update to the newest tip as well. @@ -607,6 +611,11 @@ impl Node { assert_eq!(tx_pool_info.total_tx_cycles.value(), total_tx_cycles); } + pub fn assert_pool_entry_status(&self, hash: Byte32, expect_status: &str) { + let response = self.get_pool_tx_detail_info(hash); + assert_eq!(response.entry_status, expect_status); + } + pub fn assert_tx_pool_cycles(&self, total_tx_cycles: u64) { let tx_pool_info = self.get_tip_tx_pool_info(); assert_eq!(tx_pool_info.total_tx_cycles.value(), total_tx_cycles); diff --git a/test/src/rpc.rs b/test/src/rpc.rs index a4c7aa4697..d0a11ef1f3 100644 --- a/test/src/rpc.rs +++ b/test/src/rpc.rs @@ -7,8 +7,9 @@ use ckb_error::AnyError; use ckb_jsonrpc_types::{ Alert, BannedAddr, Block, BlockEconomicState, BlockFilter, BlockNumber, BlockTemplate, BlockView, Capacity, CellWithStatus, ChainInfo, EpochNumber, EpochView, EstimateCycles, - HeaderView, LocalNode, OutPoint, RawTxPool, RemoteNode, Timestamp, Transaction, - TransactionProof, TransactionWithStatusResponse, TxPoolInfo, Uint32, Uint64, Version, + HeaderView, LocalNode, OutPoint, PoolTxDetailInfo, RawTxPool, RemoteNode, Timestamp, + Transaction, TransactionProof, TransactionWithStatusResponse, TxPoolInfo, Uint32, Uint64, + Version, }; use ckb_types::core::{ BlockNumber as CoreBlockNumber, Capacity as CoreCapacity, EpochNumber as CoreEpochNumber, @@ -89,6 +90,12 @@ impl RpcClient { .expect("rpc call get_transaction") } + pub fn get_pool_tx_detail_info(&self, hash: Byte32) -> PoolTxDetailInfo { + self.inner + .get_pool_tx_detail_info(hash.unpack()) + .expect("rpc call get_transaction_tx_pool_details") + } + pub fn get_block_hash(&self, number: CoreBlockNumber) -> Option { self.inner .get_block_hash(number.into()) @@ -357,4 +364,5 @@ jsonrpc!(pub struct Inner { pub fn verify_transaction_proof(&self, tx_proof: TransactionProof) -> Vec; pub fn notify_transaction(&self, tx: Transaction) -> H256; pub fn tx_pool_ready(&self) -> bool; + pub fn get_pool_tx_detail_info(&self, _hash: H256) -> PoolTxDetailInfo; }); diff --git a/test/src/specs/rpc/get_pool.rs b/test/src/specs/rpc/get_pool.rs new file mode 100644 index 0000000000..39953c9158 --- /dev/null +++ b/test/src/specs/rpc/get_pool.rs @@ -0,0 +1,20 @@ +use crate::{Node, Spec}; + +pub struct TxPoolEntryStatus; + +impl Spec for TxPoolEntryStatus { + fn run(&self, nodes: &mut Vec) { + let node0 = &nodes[0]; + + node0.mine_until_out_bootstrap_period(); + node0.new_block_with_blocking(|template| template.number.value() != 13); + let tx_hash_0 = node0.generate_transaction(); + let tx = node0.new_transaction(tx_hash_0.clone()); + node0.rpc_client().send_transaction(tx.data().into()); + node0.assert_pool_entry_status(tx_hash_0.clone(), "pending"); + node0.mine(1); + node0.assert_pool_entry_status(tx_hash_0.clone(), "gap"); + node0.mine(1); + node0.assert_pool_entry_status(tx_hash_0, "proposed"); + } +} diff --git a/test/src/specs/rpc/mod.rs b/test/src/specs/rpc/mod.rs index df0665ecaa..c0bba906ee 100644 --- a/test/src/specs/rpc/mod.rs +++ b/test/src/specs/rpc/mod.rs @@ -1,6 +1,7 @@ mod get_block_median_time; mod get_block_template; mod get_blockchain_info; +mod get_pool; #[cfg(target_os = "linux")] mod set_ban; mod submit_block; @@ -10,6 +11,7 @@ mod truncate; pub use get_block_median_time::*; pub use get_block_template::*; pub use get_blockchain_info::*; +pub use get_pool::*; #[cfg(target_os = "linux")] pub use set_ban::*; pub use submit_block::*; diff --git a/tx-pool/Cargo.toml b/tx-pool/Cargo.toml index 4a08cced80..2a52d27484 100644 --- a/tx-pool/Cargo.toml +++ b/tx-pool/Cargo.toml @@ -16,6 +16,7 @@ ckb-logger = { path = "../util/logger", version = "= 0.113.0-pre" } ckb-verification = { path = "../verification", version = "= 0.113.0-pre" } ckb-systemtime = { path = "../util/systemtime", version = "= 0.113.0-pre" } lru = "0.7.1" + ckb-dao = { path = "../util/dao", version = "= 0.113.0-pre" } ckb-reward-calculator = { path = "../util/reward-calculator", version = "= 0.113.0-pre" } ckb-store = { path = "../store", version = "= 0.113.0-pre" } @@ -23,6 +24,7 @@ ckb-util = { path = "../util", version = "= 0.113.0-pre" } ckb-jsonrpc-types = { path = "../util/jsonrpc-types", version = "= 0.113.0-pre" } ckb-chain-spec = { path = "../spec", version = "= 0.113.0-pre" } ckb-snapshot = { path = "../util/snapshot", version = "= 0.113.0-pre" } +ckb-metrics = {path = "../util/metrics", version = "= 0.113.0-pre"} ckb-error = { path = "../error", version = "= 0.113.0-pre" } tokio = { version = "1", features = ["sync", "process"] } ckb-async-runtime = { path = "../util/runtime", version = "= 0.113.0-pre" } diff --git a/tx-pool/src/component/pool_map.rs b/tx-pool/src/component/pool_map.rs index dc573ab675..58b73b1815 100644 --- a/tx-pool/src/component/pool_map.rs +++ b/tx-pool/src/component/pool_map.rs @@ -1,12 +1,12 @@ //! Top-level Pool type, methods, and tests extern crate rustc_hash; extern crate slab; +use super::links::TxLinks; use crate::component::edges::Edges; use crate::component::links::{Relation, TxLinksMap}; use crate::component::sort_key::{AncestorsScoreSortKey, EvictKey}; use crate::error::Reject; use crate::TxEntry; - use ckb_logger::{debug, trace}; use ckb_types::core::error::OutPointError; use ckb_types::packed::OutPoint; @@ -19,8 +19,6 @@ use ckb_types::{ use multi_index_map::MultiIndexMap; use std::collections::HashSet; -use super::links::TxLinks; - type ConflictEntry = (TxEntry, Reject); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -30,6 +28,16 @@ pub enum Status { Proposed, } +impl ToString for Status { + fn to_string(&self) -> String { + match self { + Status::Pending => "pending".to_string(), + Status::Gap => "gap".to_string(), + Status::Proposed => "proposed".to_string(), + } + } +} + #[derive(Copy, Clone)] enum EntryOp { Add, @@ -107,6 +115,14 @@ impl PoolMap { self.add_entry(entry, Status::Proposed) } + pub(crate) fn get_max_update_time(&self) -> u64 { + self.entries + .iter() + .map(|(_, entry)| entry.inner.timestamp) + .max() + .unwrap_or(0) + } + pub(crate) fn get_by_id(&self, id: &ProposalShortId) -> Option<&PoolEntry> { self.entries.get_by_id(id) } @@ -176,6 +192,7 @@ impl PoolMap { self.insert_entry(&entry, status); self.record_entry_edges(&entry); self.record_entry_descendants(&entry); + self.track_entry_statics(); Ok(true) } @@ -186,6 +203,7 @@ impl PoolMap { e.status = status; }) .expect("unconsistent pool"); + self.track_entry_statics(); } pub(crate) fn remove_entry(&mut self, id: &ProposalShortId) -> Option { @@ -503,4 +521,21 @@ impl PoolMap { evict_key, }); } + + fn track_entry_statics(&self) { + if let Some(metrics) = ckb_metrics::handle() { + metrics + .ckb_tx_pool_entry + .pending + .set(self.entries.get_by_status(&Status::Pending).len() as i64); + metrics + .ckb_tx_pool_entry + .gap + .set(self.entries.get_by_status(&Status::Gap).len() as i64); + metrics + .ckb_tx_pool_entry + .proposed + .set(self.proposed_size() as i64); + } + } } diff --git a/tx-pool/src/component/sort_key.rs b/tx-pool/src/component/sort_key.rs index ceeab649bc..2c1797d9d7 100644 --- a/tx-pool/src/component/sort_key.rs +++ b/tx-pool/src/component/sort_key.rs @@ -47,6 +47,18 @@ impl Ord for AncestorsScoreSortKey { } } +impl ToString for AncestorsScoreSortKey { + fn to_string(&self) -> String { + format!( + "fee: {:#02X}, ancestors_fee: {:#02X}, weight: {:#02X}, ancestors_weight: {:#02X}", + self.fee.as_u64(), + self.ancestors_fee.as_u64(), + self.weight, + self.ancestors_weight + ) + } +} + /// First compare fee_rate, select the smallest fee_rate, /// and then select the latest timestamp, for eviction, /// the latest timestamp which also means that the fewer descendants may exist. diff --git a/tx-pool/src/pool.rs b/tx-pool/src/pool.rs index ab7b6863be..732cf996af 100644 --- a/tx-pool/src/pool.rs +++ b/tx-pool/src/pool.rs @@ -11,6 +11,7 @@ use ckb_app_config::TxPoolConfig; use ckb_logger::{debug, error, warn}; use ckb_snapshot::Snapshot; use ckb_store::ChainStore; +use ckb_types::core::tx_pool::PoolTxDetailInfo; use ckb_types::core::CapacityError; use ckb_types::{ core::{ @@ -626,6 +627,38 @@ impl TxPool { Ok(()) } + /// query the details of a transaction in the pool, only for trouble shooting + pub(crate) fn get_tx_detail(&self, id: &ProposalShortId) -> Option { + if let Some(entry) = self.pool_map.get_by_id(id) { + let ids = self.get_ids(); + let rank_in_pending = if entry.status == Status::Proposed { + 0 + } else { + let tx_hash = entry.inner.transaction().hash(); + ids.pending + .iter() + .enumerate() + .find(|(_, hash)| &tx_hash == *hash) + .map(|r| r.0) + .unwrap_or_default() + + 1 + }; + let res = PoolTxDetailInfo { + timestamp: entry.inner.timestamp, + entry_status: entry.status.to_string(), + pending_count: self.pool_map.pending_size(), + rank_in_pending, + proposed_count: ids.proposed.len(), + descendants_count: self.pool_map.calc_descendants(id).len(), + ancestors_count: self.pool_map.calc_ancestors(id).len(), + score_sortkey: entry.inner.as_score_key().to_string(), + }; + Some(res) + } else { + None + } + } + fn build_recent_reject(config: &TxPoolConfig) -> Option { if !config.recent_reject.as_os_str().is_empty() { let recent_reject_ttl = diff --git a/tx-pool/src/service.rs b/tx-pool/src/service.rs index 9df681f071..696fbeeee8 100644 --- a/tx-pool/src/service.rs +++ b/tx-pool/src/service.rs @@ -19,7 +19,7 @@ use ckb_logger::{debug, error}; use ckb_network::{NetworkController, PeerIndex}; use ckb_snapshot::Snapshot; use ckb_stop_handler::new_tokio_exit_rx; -use ckb_types::core::tx_pool::{TransactionWithStatus, TxStatus}; +use ckb_types::core::tx_pool::{PoolTxDetailInfo, TransactionWithStatus, TxStatus}; use ckb_types::{ core::{ tx_pool::{Reject, TxPoolEntryInfo, TxPoolIds, TxPoolInfo, TRANSACTION_SIZE_LIMIT}, @@ -76,9 +76,7 @@ type BlockTemplateArgs = (Option, Option, Option); pub(crate) type SubmitTxResult = Result<(), Reject>; type GetTxStatusResult = Result<(TxStatus, Option), AnyError>; - type GetTransactionWithStatusResult = Result; - type FetchTxsWithCyclesResult = Vec<(ProposalShortId, (TransactionView, Cycle))>; pub(crate) type ChainReorgArgs = ( @@ -105,6 +103,7 @@ pub(crate) enum Message { GetAllEntryInfo(Request<(), TxPoolEntryInfo>), GetAllIds(Request<(), TxPoolIds>), SavePool(Request<(), ()>), + GetPoolTxDetails(Request), // test #[cfg(feature = "internal")] @@ -304,6 +303,11 @@ impl TxPoolController { send_message!(self, GetAllIds, ()) } + /// query the details of a transaction in the pool + pub fn get_tx_detail(&self, tx_hash: Byte32) -> Result { + send_message!(self, GetPoolTxDetails, tx_hash) + } + /// Saves tx pool into disk. pub fn save_pool(&self) -> Result<(), AnyError> { info!("Please be patient, tx-pool are saving data into disk ..."); @@ -852,6 +856,19 @@ async fn process(mut service: TxPoolService, message: Message) { error!("responder send clear_pool failed {:?}", e) }; } + Message::GetPoolTxDetails(Request { + responder, + arguments: tx_hash, + }) => { + let tx_pool = service.tx_pool.read().await; + let id = ProposalShortId::from_tx_hash(&tx_hash); + let tx_details = tx_pool + .get_tx_detail(&id) + .unwrap_or(PoolTxDetailInfo::with_unknown()); + if let Err(e) = responder.send(tx_details) { + error!("responder send get_pool_tx_details failed {:?}", e) + }; + } Message::GetAllEntryInfo(Request { responder, .. }) => { let tx_pool = service.tx_pool.read().await; let info = tx_pool.get_all_entry_info(); @@ -918,7 +935,7 @@ impl TxPoolService { total_tx_cycles: tx_pool.total_tx_cycles, min_fee_rate: self.tx_pool_config.min_fee_rate, min_rbf_rate: self.tx_pool_config.min_rbf_rate, - last_txs_updated_at: 0, + last_txs_updated_at: tx_pool.pool_map.get_max_update_time(), tx_size_limit: TRANSACTION_SIZE_LIMIT, max_tx_pool_size: self.tx_pool_config.max_tx_pool_size as u64, } diff --git a/util/jsonrpc-types/src/lib.rs b/util/jsonrpc-types/src/lib.rs index 8836756b26..4944ca8636 100644 --- a/util/jsonrpc-types/src/lib.rs +++ b/util/jsonrpc-types/src/lib.rs @@ -44,8 +44,8 @@ pub use self::net::{ RemoteNodeProtocol, SyncState, }; pub use self::pool::{ - OutputsValidator, PoolTransactionEntry, PoolTransactionReject, RawTxPool, TxPoolEntries, - TxPoolEntry, TxPoolIds, TxPoolInfo, + OutputsValidator, PoolTransactionEntry, PoolTransactionReject, PoolTxDetailInfo, RawTxPool, + TxPoolEntries, TxPoolEntry, TxPoolIds, TxPoolInfo, }; pub use self::proposal_short_id::ProposalShortId; pub use self::subscription::Topic; diff --git a/util/jsonrpc-types/src/pool.rs b/util/jsonrpc-types/src/pool.rs index e8fea3b830..48806c7bc4 100644 --- a/util/jsonrpc-types/src/pool.rs +++ b/util/jsonrpc-types/src/pool.rs @@ -1,7 +1,8 @@ use crate::{BlockNumber, Capacity, Cycle, Timestamp, TransactionView, Uint64}; use ckb_types::core::service::PoolTransactionEntry as CorePoolTransactionEntry; use ckb_types::core::tx_pool::{ - Reject, TxEntryInfo, TxPoolEntryInfo, TxPoolIds as CoreTxPoolIds, TxPoolInfo as CoreTxPoolInfo, + PoolTxDetailInfo as CorePoolTxDetailInfo, Reject, TxEntryInfo, TxPoolEntryInfo, + TxPoolIds as CoreTxPoolIds, TxPoolInfo as CoreTxPoolInfo, }; use ckb_types::prelude::Unpack; use ckb_types::H256; @@ -214,6 +215,42 @@ pub enum RawTxPool { Verbose(TxPoolEntries), } +/// A Tx details info in tx-pool. +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] +pub struct PoolTxDetailInfo { + /// The time added into tx-pool + pub timestamp: Uint64, + /// The detailed status in tx-pool, `pending`, `gap`, `proposed` + pub entry_status: String, + /// The rank in pending, starting from 0 + pub rank_in_pending: Uint64, + /// The pending(`pending` and `gap`) count + pub pending_count: Uint64, + /// The proposed count + pub proposed_count: Uint64, + /// The descendants count of tx + pub descendants_count: Uint64, + /// The ancestors count of tx + pub ancestors_count: Uint64, + /// The score key details, useful to debug + pub score_sortkey: String, +} + +impl From for PoolTxDetailInfo { + fn from(info: CorePoolTxDetailInfo) -> Self { + Self { + timestamp: info.timestamp.into(), + entry_status: info.entry_status, + rank_in_pending: (info.rank_in_pending as u64).into(), + pending_count: (info.pending_count as u64).into(), + proposed_count: (info.proposed_count as u64).into(), + descendants_count: (info.descendants_count as u64).into(), + ancestors_count: (info.ancestors_count as u64).into(), + score_sortkey: info.score_sortkey, + } + } +} + /// TX reject message #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(tag = "type", content = "description")] diff --git a/util/metrics/src/lib.rs b/util/metrics/src/lib.rs index 96f55b4069..e67718827f 100644 --- a/util/metrics/src/lib.rs +++ b/util/metrics/src/lib.rs @@ -37,6 +37,15 @@ make_static_metric! { metadata, }, } + + // Struct for CKB tx-pool entry status statistics type label + struct CkbTxPoolEntryStatistics: IntGauge{ + "type" => { + pending, + gap, + proposed, + }, + } } pub struct Metrics { @@ -64,6 +73,8 @@ pub struct Metrics { pub ckb_sys_mem_process: CkbSysMemProcessStatistics, // GaugeVec for CKB system memory jemalloc statistics pub ckb_sys_mem_jemalloc: CkbSysMemJemallocStatistics, + // GaugeVec for CKB tx-pool tx entry status statistics + pub ckb_tx_pool_entry: CkbTxPoolEntryStatistics, /// Histogram for CKB network connections pub ckb_message_bytes: HistogramVec, /// Gauge for CKB rocksdb statistics @@ -127,6 +138,14 @@ static METRICS: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| M ) .unwrap(), ), + ckb_tx_pool_entry: CkbTxPoolEntryStatistics::from( + ®ister_int_gauge_vec!( + "ckb_tx_pool_entry", + "CKB tx-pool entry status statistics", + &["type"] + ) + .unwrap(), + ), ckb_message_bytes: register_histogram_vec!( "ckb_message_bytes", "The CKB message bytes", diff --git a/util/types/src/core/tx_pool.rs b/util/types/src/core/tx_pool.rs index cb1a569444..1254eb179e 100644 --- a/util/types/src/core/tx_pool.rs +++ b/util/types/src/core/tx_pool.rs @@ -344,3 +344,34 @@ pub struct TxPoolInfo { /// Total limit on the size of transactions in the tx-pool pub max_tx_pool_size: u64, } + +/// A Tx details info in tx-pool. +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct PoolTxDetailInfo { + /// The time added into tx-pool + pub timestamp: u64, + /// The detailed status in tx-pool, `Pending`, `Gap`, `Proposed` + pub entry_status: String, + /// The rank in pending, starting from 0 + pub rank_in_pending: usize, + /// The pending(`Pending` and `Gap`) count + pub pending_count: usize, + /// The proposed count + pub proposed_count: usize, + /// The descendants count of tx + pub descendants_count: usize, + /// The ancestors count of tx + pub ancestors_count: usize, + /// The score key details, useful to debug + pub score_sortkey: String, +} + +impl PoolTxDetailInfo { + /// Build with rejected status + pub fn with_unknown() -> Self { + Self { + entry_status: "unknown".to_string(), + ..Default::default() + } + } +}