Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tx pool Replace-by-fee #4079

Merged
merged 60 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e655819
begin refactor tx_pool
chenyukang May 15, 2023
7349d02
add pool_map
chenyukang May 16, 2023
b0cf66b
remove pending and gap
chenyukang May 16, 2023
5788226
move double spend checking and fix tests
chenyukang Jun 1, 2023
92bc733
fix makefile so that we may run a specific integration test with envi…
chenyukang Jun 5, 2023
180159e
fix TransactionRelayConflict
chenyukang Jun 5, 2023
ea00a95
upgrade multi_index_map for iterator rev
chenyukang Jun 6, 2023
9d375c8
enable PoolResolveConflictAfterReorg
chenyukang Jun 6, 2023
d46bd54
comments feedback and code cleanup
chenyukang Jun 7, 2023
466c971
add index_map shrink
chenyukang Jun 8, 2023
12b2116
confirmed TransactionRelayConflict is ok
chenyukang Jun 8, 2023
abbcd23
clean up proposed_rtx and gap_rtx
chenyukang Jun 8, 2023
508e281
check keep_rejected_tx_hashes_days
chenyukang Jun 8, 2023
f2b3b86
add more test for pool
chenyukang Jun 13, 2023
bdf557d
change name (add/sub)_ancestor_weight, prepare for (add/sub)_descenda…
chenyukang Jun 13, 2023
8a4172f
remove ancestors_size from AncestorsScoreSortKey since it is useless
chenyukang Jun 13, 2023
7027915
trivial refactor for PoolTransactionEntry
chenyukang Jun 13, 2023
8e72e3e
high score tx_entry will not blocked at pending
chenyukang Jun 13, 2023
08f58be
add descendants related info for evict, has performance regression
chenyukang Jun 14, 2023
0d9a103
cleanup and remove unused function names and tests
chenyukang Jun 14, 2023
2ab1e5f
remove outputs in edges
chenyukang Jun 26, 2023
459189a
fix get_all_entry_info and get_ids to sort tx
chenyukang Jul 13, 2023
8ffeeab
fix tests and remove timestamp in sort key
chenyukang Jul 13, 2023
cc1a72c
cleanup entry deps, ancestors, descendants
chenyukang Jul 14, 2023
66254de
move double spend checking and fix tests
chenyukang Jun 1, 2023
6e47d1f
begin to work on RBF
chenyukang Jun 25, 2023
20db306
introduce pool_cell
chenyukang Jun 25, 2023
8bbac25
more on RBF, make first RBF test pass
chenyukang Jun 27, 2023
bcb79a6
add checking rules for RBF
chenyukang Jun 28, 2023
2f6b13b
fix RBF callbacks issues
chenyukang Jul 4, 2023
f2ec36a
add more test for RBF and cleanup
chenyukang Jul 5, 2023
d37254e
add enbale_rbf config option
chenyukang Jul 19, 2023
f731e81
add Rule 7 for RBF, any old Tx should be in Pending status
chenyukang Jul 25, 2023
0464eab
merge score_key and evict_key to sort_key
chenyukang Jul 26, 2023
0e0b7ae
uncomment test
chenyukang Jul 26, 2023
193ccb1
add comments for min_rbf_rate
chenyukang Jul 26, 2023
e1d9125
refactor RBF and add fee related info to get_transaction
chenyukang Jul 26, 2023
d3a61b4
add min_replace_fee to get_transaction
chenyukang Jul 26, 2023
339ebab
fix calculate_min_replace_fee, using sum of fee from replaced Tx
chenyukang Jul 26, 2023
9d364c6
refactor TxModifiedEntries with MultiIndexMap
chenyukang Jul 26, 2023
389c30a
remove id from AncestorsScoreSortKey for perf
chenyukang Jul 26, 2023
279c3b7
Fix typo
chenyukang Jul 27, 2023
14e5cbf
Proposed tx will not get min_replace_fee
chenyukang Jul 27, 2023
446b0bd
add changelog for tx-pool refactor and RBF
chenyukang Jul 28, 2023
396584f
use proper name for tx-pool func
chenyukang Aug 1, 2023
792ef87
code cleanup to remove unwrap
chenyukang Aug 1, 2023
96ae57b
add comments for PreCheckedTx
chenyukang Aug 3, 2023
df06351
return proper error when calculate_min_replace_fee failed
chenyukang Aug 3, 2023
b4aec5a
use upstream multi_index_map
chenyukang Aug 4, 2023
99a010c
Merge branch 'tx_pool_refactor' into tx_pool_rbf
chenyukang Aug 7, 2023
c902bc2
fix fmt
chenyukang Aug 7, 2023
21f5159
use hashed_non_unique for status in entry
chenyukang Aug 10, 2023
05e2f8a
only check_rbf for resolve dead outpoint
chenyukang Aug 11, 2023
bc7e613
cleanup find_conflict_tx
chenyukang Aug 11, 2023
9e8dc8b
fix comments
chenyukang Aug 14, 2023
4512dc8
remove unnecessary clone in rbf
chenyukang Aug 14, 2023
95e3596
refactor rbf check
chenyukang Aug 14, 2023
a7dc32e
Merge branch 'tx_pool_refactor' into tx_pool_rbf
chenyukang Aug 14, 2023
0374910
add test case for RBFEnable and get_transaction_with_verbosity
chenyukang Aug 14, 2023
3e4b947
entries in Gap will not be fill proposals
chenyukang Aug 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
### Features
* #4079, **tx-pol:** Implement `Replace-by-Fee(RBF)` for tx-pool (@chenyukang)
This feature enables users to replace a transaction with a higher fee rate, which is useful when the transaction is stuck in the tx-pool:
* Add `min_rbf_rate` in `ckb.toml` with default value `1500`, which means the minimum extra fee rate for RBF, the unit is `shannons/KB`
* Add fields `fee` and `min_replace_fee` in `get_transaction`, which means the the minimal fee need to pay for RBF for a specific transaction
* The replaced transaction will be removed from `tx-pool` and with the status `Rejected`.

