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

Fix orphan pool for long pending tx issues #4199

Merged
merged 6 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading