Refers:
txid will change hence the fee and weight was changed. a simple inject-replace is impossible.
The key point is, submmit the new tx before old one. Since ChunkQueue is FIFO, so the simplest way is insert new tx into its head.
A tx replacement must follow
- The transaction is submitted through new extended version of
SubmitLocalTxandSubmitRemoteTxcalledSubmitLocalTxReplaceableandSubmitRemoteTxReplaceable. In the client side which is send through RPCsend_transaction_replaceable - Assume that a Tx called “Tx_A” was submmited with these functions/calls will be marked as
replaceableinTxPool - Client can construct a new replace tx called “TX_B” with any same
inputbefore the original tx’s status becomesCommittedorRejected - The Tx_B must have a higher fee, and spends the same input cell with Tx_A
- The Tx_B will be inserted into a high priority queue, chunk’s process will pop and use value inside it, until it’s empty, then
self.front - When submitting tx from high priority queue, it will first check if the hash of it’s input is still in the map; If not, it means that this tx is no longer replaceable(whether
CommittedorRejected), this tx will be rejected(not accepted as a replacement) - When process a tx in tx_pool, it will first check if any input of this tx is found in the record map, if so, this is a replacement tx, which will perfoms a rbf; if not, it will continue the original resolve process
So the modification would be:
adds a bool in ckb_tx_pool::service::Message
pub(crate) enum Message {
//.....
SubmitLocalTx(Request<TransactionView, bool, SubmitTxResult>),
SubmitRemoteTx(Request<(TransactionView, bool, Cycle, PeerIndex), ()>),
//....
}Adds send_transaction_replaceable , make send_transaction as a wrapper for it, defaults replaceable is false
#[rpc(name = "send_transaction_replaceable")]
fn send_transaction_replaceable(
&self,
tx: Transaction,
replaceable: bool,
outputs_validator: Option<OutputsValidator>,
) -> Result<H256>;/// An entry in the transaction pool.
#[derive(Debug, Clone, Eq)]
pub struct TxEntry {
/// Transaction
pub rtx: ResolvedTransaction,
/// Cycles
pub cycles: Cycle,
/// tx size
pub size: usize,
/// fee
pub fee: Capacity,
//......
pub timestamp: u64,
**/// The flag to show whether this transaction can be replaced**
**pub replaceable: bool,**
}Adds a map that records all inputs in a replaceable tx
/// Tx-pool implementation
pub struct TxPool {
pub(crate) config: TxPoolConfig,
/// The short id that has not been proposed
pub(crate) pending: PendingQueue,
/// The proposal gap
pub(crate) gap: PendingQueue,
/// Tx pool that finely for commit
pub(crate) proposed: ProposedPool,
/// cache for committed transactions hash
pub(crate) committed_txs_hash_cache: LruCache<ProposalShortId, Byte32>,
// sum of all tx_pool tx's virtual sizes.
pub(crate) total_tx_size: usize,
// sum of all tx_pool tx's cycles.
pub(crate) total_tx_cycles: Cycle,
/// storage snapshot reference
pub(crate) snapshot: Arc<Snapshot>,
/// record recent reject
pub recent_reject: Option<RecentReject>,
// expiration milliseconds,
pub(crate) expiry: u64,
// records the replaceable tx's inputs
**pub replaceable_inputs: HashMap<Outpoint, ProposalShortId>,**
}In Precheck,there’s a method call named resolve_tx_from_pending_and_proposed, which is used to check if a tx used a seen input in pool
pub(crate) fn resolve_tx_from_pending_and_proposed(
&self,
tx: TransactionView,
) -> Result<ResolvedTransaction, Reject> {
//......
//......
// Check if tx's inputs is marked replaceable
if **check_replaceable(tx) {
// deal with replaceable inputs
// maybe just act as a normal tx
} else {**
resolve_transaction(
tx,
&mut seen_inputs,
&pending_and_proposed_provider,
snapshot,
)
.map_err(Reject::Resolve)
**}**
}All inputs which contain in the tx that the conflict replaceable input from should be removed from TxPool.replaceable_inputs
Since the replacement will never happen when after a tx being Commited/ Rjected,the original tx shall be able to remove from the pool.
It will not become a new tx, because the replace tx spends the same input cell with the original one.
check: https://github.com/nervosnetwork/ckb/blob/develop/util/types/schemas/extensions.mol#L155
table RelayTransaction {
cycles: Uint64,
transaction: Transaction,
**replaceable: BoolOpt,**
}// util/types/src/core/service.rs
/// Notify pool transaction entry
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PoolTransactionEntry {
/// Transaction view
pub transaction: TransactionView,
/// Transaction consumed cycles
pub cycles: Cycle,
/// Transaction serialized cycles
pub size: usize,
/// Transaction fee
pub fee: Capacity,
/// The unix timestamp when entering the Txpool, unit: Millisecond
pub timestamp: u64,
**pub replaceable: bool,**
}This is used in NotifyController::notify_new_transaction
As a refer to https://github.com/bitcoin/bitcoin/blob/master/doc/policy/packages.md#package-mempool-acceptance-rules
We need to introduce some additional policy restriction to RBF feature:
- The replacement tx can not contains any unconfirmed inputs in txpool. This can solve the “tx pin problem”
- The replacement transaction should pay a higher effective fee rate than the total fee of the root of the set of transactions it replaces
- The replacement transaction must also pay for replacing the original transactions at or above the rate set by the node's minimum fee. https://github.com/nervosnetwork/ckb/wiki/Transaction-»-Transaction-Fee#min-transaction-fee-rate
- Replace rate limitation: Assume that we set a time duration as a rate limit(for example 5s), if a tx was replaced within 5s before, then the incomming replace tx will be rejected directly