Skip to content

Commit

Permalink
Merge pull request #4199 from chenyukang/debug-orphan-issues
Browse files Browse the repository at this point in the history
Fix orphan pool for long pending tx issues
  • Loading branch information
chenyukang authored Oct 27, 2023
2 parents 34dbc77 + 97bdabd commit 4cbb826
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 52 deletions.
6 changes: 4 additions & 2 deletions spec/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ pub(crate) const GENESIS_EPOCH_LENGTH: u64 = 1_000;
// o_ideal = 1/40 = 2.5%
pub(crate) const DEFAULT_ORPHAN_RATE_TARGET: (u32, u32) = (1, 40);

const MAX_BLOCK_INTERVAL: u64 = 48; // 48s
const MIN_BLOCK_INTERVAL: u64 = 8; // 8s
/// max block interval, 48 seconds
pub const MAX_BLOCK_INTERVAL: u64 = 48;
/// min block interval, 8 seconds
pub const MIN_BLOCK_INTERVAL: u64 = 8;

/// cycles of a typical two-in-two-out tx.
pub const TWO_IN_TWO_OUT_CYCLES: Cycle = 3_500_000;
Expand Down
6 changes: 3 additions & 3 deletions sync/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::utils::is_internal_db_error;
use crate::{Status, StatusCode, FAST_INDEX, LOW_INDEX, NORMAL_INDEX, TIME_TRACE_SIZE};
use ckb_app_config::SyncConfig;
use ckb_chain::chain::ChainController;
use ckb_chain_spec::consensus::Consensus;
use ckb_chain_spec::consensus::{Consensus, MAX_BLOCK_INTERVAL, MIN_BLOCK_INTERVAL};
use ckb_channel::Receiver;
use ckb_constant::sync::{
BLOCK_DOWNLOAD_TIMEOUT, HEADERS_DOWNLOAD_HEADERS_PER_SECOND, HEADERS_DOWNLOAD_INSPECT_WINDOW,
Expand Down Expand Up @@ -103,8 +103,8 @@ impl ChainSyncState {

fn tip_synced(&mut self) {
let now = unix_time_as_millis();
// use avg block interval: (MAX_BLOCK_INTERVAL + MIN_BLOCK_INTERVAL) / 2 = 28
self.headers_sync_state = HeadersSyncState::TipSynced(now + 28000);
let avg_interval = (MAX_BLOCK_INTERVAL + MIN_BLOCK_INTERVAL) / 2;
self.headers_sync_state = HeadersSyncState::TipSynced(now + avg_interval * 1000);
}

fn started(&self) -> bool {
Expand Down
4 changes: 4 additions & 0 deletions test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,10 @@ fn all_specs() -> Vec<Box<dyn Spec>> {
Box::new(DeclaredWrongCyclesChunk),
Box::new(DeclaredWrongCyclesAndRelayAgain),
Box::new(OrphanTxAccepted),
Box::new(TxPoolOrphanNormal),
Box::new(TxPoolOrphanReverse),
Box::new(TxPoolOrphanUnordered),
Box::new(TxPoolOrphanDoubleSpend),
Box::new(OrphanTxRejected),
Box::new(GetRawTxPool),
Box::new(PoolReconcile),
Expand Down
23 changes: 20 additions & 3 deletions test/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ impl Node {
self.submit_transaction(&self.new_transaction_spend_tip_cellbase())
}

// generate a transaction which spend tip block's cellbase and capacity
pub fn new_transaction_with_capacity(&self, capacity: Capacity) -> TransactionView {
let block = self.get_tip_block();
let cellbase = &block.transactions()[0];
self.new_transaction_with_since_capacity(cellbase.hash(), 0, capacity)
}

// generate a transaction which spend tip block's cellbase
pub fn new_transaction_spend_tip_cellbase(&self) -> TransactionView {
let block = self.get_tip_block();
Expand Down Expand Up @@ -539,11 +546,12 @@ impl Node {
self.new_transaction_with_since_capacity(hash, since, capacity_bytes!(100))
}

pub fn new_transaction_with_since_capacity(
pub fn new_transaction_with_capacity_and_index(
&self,
hash: Byte32,
since: u64,
capacity: Capacity,
index: u32,
since: u64,
) -> TransactionView {
let always_success_cell_dep = self.always_success_cell_dep();
let always_success_script = self.always_success_script();
Expand All @@ -557,10 +565,19 @@ impl Node {
.build(),
)
.output_data(Default::default())
.input(CellInput::new(OutPoint::new(hash, 0), since))
.input(CellInput::new(OutPoint::new(hash, index), since))
.build()
}

pub fn new_transaction_with_since_capacity(
&self,
hash: Byte32,
since: u64,
capacity: Capacity,
) -> TransactionView {
self.new_transaction_with_capacity_and_index(hash, capacity, 0, since)
}

pub fn new_always_failure_transaction(&self, hash: Byte32) -> TransactionView {
let always_failure_cell_dep = self.always_failure_cell_dep();
let always_failure_script = self.always_failure_script();
Expand Down
251 changes: 251 additions & 0 deletions test/src/specs/tx_pool/orphan_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ use crate::utils::wait_until;
use crate::{Net, Node, Spec};
use ckb_jsonrpc_types::Status;
use ckb_network::SupportProtocols;
use ckb_types::core::{capacity_bytes, Capacity, TransactionBuilder, TransactionView};
use ckb_types::packed::CellOutputBuilder;
use ckb_types::{
packed::{CellInput, OutPoint},
prelude::*,
};

const ALWAYS_SUCCESS_SCRIPT_CYCLE: u64 = 537;
// always_failure, as the name implies, so it doesn't matter what the cycles are
Expand Down Expand Up @@ -97,3 +103,248 @@ impl Spec for OrphanTxRejected {
assert!(matches!(ret.tx_status.status, Status::Rejected));
}
}

// construct a tx chain with such structure:
//
// parent
// |
// tx1
// / | \
// tx11 tx12 tx13
// \ | /
// final_tx
//
fn build_tx_chain(
node0: &Node,
) -> (
Net,
(
TransactionView,
TransactionView,
TransactionView,
TransactionView,
TransactionView,
TransactionView,
),
) {
node0.mine_until_out_bootstrap_period();
let parent = node0.new_transaction_with_capacity(capacity_bytes!(800));

let script = node0.always_success_script();
let new_output1 = CellOutputBuilder::default()
.capacity(capacity_bytes!(200).pack())
.lock(script.clone())
.build();
let new_output2 = new_output1.clone();
let new_output3 = new_output1.clone();

let tx1 = parent
.as_advanced_builder()
.set_inputs(vec![CellInput::new(OutPoint::new(parent.hash(), 0), 0)])
.set_outputs(vec![new_output1, new_output2, new_output3])
.set_outputs_data(vec![Default::default(); 3])
.build();

let tx11 =
node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(100), 0, 0);
let tx12 =
node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(100), 1, 0);
let tx13 =
node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(100), 2, 0);

let cell_dep = node0.always_success_cell_dep();
let final_output = CellOutputBuilder::default()
.capacity(capacity_bytes!(80).pack())
.lock(script)
.build();
let final_tx = TransactionBuilder::default()
.cell_dep(cell_dep)
.set_inputs(vec![
CellInput::new(OutPoint::new(tx11.hash(), 0), 0),
CellInput::new(OutPoint::new(tx12.hash(), 0), 0),
CellInput::new(OutPoint::new(tx13.hash(), 0), 0),
])
.set_outputs(vec![final_output])
.set_outputs_data(vec![Default::default(); 1])
.build();

let mut net = Net::new(
"orphan_tx_test",
node0.consensus(),
vec![SupportProtocols::RelayV3],
);
net.connect(node0);

(net, (parent, tx1, tx11, tx12, tx13, final_tx))
}

fn run_replay_tx(
net: &Net,
node0: &Node,
tx: TransactionView,
orphan_tx_cnt: u64,
pending_cnt: u64,
) -> bool {
relay_tx(net, node0, tx, ALWAYS_SUCCESS_SCRIPT_CYCLE);

wait_until(5, || {
let tx_pool_info = node0.get_tip_tx_pool_info();
tx_pool_info.orphan.value() == orphan_tx_cnt && tx_pool_info.pending.value() == pending_cnt
})
}

pub struct TxPoolOrphanNormal;
impl Spec for TxPoolOrphanNormal {
fn run(&self, nodes: &mut Vec<Node>) {
let node0 = &nodes[0];
let (net, (parent, tx1, tx11, tx12, tx13, final_tx)) = build_tx_chain(node0);

assert!(
run_replay_tx(&net, node0, parent, 0, 1),
"parent sended expect nothing in orphan pool"
);
assert!(
run_replay_tx(&net, node0, tx1, 0, 2),
"tx1 is sent expect nothing in orphan pool"
);
assert!(
run_replay_tx(&net, node0, tx11, 0, 3),
"tx11 is sent expect nothing in orphan pool"
);
assert!(
run_replay_tx(&net, node0, tx12, 0, 4),
"tx12 is sent expect nothing in orphan pool"
);
assert!(
run_replay_tx(&net, node0, tx13, 0, 5),
"tx13 is sent expect nothing in orphan pool"
);
assert!(
run_replay_tx(&net, node0, final_tx, 0, 6),
"final_tx is sent expect nothing in orphan pool"
);
}
}

pub struct TxPoolOrphanReverse;
impl Spec for TxPoolOrphanReverse {
fn run(&self, nodes: &mut Vec<Node>) {
let node0 = &nodes[0];
let (net, (parent, tx1, tx11, tx12, tx13, final_tx)) = build_tx_chain(node0);

assert!(
run_replay_tx(&net, node0, final_tx, 1, 0),
"expect final_tx is in orphan pool"
);

assert!(
run_replay_tx(&net, node0, tx13, 2, 0),
"tx13 in orphan pool"
);
assert!(
run_replay_tx(&net, node0, tx12, 3, 0),
"tx12 is in orphan pool"
);
assert!(run_replay_tx(&net, node0, tx11, 4, 0), "tx11 is in orphan");

assert!(run_replay_tx(&net, node0, tx1, 5, 0), "tx1 is in orphan");

assert!(
run_replay_tx(&net, node0, parent, 0, 6),
"all is in pending"
);
}
}

pub struct TxPoolOrphanUnordered;
impl Spec for TxPoolOrphanUnordered {
fn run(&self, nodes: &mut Vec<Node>) {
let node0 = &nodes[0];
let (net, (parent, tx1, tx11, tx12, tx13, final_tx)) = build_tx_chain(node0);

assert!(
run_replay_tx(&net, node0, final_tx, 1, 0),
"expect final_tx is in orphan pool"
);

assert!(
run_replay_tx(&net, node0, tx11, 2, 0),
"tx11 in orphan pool"
);
let tx12_clone = tx12.clone();
assert!(
run_replay_tx(&net, node0, tx12, 3, 0),
"tx12 is in orphan pool"
);

// set tx12_clone with rpc
let ret = node0
.rpc_client()
.send_transaction_result(tx12_clone.data().into());
assert!(ret
.err()
.unwrap()
.to_string()
.contains("already exist in transaction_pool"));

assert!(
run_replay_tx(&net, node0, parent, 3, 1),
"parent is sent, should be in pending without change orphan pool"
);
assert!(
run_replay_tx(&net, node0, tx1, 1, 4),
"tx1 is sent, orphan pool only contains final_tx"
);

assert!(
run_replay_tx(&net, node0, tx13, 0, 6),
"tx13 is sent, orphan pool is empty"
);
}
}

pub struct TxPoolOrphanDoubleSpend;
impl Spec for TxPoolOrphanDoubleSpend {
fn run(&self, nodes: &mut Vec<Node>) {
let node0 = &nodes[0];
node0.mine_until_out_bootstrap_period();
let parent = node0.new_transaction_with_capacity(capacity_bytes!(800));

let script = node0.always_success_script();
let new_output1 = CellOutputBuilder::default()
.capacity(capacity_bytes!(200).pack())
.lock(script.clone())
.build();
let new_output2 = new_output1.clone();
let new_output3 = new_output1.clone();

let tx1 = parent
.as_advanced_builder()
.set_inputs(vec![CellInput::new(OutPoint::new(parent.hash(), 0), 0)])
.set_outputs(vec![new_output1, new_output2, new_output3])
.set_outputs_data(vec![Default::default(); 3])
.build();

let tx11 =
node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(100), 0, 0);
let tx12 =
node0.new_transaction_with_capacity_and_index(tx1.hash(), capacity_bytes!(120), 0, 0);

let mut net = Net::new(
"orphan_tx_test",
node0.consensus(),
vec![SupportProtocols::RelayV3],
);
net.connect(node0);

assert!(
run_replay_tx(&net, node0, tx11, 1, 0),
"tx11 in orphan pool"
);

assert!(
run_replay_tx(&net, node0, tx12, 2, 0),
"tx12 in orphan pool"
);
}
}
Loading

0 comments on commit 4cbb826

Please sign in to comment.