### Improvements
* #3993, **tx-pool:** Almost reimplemented `tx-pool` with `multi_index_map`, with the following improvements (@chenyukang):
* Sort txs in pool by `score` in `Pending` stage, `txs` with higher `score` be processed first
* Evict `txs` from pool with `descendants_count` and `fee_rate`
* Eliminate redundant code for clean and consistent code

# [v0.110.0](https://github.com/nervosnetwork/ckb/compare/v0.109.0...v0.110.0) (2023-05-15)

### Features
Expand Down
41 changes: 25 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ pub enum Pow {
/// Mocking dummy PoW engine
Dummy,
/// The Eaglesong PoW engine
/// Check details of Eaglesong from: https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0010-eaglesong/0010-eaglesong.md
/// Check details of Eaglesong from: <https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0010-eaglesong/0010-eaglesong.md>
Eaglesong,
/// The Eaglesong PoW engine, similar to `Eaglesong`, but using `blake2b` hash as the final output.
/// Check details of blake2b from: https://tools.ietf.org/html/rfc7693 and blake2b-rs from: https://github.com/nervosnetwork/blake2b-rs
/// Check details of blake2b from: <https://tools.ietf.org/html/rfc7693> and blake2b-rs from: <https://github.com/nervosnetwork/blake2b-rs>
EaglesongBlake2b,
}

Expand Down
2 changes: 2 additions & 0 deletions resource/ckb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ enable_deprecated_rpc = false # {{
[tx_pool]
max_tx_pool_size = 180_000_000 # 180mb
min_fee_rate = 1_000 # Here fee_rate are calculated directly using size in units of shannons/KB
# min_rbf_rate > min_fee_rate means RBF is enabled
min_rbf_rate = 1_500 # Here fee_rate are calculated directly using size in units of shannons/KB
max_tx_verify_cycles = 70_000_000
max_ancestors_count = 25

