1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
use crate::{BlockNumber, Capacity, Cycle, Timestamp, TransactionView, Uint64};
use ckb_types::core::service::PoolTransactionEntry as CorePoolTransactionEntry;
use ckb_types::core::tx_pool::{
    AncestorsScoreSortKey as CoreAncestorsScoreSortKey, PoolTxDetailInfo as CorePoolTxDetailInfo,
    Reject, TxEntryInfo, TxPoolEntryInfo, TxPoolIds as CoreTxPoolIds, TxPoolInfo as CoreTxPoolInfo,
};
use ckb_types::prelude::Unpack;
use ckb_types::H256;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Transaction pool information.
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, JsonSchema)]
pub struct TxPoolInfo {
    /// The associated chain tip block hash.
    ///
    /// The transaction pool is stateful. It manages the transactions which are valid to be
    /// committed after this block.
    pub tip_hash: H256,
    /// The block number of the block `tip_hash`.
    pub tip_number: BlockNumber,
    /// Count of transactions in the pending state.
    ///
    /// The pending transactions must be proposed in a new block first.
    pub pending: Uint64,
    /// Count of transactions in the proposed state.
    ///
    /// The proposed transactions are ready to be committed in the new block after the block
    /// `tip_hash`.
    pub proposed: Uint64,
    /// Count of orphan transactions.
    ///
    /// An orphan transaction has an input cell from the transaction which is neither in the chain
    /// nor in the transaction pool.
    pub orphan: Uint64,
    /// Total count of transactions in the pool of all the different kinds of states (excluding orphan transactions).
    pub total_tx_size: Uint64,
    /// Total consumed VM cycles of all the transactions in the pool (excluding orphan transactions).
    pub total_tx_cycles: Uint64,
    /// Fee rate threshold. The pool rejects transactions which fee rate is below this threshold.
    ///
    /// The unit is Shannons per 1000 bytes transaction serialization size in the block.
    pub min_fee_rate: Uint64,
    /// RBF rate threshold.
    ///
    /// The pool reject to replace for transactions which fee rate is below this threshold.
    /// if min_rbf_rate > min_fee_rate then RBF is enabled on the node.
    ///
    /// The unit is Shannons per 1000 bytes transaction serialization size in the block.
    pub min_rbf_rate: Uint64,
    /// Last updated time. This is the Unix timestamp in milliseconds.
    pub last_txs_updated_at: Timestamp,
    /// Limiting transactions to tx_size_limit
    ///
    /// Transactions with a large size close to the block size limit may not be packaged,
    /// because the block header and cellbase are occupied,
    /// so the tx-pool is limited to accepting transaction up to tx_size_limit.
    pub tx_size_limit: Uint64,
    /// Total limit on the size of transactions in the tx-pool
    pub max_tx_pool_size: Uint64,
}

impl From<CoreTxPoolInfo> for TxPoolInfo {
    fn from(tx_pool_info: CoreTxPoolInfo) -> Self {
        TxPoolInfo {
            tip_hash: tx_pool_info.tip_hash.unpack(),
            tip_number: tx_pool_info.tip_number.into(),
            pending: (tx_pool_info.pending_size as u64).into(),
            proposed: (tx_pool_info.proposed_size as u64).into(),
            orphan: (tx_pool_info.orphan_size as u64).into(),
            total_tx_size: (tx_pool_info.total_tx_size as u64).into(),
            total_tx_cycles: tx_pool_info.total_tx_cycles.into(),
            min_fee_rate: tx_pool_info.min_fee_rate.as_u64().into(),
            min_rbf_rate: tx_pool_info.min_rbf_rate.as_u64().into(),
            last_txs_updated_at: tx_pool_info.last_txs_updated_at.into(),
            tx_size_limit: tx_pool_info.tx_size_limit.into(),
            max_tx_pool_size: tx_pool_info.max_tx_pool_size.into(),
        }
    }
}

