diff --git a/README.md b/README.md index ef0e3253cf0..597f73db732 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ TBD. ## Mining -Mining on testnet Rylai is for testing purpose only, the mining algorithm **WILL BE CHANGED SOON**. +Testnet Rylai uses the [Eaglesong](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0010-eaglesong/0010-eaglesong.md) mining algorithm, which the mainnet is going to use too. Mining on testnet is for testing purpose only. At this time Rylai will be **RESET** every two weeks. diff --git a/test/src/main.rs b/test/src/main.rs index c2ce585c75a..8a33ada4cfa 100644 --- a/test/src/main.rs +++ b/test/src/main.rs @@ -288,6 +288,12 @@ fn all_specs() -> SpecMap { Box::new(ProposeButNotCommit), Box::new(ProposeDuplicated), Box::new(ForkedTransaction), + Box::new(HandlingDescendantsOfProposed), + Box::new(HandlingDescendantsOfCommitted), + Box::new(ProposeOutOfOrder), + Box::new(SubmitTransactionWhenItsParentInGap), + Box::new(SubmitTransactionWhenItsParentInProposed), + Box::new(ProposeTransactionButParentNot), ]; specs.into_iter().map(|spec| (spec.name(), spec)).collect() } diff --git a/test/src/net.rs b/test/src/net.rs index 43b469e6e9f..c2c439d0750 100644 --- a/test/src/net.rs +++ b/test/src/net.rs @@ -179,7 +179,7 @@ impl Net { pub fn exit_ibd_mode(&self) -> BlockView { let block = self.nodes[0].new_block(None, None, None); self.nodes.iter().for_each(|node| { - node.submit_block(&block.data()); + node.submit_block(&block); }); block } diff --git a/test/src/node.rs b/test/src/node.rs index 95209235dc5..f0dc5773461 100644 --- a/test/src/node.rs +++ b/test/src/node.rs @@ -253,9 +253,9 @@ impl Node { &self.rpc_client } - pub fn submit_block(&self, block: &Block) -> Byte32 { + pub fn submit_block(&self, block: &BlockView) -> Byte32 { self.rpc_client() - .submit_block("".to_owned(), block.clone().into()) + .submit_block("".to_owned(), block.data().into()) .expect("submit_block failed") } @@ -271,7 +271,7 @@ impl Node { // generate a new block and submit it through rpc. pub fn generate_block(&self) -> Byte32 { - self.submit_block(&self.new_block(None, None, None).data()) + self.submit_block(&self.new_block(None, None, None)) } // Convenient way to construct an uncle block diff --git a/test/src/specs/mining/basic.rs b/test/src/specs/mining/basic.rs index 29abed182d1..ba8b5e1c83a 100644 --- a/test/src/specs/mining/basic.rs +++ b/test/src/specs/mining/basic.rs @@ -64,7 +64,7 @@ impl MiningBasic { // According to the first-received policy, // the first block is always the best block let rpc_client = node.rpc_client(); - assert_eq!(block1.hash(), node.submit_block(&block1.data())); + assert_eq!(block1.hash(), node.submit_block(&block1)); assert_eq!(block1.hash(), rpc_client.get_tip_header().hash.pack()); let template1 = rpc_client.get_block_template(None, None, None); @@ -76,7 +76,7 @@ impl MiningBasic { "templates keep same since block template cache", ); - assert_eq!(block2.hash(), node.submit_block(&block2.data())); + assert_eq!(block2.hash(), node.submit_block(&block2)); assert_eq!(block1.hash(), rpc_client.get_tip_header().hash.pack()); let template3 = rpc_client.get_block_template(None, None, None); assert_eq!(block1.hash(), template3.parent_hash.pack()); diff --git a/test/src/specs/mining/fee.rs b/test/src/specs/mining/fee.rs index 408ddfedcdc..698ee87b298 100644 --- a/test/src/specs/mining/fee.rs +++ b/test/src/specs/mining/fee.rs @@ -94,7 +94,7 @@ impl Spec for FeeOfMultipleMaxBlockProposalsLimit { (0..multiple).for_each(|_| { let block = node.new_block(None, None, None); - node.submit_block(&block.data()); + node.submit_block(&block); assert_eq!( max_block_proposals_limit as usize, block.union_proposal_ids_iter().count(), @@ -136,7 +136,7 @@ impl Spec for ProposeButNotCommit { // `target_node` propose `tx` feed_blocks.iter().for_each(|block| { - target_node.submit_block(&block.data()); + target_node.submit_block(&block); }); // `target_node` keeps growing, but it will never commit `tx` since its transactions_pool @@ -185,7 +185,7 @@ impl Spec for ProposeDuplicated { .uncle(uncle2) .build(); node.submit_transaction(tx); - node.submit_block(&block.data()); + node.submit_block(&block); let finalization_delay_length = node.consensus().finalization_delay_length(); node.generate_blocks(2 * finalization_delay_length as usize); diff --git a/test/src/specs/mining/size_limit.rs b/test/src/specs/mining/size_limit.rs index ebe13e924db..454030da604 100644 --- a/test/src/specs/mining/size_limit.rs +++ b/test/src/specs/mining/size_limit.rs @@ -13,7 +13,7 @@ impl Spec for TemplateSizeLimit { info!("Generate 1 block"); let blank_block = node.new_block(None, None, None); - node.submit_block(&blank_block.data()); + node.submit_block(&blank_block); let blank_block_size = blank_block.data().serialized_size_without_uncle_proposals(); info!("Generate 6 txs"); diff --git a/test/src/specs/mining/uncle.rs b/test/src/specs/mining/uncle.rs index 6e2a465fa41..186f96ac726 100644 --- a/test/src/specs/mining/uncle.rs +++ b/test/src/specs/mining/uncle.rs @@ -29,11 +29,11 @@ impl Spec for UncleInheritFromForkBlock { info!("(2) Force reorg, so that the parent of `uncle` become fork-block"); let longer_fork = (0..=target_node.get_tip_block_number()).map(|_| { let block = feed_node.new_block(None, None, None); - feed_node.submit_block(&block.data()); + feed_node.submit_block(&block); block }); longer_fork.for_each(|block| { - target_node.submit_block(&block.data()); + target_node.submit_block(&block); }); info!( @@ -56,7 +56,7 @@ impl Spec for UncleInheritFromForkBlock { .new_block_builder(None, None, None) .set_uncles(vec![uncle.as_uncle()]) .build(); - target_node.submit_block(&block.data()); + target_node.submit_block(&block); } } @@ -77,7 +77,7 @@ impl Spec for UncleInheritFromForkUncle { info!("(1) Build a chain which embedded `uncle_parent` as an uncle"); let uncle_parent = construct_uncle(target_node); - target_node.submit_block(&uncle_parent.data()); + target_node.submit_block(&uncle_parent); let uncle_child = uncle_parent .as_advanced_builder() @@ -99,11 +99,11 @@ impl Spec for UncleInheritFromForkUncle { info!("(2) Force reorg, so that `uncle_parent` become a fork-uncle"); let longer_fork = (0..=target_node.get_tip_block_number()).map(|_| { let block = feed_node.new_block(None, None, None); - feed_node.submit_block(&block.data()); + feed_node.submit_block(&block); block }); longer_fork.for_each(|block| { - target_node.submit_block(&block.data()); + target_node.submit_block(&block); }); info!("(3) Submit block with `uncle`, which is inherited from fork-uncle `uncle_parent`, should be failed"); @@ -124,7 +124,7 @@ impl Spec for UncleInheritFromForkUncle { .new_block_builder(None, None, None) .set_uncles(vec![uncle_child.as_uncle()]) .build(); - target_node.submit_block(&block.data()); + target_node.submit_block(&block); } } @@ -151,7 +151,7 @@ impl Spec for PackUnclesIntoEpochStarting { assert_eq!(current_epoch_end - 1, node.get_tip_block_number()); info!("(2) Submit the target uncle"); - node.submit_block(&uncle.data()); + node.submit_block(&uncle); info!("(3) Expect the next mining block(CURRENT_EPOCH_END) contains the target uncle"); let block = node.new_block(None, None, None); @@ -160,7 +160,7 @@ impl Spec for PackUnclesIntoEpochStarting { // Clear the uncles in the next block, we don't want to pack the target `uncle` now. info!("(4) Submit the next block with empty uncles"); let block_with_empty_uncles = block.as_advanced_builder().set_uncles(vec![]).build(); - node.submit_block(&block_with_empty_uncles.data()); + node.submit_block(&block_with_empty_uncles); info!("(5) Expect the next mining block(NEXT_EPOCH_START) not contains the target uncle"); let block = node.new_block(None, None, None); @@ -184,6 +184,6 @@ fn until_no_uncles_left(node: &Node) { if block.uncles().into_iter().count() == 0 { break; } - node.submit_block(&block.data()); + node.submit_block(&block); } } diff --git a/test/src/specs/relay/compact_block.rs b/test/src/specs/relay/compact_block.rs index 6085e1c111b..9db06a5700e 100644 --- a/test/src/specs/relay/compact_block.rs +++ b/test/src/specs/relay/compact_block.rs @@ -110,8 +110,7 @@ impl Spec for CompactBlockPrefilled { &node .new_block_builder(None, None, None) .proposal(new_tx.proposal_short_id()) - .build() - .data(), + .build(), ); node.generate_blocks(3); @@ -155,8 +154,7 @@ impl Spec for CompactBlockMissingFreshTxs { &node .new_block_builder(None, None, None) .proposal(new_tx.proposal_short_id()) - .build() - .data(), + .build(), ); node.generate_blocks(3); @@ -214,8 +212,7 @@ impl Spec for CompactBlockMissingNotFreshTxs { &node .new_block_builder(None, None, None) .proposal(new_tx.proposal_short_id()) - .build() - .data(), + .build(), ); node.generate_blocks(3); @@ -266,8 +263,7 @@ impl Spec for CompactBlockLoseGetBlockTransactions { &node0 .new_block_builder(None, None, None) .proposal(new_tx.proposal_short_id()) - .build() - .data(), + .build(), ); // Proposal a tx, and grow up into proposal window node0.generate_blocks(6); @@ -304,7 +300,7 @@ impl Spec for CompactBlockLoseGetBlockTransactions { ); // Submit the new block to node1. We expect node1 will relay the new block to node0. - node1.submit_block(&block.data()); + node1.submit_block(&block); node1.waiting_for_sync(node0, node1.get_tip_block().header().number()); } } @@ -333,8 +329,7 @@ impl Spec for CompactBlockRelayParentOfOrphanBlock { &node .new_block_builder(None, None, None) .proposal(new_tx.proposal_short_id()) - .build() - .data(), + .build(), ); node.generate_blocks(6); diff --git a/test/src/specs/sync/block_sync.rs b/test/src/specs/sync/block_sync.rs index 0ac0e16af59..e25bda926b3 100644 --- a/test/src/specs/sync/block_sync.rs +++ b/test/src/specs/sync/block_sync.rs @@ -5,7 +5,7 @@ use crate::{Net, Node, Spec, TestProtocol}; use ckb_jsonrpc_types::ChainInfo; use ckb_network::PeerIndex; use ckb_sync::NetworkProtocol; -use ckb_types::packed::{Block, Byte32}; +use ckb_types::packed::Byte32; use ckb_types::{ core::BlockView, packed::{self, SyncMessage}, @@ -72,8 +72,8 @@ impl Spec for BlockSyncWithUncle { let new_block1 = new_builder.clone().nonce(0.pack()).build(); let new_block2 = new_builder.clone().nonce(1.pack()).build(); - node1.submit_block(&new_block1.data()); - node1.submit_block(&new_block2.data()); + node1.submit_block(&new_block1); + node1.submit_block(&new_block2); let uncle = if node1.get_tip_block() == new_block1 { new_block2.as_uncle() @@ -87,8 +87,7 @@ impl Spec for BlockSyncWithUncle { &block_builder .clone() .set_uncles(vec![uncle.clone()]) - .build() - .data(), + .build(), ); target.connect(node1); @@ -269,7 +268,7 @@ impl Spec for BlockSyncOrphanBlocks { let mut blocks: Vec = (1..=5) .map(|_| { let block = node1.new_block(None, None, None); - node1.submit_block(&block.data()); + node1.submit_block(&block); block }) .collect(); @@ -327,7 +326,7 @@ impl Spec for BlockSyncNonAncestorBestBlocks { .timestamp((a.timestamp() + 1).pack()) .build(); assert_ne!(a.hash(), b.hash()); - node1.submit_block(&b.data()); + node1.submit_block(&b); net.connect(node0); let (peer_id, _, _) = net @@ -381,14 +380,8 @@ impl Spec for RequestUnverifiedBlocks { fork_chain.iter().for_each(|block| { target_node.submit_block(block); }); - let main_hashes: Vec<_> = main_chain - .iter() - .map(|block| block.calc_header_hash()) - .collect(); - let fork_hashes: Vec<_> = fork_chain - .iter() - .map(|block| block.calc_header_hash()) - .collect(); + let main_hashes: Vec<_> = main_chain.iter().map(|block| block.hash()).collect(); + let fork_hashes: Vec<_> = fork_chain.iter().map(|block| block.hash()).collect(); // Request for the blocks on `main_chain` and `fork_chain`. We should only receive the // `main_chain` blocks @@ -422,7 +415,7 @@ impl Spec for RequestUnverifiedBlocks { } } -fn build_forks(node: &Node, offsets: &[u64]) -> Vec { +fn build_forks(node: &Node, offsets: &[u64]) -> Vec { let rpc_client = node.rpc_client(); let mut blocks = Vec::with_capacity(offsets.len()); for offset in offsets.iter() { diff --git a/test/src/specs/sync/chain_forks.rs b/test/src/specs/sync/chain_forks.rs index da4de84adcb..94862a74b91 100644 --- a/test/src/specs/sync/chain_forks.rs +++ b/test/src/specs/sync/chain_forks.rs @@ -295,7 +295,7 @@ impl Spec for ChainFork5 { .build() } }; - node1.submit_block(&block.data()); + node1.submit_block(&block); assert_eq!(15, node1.rpc_client().get_tip_block_number()); info!("Generate 1 blocks (F) with spent transaction on node1"); let block = node1.new_block(None, None, None); @@ -589,8 +589,8 @@ impl Spec for ForksContainSameUncle { node_b.generate_block(); info!("(2) Add `uncle` into different forks in node_a and node_b"); - node_a.submit_block(&uncle.data()); - node_b.submit_block(&uncle.data()); + node_a.submit_block(&uncle); + node_b.submit_block(&uncle); let block_a = node_a .new_block_builder(None, None, None) .set_uncles(vec![uncle.as_uncle()]) @@ -600,8 +600,8 @@ impl Spec for ForksContainSameUncle { .set_uncles(vec![uncle.as_uncle()]) .timestamp((block_a.timestamp() + 2).pack()) .build(); - node_a.submit_block(&block_a.data()); - node_b.submit_block(&block_b.data()); + node_a.submit_block(&block_a); + node_b.submit_block(&block_b); info!("(3) Make node_b's fork longer(to help check whether is synchronized)"); node_b.generate_block(); @@ -626,8 +626,8 @@ impl Spec for ForkedTransaction { let finalization_delay_length = node0.consensus().finalization_delay_length(); (0..=finalization_delay_length).for_each(|_| { let block = node0.new_block(None, None, None); - node0.submit_block(&block.data()); - node1.submit_block(&block.data()); + node0.submit_block(&block); + node1.submit_block(&block); }); net.exit_ibd_mode(); @@ -656,7 +656,7 @@ impl Spec for ForkedTransaction { { (fixed_point..=node1.get_tip_block_number()).for_each(|number| { let block = node1.get_block_by_number(number); - node0.submit_block(&block.data()); + node0.submit_block(&block); }); let tx_status = node0.rpc_client().get_transaction(tx.hash()); assert!(tx_status.is_none(), "node0 maintains tx in unverified fork"); @@ -670,7 +670,7 @@ impl Spec for ForkedTransaction { { (fixed_point..=node0.get_tip_block_number()).for_each(|number| { let block = node0.get_block_by_number(number); - node1.submit_block(&block.data()); + node1.submit_block(&block); }); let is_pending = |tx_status: &TransactionWithStatus| { diff --git a/test/src/specs/sync/ibd_process.rs b/test/src/specs/sync/ibd_process.rs index ee2b02c7c59..a5176c9dca7 100644 --- a/test/src/specs/sync/ibd_process.rs +++ b/test/src/specs/sync/ibd_process.rs @@ -139,6 +139,17 @@ impl Spec for IBDProcessWithWhiteList { } node6.disconnect(node0); + + // Make sure both sides are disconnected + let is_disconnect_already = wait_until(10, || { + let peers = rpc_client0.get_peers(); + peers.iter().any(|peer| peer.node_id == node6.node_id()) + }); + + if is_disconnect_already { + panic!("node6 can't disconnect with node0"); + } + node6.generate_blocks(2); let generate_res = wait_until(10, || net.nodes[6].get_tip_block_number() == 2); @@ -147,7 +158,8 @@ impl Spec for IBDProcessWithWhiteList { panic!("node6 can't generate blocks to 2"); } - node0.connect_uncheck(node6); + // Make sure node0 re-connect with node6 + node0.connect(node6); // IBD only with outbound/whitelist node let rpc_client1 = node1.rpc_client(); diff --git a/test/src/specs/tx_pool/descendant.rs b/test/src/specs/tx_pool/descendant.rs new file mode 100644 index 00000000000..3b5dec1616d --- /dev/null +++ b/test/src/specs/tx_pool/descendant.rs @@ -0,0 +1,261 @@ +use crate::specs::tx_pool::utils::prepare_tx_family; +use crate::{Net, Node, Spec}; +use ckb_types::core::{BlockView, TransactionView}; +use std::collections::HashSet; + +// Convention: +// * `tx_family` is a set of relative transactions, `tx_family.a <- tx_family.b <- +// tx_family.c <- tx_family.d <- tx_family.e`, note that `x <- y` represents `x` is the parent +// transaction of `y`. + +pub struct HandlingDescendantsOfProposed; + +impl Spec for HandlingDescendantsOfProposed { + crate::name!("handling_descendants_of_proposed"); + + // Case: This case intends to test the handling of proposed transactions. + // We construct a scenario that although both `tx_family.a` and `tx_family.b` are in + // txpool, but only propose `tx_family.a`. We expect that after proposing + // `tx_family.a`, miner is able to propose `tx_family.b` in the next blocks. + fn run(&self, net: &mut Net) { + let node = &net.nodes[0]; + let window = node.consensus().tx_proposal_window(); + node.generate_blocks((window.farthest() + 2) as usize); + + // 1. Put `tx_family` into pending-pool. + let family = prepare_tx_family(node); + node.submit_transaction(family.a()); + node.submit_transaction(family.b()); + + // 2. Propose `tx_family.a` only, then we expect `tx_family.b` being proposed in the next + // blocks, even after `tx_family.a` expiring, out of `tx_family.a`'s proposal window + node.submit_block(&propose(node, &[family.a()])); + (0..=window.farthest() + window.closest()).for_each(|_| { + let block = node.new_block(None, None, None); + assert!( + block + .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(), + ); + + node.submit_block(&blank(node)); // continuously submit blank blocks. + }); + + // 3. At this point, `tx_family.a` has been moved in pending-pool since it is + // out of proposal-window. Hence miner will propose `tx_family.a` and `tx_family.b` + // in the next blocks. + let block = node.new_block(None, None, None); + assert_eq!( + vec![ + family.a().proposal_short_id(), + family.b().proposal_short_id() + ] + .into_iter() + .collect::>(), + block.union_proposal_ids(), + ); + } +} + +pub struct HandlingDescendantsOfCommitted; + +impl Spec for HandlingDescendantsOfCommitted { + crate::name!("handling_descendants_of_committed"); + + // Case: This case intends to test the handling descendants of committed transactions. + // We construct a scenario that although both `tx_family.a` and `tx_family.b` are in + // txpool, but only propose and commit `tx_family.a`. We expect that after proposing + // `tx_family.a`, miner is able to propose `tx_family.b` in the next blocks. + fn run(&self, net: &mut Net) { + let node = &net.nodes[0]; + let window = node.consensus().tx_proposal_window(); + node.generate_blocks((window.farthest() + 2) as usize); + + // 1. Put `tx_family` into pending-pool. + let family = prepare_tx_family(node); + node.submit_transaction(family.a()); + node.submit_transaction(family.b()); + + // 2. Propose and commit `tx_family.a` only + node.submit_block(&propose(node, &[family.a()])); + (0..window.closest()).for_each(|_| { + node.submit_block(&blank(node)); + }); // continuously submit blank blocks. + node.submit_block(&commit(node, &[family.a()])); + + let block = node.new_block(None, None, None); + assert_eq!( + vec![family.b().proposal_short_id()] + .into_iter() + .collect::>(), + block.union_proposal_ids(), + ); + node.submit_block(&block); + } +} + +pub struct ProposeOutOfOrder; + +impl Spec for ProposeOutOfOrder { + crate::name!("propose_out_of_order"); + + // Case: Even if the proposals is out of order of relatives(child transaction + // proposed before its parent transaction), miner commits in order of + // relatives + // 1. Put `tx_family` into pending-pool. + // 2. Propose `[tx_family.b, tx_family.a]`, then continuously submit blank blocks. + // 3. Expect committing `[tx_family.a, tx_family.b]`. + fn run(&self, net: &mut Net) { + let node = &net.nodes[0]; + let window = node.consensus().tx_proposal_window(); + node.generate_blocks((window.farthest() + 2) as usize); + + // 1. Put `tx_family` into pending-pool. + let family = prepare_tx_family(node); + node.submit_transaction(family.a()); + node.submit_transaction(family.b()); + + // 2. Propose `[tx_family.b, tx_family.a]`, then continuously submit blank blocks. + node.submit_block(&propose(node, &[family.b(), family.a()])); + (0..window.closest()).for_each(|_| { + node.submit_block(&blank(node)); // continuously submit blank blocks. + }); + + // 3. Expect committing `[tx_family.a, tx_family.b]`. + let block = node.new_block(None, None, None); + assert_eq!( + [family.a().to_owned(), family.b().to_owned()], + block.transactions()[1..], + ); + node.submit_block(&block); + } +} + +pub struct SubmitTransactionWhenItsParentInGap; + +impl Spec for SubmitTransactionWhenItsParentInGap { + crate::name!("submit_transaction_when_its_parent_in_gap"); + + // Case: This case intends to test that submit a transaction which its parent is in gap-pool + fn run(&self, net: &mut Net) { + let node = &net.nodes[0]; + let window = node.consensus().tx_proposal_window(); + node.generate_blocks((window.farthest() + 2) as usize); + + // 1. Propose `tx_family.a` into gap-pool. + let family = prepare_tx_family(node); + node.submit_transaction(family.a()); + node.submit_block(&propose(node, &[family.a()])); + + // 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(None, None, None); + assert!( + block + .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(), + ); + node.submit_block(&block); + } +} + +pub struct SubmitTransactionWhenItsParentInProposed; + +impl Spec for SubmitTransactionWhenItsParentInProposed { + crate::name!("submit_transaction_when_its_parent_in_proposed"); + + // Case: This case intends to test that submit a transaction which its parent is in proposed-pool + fn run(&self, net: &mut Net) { + let node = &net.nodes[0]; + let window = node.consensus().tx_proposal_window(); + node.generate_blocks((window.farthest() + 2) as usize); + + // 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()])); + (0..=window.closest()).for_each(|_| { + node.submit_block(&blank(node)); + }); + + // 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(None, None, None); + assert!( + block + .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(), + ); + node.submit_block(&block); + } +} + +pub struct ProposeTransactionButParentNot; + +impl Spec for ProposeTransactionButParentNot { + crate::name!("propose_transaction_but_parent_not"); + + // Case: A proposed transaction cannot be committed if its parent has not been committed + fn run(&self, net: &mut Net) { + let node = &net.nodes[0]; + let window = node.consensus().tx_proposal_window(); + node.generate_blocks((window.farthest() + 2) as usize); + + // 1. Propose `tx_family.a` and `tx_family.b` into pending-pool. + let family = prepare_tx_family(node); + node.submit_transaction(family.a()); + node.submit_transaction(family.b()); + + // 2. Propose `tx_family.b`, but `tx_family.a` not, then continuously submit blank blocks. + // In the time, miner should not commit `tx_family.b` as its parent, `tx_family.a` has + // not been not proposed and committed yet. + node.submit_block(&propose(node, &[family.b()])); + (0..window.closest()).for_each(|_| { + node.submit_block(&blank(node)); // continuously submit blank blocks. + }); + let block = node.new_block(None, None, None); + assert_eq!(block.transactions()[1..], []); + + let block = commit(node, &[family.b()]); + node.rpc_client() + .submit_block("".to_string(), block.data().into()) + .expect_err("should be failed as it contains invalid transaction"); + } +} + +// Return a block with empty proposed-zone and committed-zone contains `committed` +fn commit(node: &Node, committed: &[&TransactionView]) -> BlockView { + let committed = committed + .iter() + .map(|t| t.to_owned().to_owned()) + .collect::>(); + blank(node) + .as_advanced_builder() + .transactions(committed) + .build() +} + +// Return a block with proposed-zone contains `proposals` and empty committed-zone +fn propose(node: &Node, proposals: &[&TransactionView]) -> BlockView { + let proposals = proposals.iter().map(|tx| tx.proposal_short_id()); + blank(node) + .as_advanced_builder() + .proposals(proposals) + .build() +} + +// Return a block with empty proposed-zone and empty committed-zone +fn blank(node: &Node) -> BlockView { + let example = node.new_block(None, None, None); + example + .as_advanced_builder() + .set_proposals(vec![]) + .set_transactions(vec![example.transaction(0).unwrap()]) // cellbase + .build() +} diff --git a/test/src/specs/tx_pool/mod.rs b/test/src/specs/tx_pool/mod.rs index 0360ae6dbae..0361f893c56 100644 --- a/test/src/specs/tx_pool/mod.rs +++ b/test/src/specs/tx_pool/mod.rs @@ -1,5 +1,6 @@ mod cellbase_maturity; mod depend_tx_in_same_block; +mod descendant; mod different_txs_with_same_input; mod limit; mod pool_reconcile; @@ -7,10 +8,12 @@ mod pool_resurrect; mod reference_header_maturity; mod send_low_fee_rate_tx; mod send_secp_tx; +mod utils; mod valid_since; pub use cellbase_maturity::*; pub use depend_tx_in_same_block::*; +pub use descendant::*; pub use different_txs_with_same_input::*; pub use limit::*; pub use pool_reconcile::*; diff --git a/test/src/specs/tx_pool/utils.rs b/test/src/specs/tx_pool/utils.rs new file mode 100644 index 00000000000..029c9ba7c55 --- /dev/null +++ b/test/src/specs/tx_pool/utils.rs @@ -0,0 +1,74 @@ +use crate::Node; +use ckb_types::core::TransactionView; +use ckb_types::packed::{CellInput, OutPoint}; +use ckb_types::prelude::*; + +/// `TxFamily` used to represent a set of relative transactions, +/// `TxFamily.get(0)` is the parent of `TxFamily.get(1)`, +/// `TxFamily.get(1)` is the parent of `TxFamily.get(2)`, +/// and so on. +pub struct TxFamily { + transactions: Vec, +} + +impl TxFamily { + pub fn init(ancestor_transaction: TransactionView) -> Self { + const FAMILY_PEOPLES: usize = 5; + + let mut transactions = vec![ancestor_transaction]; + while transactions.len() < FAMILY_PEOPLES { + let parent = transactions.last().unwrap(); + let child = parent + .as_advanced_builder() + .set_inputs(vec![{ + CellInput::new_builder() + .previous_output(OutPoint::new(parent.hash(), 0)) + .build() + }]) + .set_outputs(vec![parent.output(0).unwrap()]) + .build(); + transactions.push(child); + } + + TxFamily { transactions } + } + + pub fn get(&self, index: usize) -> &TransactionView { + self.transactions + .get(index) + .expect("out of index of tx-family") + } + + #[allow(dead_code)] + pub fn a(&self) -> &TransactionView { + self.get(0) + } + + #[allow(dead_code)] + pub fn b(&self) -> &TransactionView { + self.get(1) + } + + #[allow(dead_code)] + pub fn c(&self) -> &TransactionView { + self.get(2) + } + + #[allow(dead_code)] + pub fn d(&self) -> &TransactionView { + self.get(3) + } + + #[allow(dead_code)] + pub fn e(&self) -> &TransactionView { + self.get(4) + } +} + +pub fn prepare_tx_family(node: &Node) -> TxFamily { + // Ensure the generated transactions are conform to the cellbase mature rule + let ancestor = node.new_transaction_spend_tip_cellbase(); + node.generate_blocks(node.consensus().cellbase_maturity().index() as usize); + + TxFamily::init(ancestor) +} diff --git a/test/src/utils.rs b/test/src/utils.rs index 2f0161565f9..97a84d3c5e8 100644 --- a/test/src/utils.rs +++ b/test/src/utils.rs @@ -116,12 +116,11 @@ pub fn build_relay_tx_hashes(hashes: &[Byte32]) -> Bytes { RelayMessage::new_builder().set(content).build().as_bytes() } -pub fn new_block_with_template(template: BlockTemplate) -> Block { +pub fn new_block_with_template(template: BlockTemplate) -> BlockView { Block::from(template) .as_advanced_builder() .nonce(rand::random::().pack()) .build() - .data() } pub fn wait_until(secs: u64, mut f: F) -> bool diff --git a/tx-pool/src/component/pending.rs b/tx-pool/src/component/pending.rs index c7efd6bf975..798d35d0836 100644 --- a/tx-pool/src/component/pending.rs +++ b/tx-pool/src/component/pending.rs @@ -47,6 +47,10 @@ impl PendingQueue { self.inner.remove_entry_and_descendants(id) } + pub(crate) fn remove_entry(&mut self, id: &ProposalShortId) -> Option { + self.inner.remove_entry(id) + } + /// find all ancestors from pool pub(crate) fn get_ancestors(&self, tx_short_id: &ProposalShortId) -> HashSet { self.inner.get_ancestors(tx_short_id) diff --git a/tx-pool/src/process/chain_reorg.rs b/tx-pool/src/process/chain_reorg.rs index 7c905a14039..2d5a123bb37 100644 --- a/tx-pool/src/process/chain_reorg.rs +++ b/tx-pool/src/process/chain_reorg.rs @@ -156,7 +156,7 @@ pub fn update_tx_pool_for_reorg( } } removed.into_iter().for_each(|id| { - tx_pool.pending.remove_entry_and_descendants(&id); + tx_pool.pending.remove_entry(&id); }); // try move conflict to proposed