Expand Down
18 changes: 17 additions & 1 deletion rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,8 @@ Response
},
"cycles": "0x219",
"time_added_to_pool" : "0x187b3d137a1",
"fee": "0x16923f7dcf",
"min_replace_fee": "0x16923f7f6a",
"tx_status": {
"block_hash": null,
"status": "pending",
Expand Down Expand Up @@ -4510,6 +4512,7 @@ Response
"result": {
"last_txs_updated_at": "0x0",
"min_fee_rate": "0x3e8",
"min_rbf_rate": "0x5dc",
"max_tx_pool_size": "0xaba9500",
"orphan": "0x0",
"pending": "0x1",
Expand Down Expand Up @@ -5072,6 +5075,10 @@ For example, a cellbase transaction is not allowed in `send_transaction` RPC.

(-1110): The transaction exceeded maximum size limit.

### Error `PoolRejectedRBF`

(-1111): The transaction is rejected for RBF checking.

### Error `Indexer`

(-1200): The indexer error.
Expand Down Expand Up @@ -6426,7 +6433,7 @@ TX reject message

`PoolTransactionReject` is a JSON object with following fields.

* `type`: `"LowFeeRate" | "ExceededMaximumAncestorsCount" | "ExceededTransactionSizeLimit" | "Full" | "Duplicated" | "Malformed" | "DeclaredWrongCycles" | "Resolve" | "Verification" | "Expiry"` - Reject type.
* `type`: `"LowFeeRate" | "ExceededMaximumAncestorsCount" | "ExceededTransactionSizeLimit" | "Full" | "Duplicated" | "Malformed" | "DeclaredWrongCycles" | "Resolve" | "Verification" | "Expiry" | "RBFRejected"` - Reject type.
* `description`: `string` - Detailed description about why the transaction is rejected.

Different reject types:
Expand All @@ -6441,6 +6448,7 @@ Different reject types:
* `Resolve`: Resolve failed
* `Verification`: Verification failed
* `Expiry`: Transaction expired
* `RBFRejected`: RBF rejected


### Type `ProposalShortId`
Expand Down Expand Up @@ -6949,6 +6957,10 @@ The JSON view of a transaction as well as its status.

* `tx_status`: [`TxStatus`](#type-txstatus) - The Transaction status.

* `fee`: [`Capacity`](#type-capacity) `|` `null` - The transaction fee of the transaction

* `min_replace_fee`: [`Capacity`](#type-capacity) `|` `null` - The minimal fee required to replace this transaction


### Type `TxPoolEntries`

Expand Down Expand Up @@ -7033,6 +7045,10 @@ Transaction pool information.

The unit is Shannons per 1000 bytes transaction serialization size in the block.

* `min_rbf_rate`: [`Uint64`](#type-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.

* `last_txs_updated_at`: [`Timestamp`](#type-timestamp) - Last updated time. This is the Unix timestamp in milliseconds.

* `tx_size_limit`: [`Uint64`](#type-uint64) - Limiting transactions to tx_size_limit
Expand Down
3 changes: 3 additions & 0 deletions rpc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub enum RPCError {
TransactionExpired = -1109,
/// (-1110): The transaction exceeded maximum size limit.
PoolRejectedTransactionBySizeLimit = -1110,
/// (-1111): The transaction is rejected for RBF checking.
PoolRejectedRBF = -1111,
/// (-1200): The indexer error.
Indexer = -1200,
}
Expand Down Expand Up @@ -173,6 +175,7 @@ impl RPCError {
Reject::DeclaredWrongCycles(..) => RPCError::PoolRejectedMalformedTransaction,
Reject::Resolve(_) => RPCError::TransactionFailedToResolve,
Reject::Verification(_) => RPCError::TransactionFailedToVerify,
Reject::RBFRejected(_) => RPCError::PoolRejectedRBF,
Reject::ExceededTransactionSizeLimit(_, _) => {
RPCError::PoolRejectedTransactionBySizeLimit
}
Expand Down
5 changes: 4 additions & 1 deletion rpc/src/module/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,8 @@ pub trait ChainRpc {
/// },
/// "cycles": "0x219",
/// "time_added_to_pool" : "0x187b3d137a1",
/// "fee": "0x16923f7dcf",
/// "min_replace_fee": "0x16923f7f6a",
/// "tx_status": {
/// "block_hash": null,
/// "status": "pending",
Expand Down Expand Up @@ -2130,11 +2132,11 @@ impl ChainRpcImpl {
.and_then(|v| v.get(tx_info.index.saturating_sub(1)).copied())
})
};

return Ok(TransactionWithStatus::with_committed(
None,
tx_info.block_hash.unpack(),
cycles,
None,
));
}

Expand Down Expand Up @@ -2181,6 +2183,7 @@ impl ChainRpcImpl {
Some(tx),
tx_info.block_hash.unpack(),
cycles,
None,
));
}

Expand Down
1 change: 1 addition & 0 deletions rpc/src/module/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub trait PoolRpc {
/// "result": {
/// "last_txs_updated_at": "0x0",
/// "min_fee_rate": "0x3e8",
/// "min_rbf_rate": "0x5dc",
/// "max_tx_pool_size": "0xaba9500",
/// "orphan": "0x0",
/// "pending": "0x1",
Expand Down
4 changes: 2 additions & 2 deletions sync/src/synchronizer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! CKB node has initial block download phase (IBD mode) like Bitcoin:
//! https://btcinformation.org/en/glossary/initial-block-download
//! <https://btcinformation.org/en/glossary/initial-block-download>
//!
//! When CKB node is in IBD mode, it will respond `packed::InIBD` to `GetHeaders` and `GetBlocks` requests
//!
//! And CKB has a headers-first synchronization style like Bitcoin:
//! https://btcinformation.org/en/glossary/headers-first-sync
//! <https://btcinformation.org/en/glossary/headers-first-sync>
//!
mod block_fetcher;
mod block_process;
Expand Down
11 changes: 10 additions & 1 deletion test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,16 @@ fn all_specs() -> Vec<Box<dyn Spec>> {
Box::new(RelayWithWrongTx::new()),
Box::new(TxsRelayOrder),
Box::new(SendTxChain),
Box::new(DifferentTxsWithSameInput),
Box::new(DifferentTxsWithSameInputWithOutRBF),
Box::new(RbfEnable),
Box::new(RbfBasic),
Box::new(RbfSameInput),
Box::new(RbfOnlyForResolveDead),
Box::new(RbfSameInputwithLessFee),
Box::new(RbfTooManyDescendants),
Box::new(RbfContainNewTx),
Box::new(RbfContainInvalidInput),
Box::new(RbfRejectReplaceProposed),
Box::new(CompactBlockEmpty),
Box::new(CompactBlockEmptyParentUnknown),
Box::new(CompactBlockPrefilled),
Expand Down
23 changes: 17 additions & 6 deletions test/src/specs/tx_pool/descendant.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use ckb_jsonrpc_types::Status;

use crate::specs::tx_pool::utils::prepare_tx_family;
use crate::utils::{blank, commit, propose};
use crate::{Node, Spec};
Expand Down Expand Up @@ -166,21 +168,30 @@ impl Spec for SubmitTransactionWhenItsParentInProposed {

// 1. Propose `tx_family.a` into proposed-pool.
let family = prepare_tx_family(node);
node.submit_transaction(family.a());
node.submit_block(&propose(node, &[family.a()]));
let tx_a = family.a();
node.submit_transaction(tx_a);
node.submit_block(&propose(node, &[tx_a]));
(0..=window.closest()).for_each(|_| {
node.submit_block(&blank(node));
});

// tx_a should in Proposed status
let tx_a_status = node.get_transaction(tx_a.hash());
assert_eq!(tx_a_status.status, Status::Proposed);

// 2. Submit `tx_family.b` into pending-pool. Then we expect that miner propose it.
node.submit_transaction(family.b());
let block = node.new_block_with_blocking(|template| template.proposals.is_empty());
let union_proposal_ids = block.union_proposal_ids();
assert!(
block
.union_proposal_ids()
.contains(&family.b().proposal_short_id()),
union_proposal_ids.contains(&family.b().proposal_short_id()),
"Miner should propose tx_family.b since it has never been proposed, actual: {:?}",
block.union_proposal_ids(),
union_proposal_ids,
);
assert!(
!union_proposal_ids.contains(&tx_a.proposal_short_id()),
"Miner should not propose tx_family.a since it has been proposed, actual: {:?}",
union_proposal_ids,
);
node.submit_block(&block);
}
Expand Down
7 changes: 4 additions & 3 deletions test/src/specs/tx_pool/different_txs_with_same_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use ckb_types::{
prelude::*,
};

pub struct DifferentTxsWithSameInput;
pub struct DifferentTxsWithSameInputWithOutRBF;

impl Spec for DifferentTxsWithSameInput {
impl Spec for DifferentTxsWithSameInputWithOutRBF {
fn run(&self, nodes: &mut Vec<Node>) {
let node0 = &nodes[0];

Expand All @@ -19,6 +19,7 @@ impl Spec for DifferentTxsWithSameInput {
info!("Generate 2 txs with same input");
let tx1 = node0.new_transaction(tx_hash_0.clone());
let tx2_temp = node0.new_transaction(tx_hash_0);

// Set tx2 fee to a higher value, tx1 capacity is 100, set tx2 capacity to 80 for +20 fee.
let output = CellOutputBuilder::default()
.capacity(capacity_bytes!(80).pack())
Expand Down Expand Up @@ -46,7 +47,7 @@ impl Spec for DifferentTxsWithSameInput {
.map(TransactionView::hash)
.collect();

// RBF (Replace-By-Fees) is not implemented
// RBF (Replace-By-Fees) is not enabled
assert!(commit_txs_hash.contains(&tx1.hash()));
assert!(!commit_txs_hash.contains(&tx2.hash()));

Expand Down
2 changes: 2 additions & 0 deletions test/src/specs/tx_pool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod pool_resurrect;
mod proposal_expire_rule;
mod remove_tx;
mod reorg_proposals;
mod replace;
mod send_defected_binary;
mod send_large_cycles_tx;
mod send_low_fee_rate_tx;
Expand Down Expand Up @@ -42,6 +43,7 @@ pub use pool_resurrect::*;
pub use proposal_expire_rule::*;
pub use remove_tx::*;
pub use reorg_proposals::*;
pub use replace::*;
pub use send_defected_binary::*;
pub use send_large_cycles_tx::*;
pub use send_low_fee_rate_tx::*;
Expand Down
Loading
Loading