/// The transaction entry in the pool.
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
pub struct PoolTransactionEntry {
    /// The transaction.
    pub transaction: TransactionView,
    /// Consumed cycles.
    pub cycles: Cycle,
    /// The transaction serialized size in block.
    pub size: Uint64,
    /// The transaction fee.
    pub fee: Capacity,
    /// The unix timestamp when entering the Txpool, unit: Millisecond
    pub timestamp: Uint64,
}

impl From<CorePoolTransactionEntry> for PoolTransactionEntry {
    fn from(entry: CorePoolTransactionEntry) -> Self {
        PoolTransactionEntry {
            transaction: entry.transaction.into(),
            cycles: entry.cycles.into(),
            size: (entry.size as u64).into(),
            fee: entry.fee.into(),
            timestamp: entry.timestamp.into(),
        }
    }
}

/// Transaction output validators that prevent common mistakes.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum OutputsValidator {
    /// the default validator, bypass output checking, thus allow any kind of transaction outputs.
    Passthrough,
    /// restricts the lock script and type script usage, see more information on <https://github.com/nervosnetwork/ckb/wiki/Transaction-%C2%BB-Default-Outputs-Validator>
    WellKnownScriptsOnly,
}

impl OutputsValidator {
    /// Gets the name of the validator when it is serialized into JSON string.
    pub fn json_display(&self) -> String {
        let v = serde_json::to_value(self).expect("OutputsValidator to JSON should never fail");
        v.as_str().unwrap_or_default().to_string()
    }
}

/// Array of transaction ids
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, JsonSchema)]
pub struct TxPoolIds {
    /// Pending transaction ids
    pub pending: Vec<H256>,
    /// Proposed transaction ids
    pub proposed: Vec<H256>,
}

impl From<CoreTxPoolIds> for TxPoolIds {
    fn from(ids: CoreTxPoolIds) -> Self {
        let CoreTxPoolIds { pending, proposed } = ids;
        TxPoolIds {
            pending: pending.iter().map(Unpack::unpack).collect(),
            proposed: proposed.iter().map(Unpack::unpack).collect(),
        }
    }
}

/// Transaction entry info
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, JsonSchema)]
pub struct TxPoolEntry {
    /// Consumed cycles.
    pub cycles: Uint64,
    /// The transaction serialized size in block.
    pub size: Uint64,
    /// The transaction fee.
    pub fee: Capacity,
    /// Size of in-tx-pool ancestor transactions
    pub ancestors_size: Uint64,
    /// Cycles of in-tx-pool ancestor transactions
    pub ancestors_cycles: Uint64,
    /// Number of in-tx-pool ancestor transactions
    pub ancestors_count: Uint64,
    /// The unix timestamp when entering the Txpool, unit: Millisecond
    pub timestamp: Uint64,
}

impl From<TxEntryInfo> for TxPoolEntry {
    fn from(info: TxEntryInfo) -> Self {
        TxPoolEntry {
            cycles: info.cycles.into(),
            size: info.size.into(),
            fee: info.fee.into(),
            ancestors_size: info.ancestors_size.into(),
            ancestors_cycles: info.ancestors_cycles.into(),
            ancestors_count: info.ancestors_count.into(),
            timestamp: info.timestamp.into(),
        }
    }
}

/// Tx-pool entries object
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, JsonSchema)]
pub struct TxPoolEntries {
    /// Pending tx verbose info
    pub pending: HashMap<H256, TxPoolEntry>,
    /// Proposed tx verbose info
    pub proposed: HashMap<H256, TxPoolEntry>,
    /// Conflicted tx hash vec
    pub conflicted: Vec<H256>,
}

impl From<TxPoolEntryInfo> for TxPoolEntries {
    fn from(info: TxPoolEntryInfo) -> Self {
        let TxPoolEntryInfo {
            pending,
            proposed,
            conflicted,
        } = info;

        TxPoolEntries {
            pending: pending
                .into_iter()
                .map(|(hash, entry)| (hash.unpack(), entry.into()))
                .collect(),
            proposed: proposed
                .into_iter()
                .map(|(hash, entry)| (hash.unpack(), entry.into()))
                .collect(),
            conflicted: conflicted.iter().map(Unpack::unpack).collect(),
        }
    }
}

/// All transactions in tx-pool.
///
/// `RawTxPool` is equivalent to [`TxPoolIds`][] `|` [`TxPoolEntries`][].
///
/// [`TxPoolIds`]: struct.TxPoolIds.html
/// [`TxPoolEntries`]: struct.TxPoolEntries.html
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, JsonSchema)]
#[serde(untagged)]
pub enum RawTxPool {
    /// verbose = false
    Ids(TxPoolIds),
    /// verbose = true
    Verbose(TxPoolEntries),
}

/// A struct as a sorted key for tx-pool
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, JsonSchema)]
pub struct AncestorsScoreSortKey {
    /// Fee
    pub fee: Uint64,
    /// Weight
    pub weight: Uint64,
    /// Ancestors fee
    pub ancestors_fee: Uint64,
    /// Ancestors weight
    pub ancestors_weight: Uint64,
}

impl From<CoreAncestorsScoreSortKey> for AncestorsScoreSortKey {
    fn from(value: CoreAncestorsScoreSortKey) -> Self {
        Self {
            fee: value.fee.into(),
            weight: value.weight.into(),
            ancestors_fee: value.ancestors_fee.into(),
            ancestors_weight: value.ancestors_weight.into(),
        }
    }
}

/// A Tx details info in tx-pool.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, JsonSchema)]
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: AncestorsScoreSortKey,
}

impl From<CorePoolTxDetailInfo> 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.into(),
        }
    }
}

/// TX reject message, `PoolTransactionReject` is a JSON object with following fields.
///    * `type`:  the Reject type with following enum values
///    * `description`: `string` - Detailed description about why the transaction is rejected.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", content = "description")]
pub enum PoolTransactionReject {
    /// Transaction fee lower than config
    LowFeeRate(String),

    /// Transaction exceeded maximum ancestors count limit
    ExceededMaximumAncestorsCount(String),

    /// Transaction exceeded maximum size limit
    ExceededTransactionSizeLimit(String),

    /// Transaction are replaced because the pool is full
    Full(String),

    /// Transaction already exists in transaction_pool
    Duplicated(String),

    /// Malformed transaction
    Malformed(String),

    /// Declared wrong cycles
    DeclaredWrongCycles(String),

    /// Resolve failed
    Resolve(String),

    /// Verification failed
    Verification(String),

    /// Transaction expired
    Expiry(String),

    /// RBF rejected
    RBFRejected(String),

    /// Invalidated rejected
    Invalidated(String),
}

impl From<Reject> for PoolTransactionReject {
    fn from(reject: Reject) -> Self {
        match reject {
            Reject::LowFeeRate(..) => Self::LowFeeRate(format!("{reject}")),
            Reject::ExceededMaximumAncestorsCount => {
                Self::ExceededMaximumAncestorsCount(format!("{reject}"))
            }
            Reject::ExceededTransactionSizeLimit(..) => {
                Self::ExceededTransactionSizeLimit(format!("{reject}"))
            }
            Reject::Full(..) => Self::Full(format!("{reject}")),
            Reject::Duplicated(_) => Self::Duplicated(format!("{reject}")),
            Reject::Malformed(_, _) => Self::Malformed(format!("{reject}")),
            Reject::DeclaredWrongCycles(..) => Self::DeclaredWrongCycles(format!("{reject}")),
            Reject::Resolve(_) => Self::Resolve(format!("{reject}")),
            Reject::Verification(_) => Self::Verification(format!("{reject}")),
            Reject::Expiry(_) => Self::Expiry(format!("{reject}")),
            Reject::RBFRejected(_) => Self::RBFRejected(format!("{reject}")),
            Reject::Invalidated(_) => Self::Invalidated(format!("{reject}")),
        }
    }
}