Skip to content

Commit 6b4b039

Browse files
kariyclaude
andcommitted
test(rpc): rewrite txpool tests to use pool directly instead of spawning nodes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 77d9ed7 commit 6b4b039

File tree

1 file changed

+149
-119
lines changed

1 file changed

+149
-119
lines changed
Lines changed: 149 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,209 @@
1-
use katana_genesis::constant::DEFAULT_STRK_FEE_TOKEN_ADDRESS;
2-
use katana_primitives::{felt, Felt};
3-
use katana_rpc_api::txpool::TxPoolApiClient;
4-
use katana_utils::TestNode;
5-
use starknet::accounts::Account;
6-
use starknet::core::types::Call;
7-
use starknet::macros::selector;
8-
9-
/// Helper: creates a test node with no_mining so transactions stay in the pool.
10-
async fn setup_no_mining_node() -> TestNode {
11-
let mut config = katana_utils::node::test_config();
12-
config.sequencing.no_mining = true;
13-
TestNode::new_with_config(config).await
1+
use katana_pool::ordering::FiFo;
2+
use katana_pool::pool::Pool;
3+
use katana_pool::validation::NoopValidator;
4+
use katana_pool::{PoolTransaction, TransactionPool};
5+
use katana_primitives::contract::{ContractAddress, Nonce};
6+
use katana_primitives::transaction::TxHash;
7+
use katana_primitives::Felt;
8+
use katana_rpc_api::txpool::TxPoolApiServer;
9+
use katana_rpc_server::txpool::TxPoolApi;
10+
11+
// -- Mock transaction type ---------------------------------------------------
12+
13+
#[derive(Clone, Debug)]
14+
struct MockTx {
15+
hash: TxHash,
16+
nonce: Nonce,
17+
sender: ContractAddress,
18+
max_fee: u128,
19+
tip: u64,
1420
}
1521

16-
/// Helper: submits a simple ERC-20 transfer invoke transaction.
17-
/// Returns the transaction hash.
18-
async fn send_transfer(
19-
account: &starknet::accounts::SingleOwnerAccount<
20-
starknet::providers::JsonRpcClient<starknet::providers::jsonrpc::HttpTransport>,
21-
starknet::signers::LocalWallet,
22-
>,
23-
) -> Felt {
24-
let to = DEFAULT_STRK_FEE_TOKEN_ADDRESS.into();
25-
let selector = selector!("transfer");
26-
let calldata = vec![felt!("0x1"), felt!("0x1"), Felt::ZERO];
27-
28-
let res = account
29-
.execute_v3(vec![Call { to, selector, calldata }])
30-
.l2_gas(100_000_000_000)
31-
.send()
32-
.await
33-
.unwrap();
34-
35-
res.transaction_hash
22+
impl MockTx {
23+
fn new(sender: ContractAddress, nonce: u64) -> Self {
24+
Self {
25+
hash: TxHash::from(Felt::from(rand::random::<u128>())),
26+
nonce: Nonce::from(nonce),
27+
sender,
28+
max_fee: 1000,
29+
tip: 10,
30+
}
31+
}
3632
}
3733

34+
impl PoolTransaction for MockTx {
35+
fn hash(&self) -> TxHash {
36+
self.hash
37+
}
38+
fn nonce(&self) -> Nonce {
39+
self.nonce
40+
}
41+
fn sender(&self) -> ContractAddress {
42+
self.sender
43+
}
44+
fn max_fee(&self) -> u128 {
45+
self.max_fee
46+
}
47+
fn tip(&self) -> u64 {
48+
self.tip
49+
}
50+
}
51+
52+
// -- Helpers -----------------------------------------------------------------
53+
54+
type TestPool = Pool<MockTx, NoopValidator<MockTx>, FiFo<MockTx>>;
55+
56+
fn test_pool() -> TestPool {
57+
Pool::new(NoopValidator::new(), FiFo::new())
58+
}
59+
60+
fn sender_a() -> ContractAddress {
61+
ContractAddress::from(Felt::from(0xA))
62+
}
63+
64+
fn sender_b() -> ContractAddress {
65+
ContractAddress::from(Felt::from(0xB))
66+
}
67+
68+
// -- Tests -------------------------------------------------------------------
69+
3870
#[tokio::test]
39-
async fn txpool_status_empty_pool() {
40-
let node = setup_no_mining_node().await;
41-
let client = node.rpc_http_client();
71+
async fn status_empty_pool() {
72+
let pool = test_pool();
73+
let api = TxPoolApi::new(pool);
4274

43-
let status = client.txpool_status().await.unwrap();
75+
let status = api.txpool_status().await.unwrap();
4476
assert_eq!(status.pending, 0);
4577
assert_eq!(status.queued, 0);
4678
}
4779

4880
#[tokio::test]
49-
async fn txpool_status_after_submit() {
50-
let node = setup_no_mining_node().await;
51-
let client = node.rpc_http_client();
52-
let account = node.account();
53-
54-
send_transfer(&account).await;
81+
async fn status_after_add() {
82+
let pool = test_pool();
83+
pool.add_transaction(MockTx::new(sender_a(), 0)).await.unwrap();
5584

56-
let status = client.txpool_status().await.unwrap();
85+
let api = TxPoolApi::new(pool);
86+
let status = api.txpool_status().await.unwrap();
5787
assert_eq!(status.pending, 1);
5888
assert_eq!(status.queued, 0);
5989
}
6090

6191
#[tokio::test]
62-
async fn txpool_content_populated() {
63-
let node = setup_no_mining_node().await;
64-
let client = node.rpc_http_client();
65-
let account = node.account();
92+
async fn content_populated() {
93+
let pool = test_pool();
94+
let tx = MockTx::new(sender_a(), 0);
95+
let expected_hash = tx.hash;
96+
pool.add_transaction(tx).await.unwrap();
6697

67-
let tx_hash = send_transfer(&account).await;
98+
let api = TxPoolApi::new(pool);
99+
let content = api.txpool_content().await.unwrap();
68100

69-
let content = client.txpool_content().await.unwrap();
70-
71-
// Should have exactly one sender in pending
72101
assert_eq!(content.pending.len(), 1);
73102
assert!(content.queued.is_empty());
74103

75-
let sender_addr = account.address().into();
76-
let sender_txs = content.pending.get(&sender_addr).expect("sender should be present");
104+
let sender_txs = content.pending.get(&sender_a()).expect("sender should be present");
77105
assert_eq!(sender_txs.len(), 1);
78106

79-
// Verify the transaction fields
80107
let tx_entry = sender_txs.values().next().unwrap();
81-
assert_eq!(tx_entry.hash, tx_hash);
82-
assert_eq!(tx_entry.sender, sender_addr);
108+
assert_eq!(tx_entry.hash, expected_hash);
109+
assert_eq!(tx_entry.sender, sender_a());
110+
assert_eq!(tx_entry.nonce, Nonce::from(0u64));
111+
assert_eq!(tx_entry.max_fee, 1000);
112+
assert_eq!(tx_entry.tip, 10);
83113
}
84114

85115
#[tokio::test]
86-
async fn txpool_content_from_filters_by_address() {
87-
let node = setup_no_mining_node().await;
88-
let client = node.rpc_http_client();
89-
let account = node.account();
90-
91-
send_transfer(&account).await;
116+
async fn content_from_filters_by_address() {
117+
let pool = test_pool();
118+
pool.add_transaction(MockTx::new(sender_a(), 0)).await.unwrap();
119+
pool.add_transaction(MockTx::new(sender_b(), 0)).await.unwrap();
92120

93-
let sender_addr = account.address().into();
121+
let api = TxPoolApi::new(pool);
94122

95-
// Filter by the actual sender — should find the transaction
96-
let content = client.txpool_content_from(sender_addr).await.unwrap();
123+
// Filter by sender_a — should only see sender_a's transaction
124+
let content = api.txpool_content_from(sender_a()).await.unwrap();
97125
assert_eq!(content.pending.len(), 1);
98-
assert!(content.pending.contains_key(&sender_addr));
126+
assert!(content.pending.contains_key(&sender_a()));
127+
assert!(!content.pending.contains_key(&sender_b()));
99128

100129
// Filter by an unrelated address — should be empty
101-
let other_addr = felt!("0xdead").into();
102-
let content = client.txpool_content_from(other_addr).await.unwrap();
130+
let other = ContractAddress::from(Felt::from(0xDEAD));
131+
let content = api.txpool_content_from(other).await.unwrap();
103132
assert!(content.pending.is_empty());
104133
}
105134

106135
#[tokio::test]
107-
async fn txpool_inspect_format() {
108-
let node = setup_no_mining_node().await;
109-
let client = node.rpc_http_client();
110-
let account = node.account();
136+
async fn inspect_format() {
137+
let pool = test_pool();
138+
pool.add_transaction(MockTx::new(sender_a(), 0)).await.unwrap();
111139

112-
send_transfer(&account).await;
113-
114-
let inspect = client.txpool_inspect().await.unwrap();
140+
let api = TxPoolApi::new(pool);
141+
let inspect = api.txpool_inspect().await.unwrap();
115142

116143
assert_eq!(inspect.pending.len(), 1);
117144
assert!(inspect.queued.is_empty());
118145

119-
let sender_addr = account.address().into();
120-
let summaries = inspect.pending.get(&sender_addr).expect("sender should be present");
146+
let summaries = inspect.pending.get(&sender_a()).expect("sender should be present");
121147
let summary = summaries.values().next().unwrap();
122148

123-
// The summary should contain key fields
124149
assert!(summary.contains("hash="), "summary should contain hash: {summary}");
125150
assert!(summary.contains("nonce="), "summary should contain nonce: {summary}");
126151
assert!(summary.contains("max_fee="), "summary should contain max_fee: {summary}");
127152
assert!(summary.contains("tip="), "summary should contain tip: {summary}");
128153
}
129154

130155
#[tokio::test]
131-
async fn txpool_cleared_after_block_produced() {
132-
// In instant mining mode, transactions are removed from the pool after the
133-
// block is produced via the BlockProductionTask polling loop.
134-
let node = TestNode::new().await;
135-
let client = node.rpc_http_client();
136-
let account = node.account();
137-
let provider = node.starknet_rpc_client();
138-
139-
let tx_hash = send_transfer(&account).await;
140-
141-
// Wait for the transaction to be included in a block
142-
katana_utils::TxWaiter::new(tx_hash, &provider).await.unwrap();
143-
144-
// After the block is produced the pool should be drained.
145-
// Poll briefly in case removal is slightly async.
146-
let mut attempts = 0;
147-
loop {
148-
let status = client.txpool_status().await.unwrap();
149-
if status.pending == 0 {
150-
break;
151-
}
152-
attempts += 1;
153-
assert!(attempts < 50, "pool not drained after mining (pending={})", status.pending);
154-
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
156+
async fn multiple_transactions_same_sender() {
157+
let pool = test_pool();
158+
for nonce in 0..3 {
159+
pool.add_transaction(MockTx::new(sender_a(), nonce)).await.unwrap();
155160
}
156161

157-
let content = client.txpool_content().await.unwrap();
158-
assert!(content.pending.is_empty());
162+
let api = TxPoolApi::new(pool);
163+
164+
let status = api.txpool_status().await.unwrap();
165+
assert_eq!(status.pending, 3);
166+
167+
let content = api.txpool_content().await.unwrap();
168+
let sender_txs = content.pending.get(&sender_a()).expect("sender should be present");
169+
assert_eq!(sender_txs.len(), 3);
159170
}
160171

161172
#[tokio::test]
162-
async fn txpool_multiple_transactions() {
163-
let node = setup_no_mining_node().await;
164-
let client = node.rpc_http_client();
165-
let account = node.account();
166-
167-
// Submit 3 transactions
168-
for _ in 0..3 {
169-
send_transfer(&account).await;
170-
}
173+
async fn multiple_senders() {
174+
let pool = test_pool();
175+
pool.add_transaction(MockTx::new(sender_a(), 0)).await.unwrap();
176+
pool.add_transaction(MockTx::new(sender_a(), 1)).await.unwrap();
177+
pool.add_transaction(MockTx::new(sender_b(), 0)).await.unwrap();
178+
179+
let api = TxPoolApi::new(pool);
171180

172-
let status = client.txpool_status().await.unwrap();
181+
let status = api.txpool_status().await.unwrap();
173182
assert_eq!(status.pending, 3);
174183

175-
let content = client.txpool_content().await.unwrap();
176-
let sender_addr = account.address().into();
177-
let sender_txs = content.pending.get(&sender_addr).expect("sender should be present");
178-
assert_eq!(sender_txs.len(), 3);
184+
let content = api.txpool_content().await.unwrap();
185+
assert_eq!(content.pending.len(), 2);
186+
assert_eq!(content.pending.get(&sender_a()).unwrap().len(), 2);
187+
assert_eq!(content.pending.get(&sender_b()).unwrap().len(), 1);
188+
}
189+
190+
#[tokio::test]
191+
async fn pool_drained_after_remove() {
192+
let pool = test_pool();
193+
let tx = MockTx::new(sender_a(), 0);
194+
let hash = tx.hash;
195+
pool.add_transaction(tx).await.unwrap();
196+
197+
let api = TxPoolApi::new(pool.clone());
198+
199+
let status = api.txpool_status().await.unwrap();
200+
assert_eq!(status.pending, 1);
201+
202+
pool.remove_transactions(&[hash]);
203+
204+
let status = api.txpool_status().await.unwrap();
205+
assert_eq!(status.pending, 0);
206+
207+
let content = api.txpool_content().await.unwrap();
208+
assert!(content.pending.is_empty());
179209
}

0 commit comments

Comments
 (0)