Skip to content

Commit a336f6b

Browse files
apollo_mempool: add delay support for transactions with proofs (#13406)
Add ProofTransaction variant to DelayedTx enum, proof_tx_delay config field, MEMPOOL_DELAYED_PROOF_TXS_SIZE metric, and delayed_proof_txs snapshot field. Parameterize existing declare delay tests to cover both declare and proof tx delay behavior. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d1e14c0 commit a336f6b

10 files changed

Lines changed: 213 additions & 101 deletions

File tree

crates/apollo_deployments/resources/app_configs/mempool_config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"mempool_config.dynamic_config.transaction_ttl": 300,
33
"mempool_config.static_config.capacity_in_bytes": 1073741824,
44
"mempool_config.static_config.committed_nonce_retention_block_count": 100,
5-
"mempool_config.static_config.declare_delay": 20,
5+
"mempool_config.static_config.declare_delay": 20.0,
6+
"mempool_config.static_config.proof_tx_delay": 0.5,
67
"mempool_config.static_config.enable_fee_escalation": true,
78
"mempool_config.static_config.fee_escalation_percentage": 10
89
}

crates/apollo_deployments/resources/app_configs/replacer_mempool_config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"mempool_config.dynamic_config.transaction_ttl": "$$$_MEMPOOL_CONFIG-DYNAMIC_CONFIG-TRANSACTION_TTL_$$$",
33
"mempool_config.static_config.capacity_in_bytes": 1073741824,
44
"mempool_config.static_config.committed_nonce_retention_block_count": 100,
5-
"mempool_config.static_config.declare_delay": 20,
5+
"mempool_config.static_config.declare_delay": 20.0,
6+
"mempool_config.static_config.proof_tx_delay": 0.5,
67
"mempool_config.static_config.enable_fee_escalation": true,
78
"mempool_config.static_config.fee_escalation_percentage": 10
89
}

crates/apollo_mempool/src/fee_mempool_test.rs

Lines changed: 138 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ use starknet_api::test_utils::invoke::internal_invoke_tx;
2626
use starknet_api::test_utils::valid_resource_bounds_for_testing;
2727
use starknet_api::transaction::fields::TransactionSignature;
2828
use starknet_api::transaction::TransactionHash;
29-
use starknet_api::{contract_address, declare_tx_args, felt, invoke_tx_args, nonce, tx_hash};
29+
use starknet_api::{
30+
contract_address,
31+
declare_tx_args,
32+
felt,
33+
invoke_tx_args,
34+
nonce,
35+
proof_facts,
36+
tx_hash,
37+
};
3038

3139
use super::DelayedQueues;
3240
use crate::communication::MempoolCommunicationWrapper;
@@ -45,6 +53,7 @@ use crate::test_utils::{
4553
commit_block,
4654
declare_add_tx_input,
4755
get_txs_and_assert_expected,
56+
proof_tx_add_tx_input,
4857
validate_tx,
4958
validate_tx_expect_error,
5059
MempoolMetrics,
@@ -150,7 +159,10 @@ impl MempoolTestContentBuilder {
150159
fn build_full_mempool(self) -> Mempool {
151160
Mempool {
152161
config: self.config.clone(),
153-
delayed_queues: DelayedQueues::new(self.config.static_config.declare_delay),
162+
delayed_queues: DelayedQueues::new(
163+
self.config.static_config.declare_delay,
164+
self.config.static_config.proof_tx_delay,
165+
),
154166
tx_pool: self.content.tx_pool.unwrap_or_default().into_values().collect(),
155167
tx_queue: Box::new(FeeTransactionQueue::new(
156168
self.content.priority_txs.unwrap_or_default(),
@@ -1220,9 +1232,10 @@ fn metrics_correctness() {
12201232
// invoke_6 | 5 | Pending queue
12211233
// declare_1 | 6 | Priority queue
12221234
// declare_2 | 7 | Delayed declare
1223-
// invoke_7 | 8 | Evicted
1224-
// invoke_8 | 9 | Pending queue
1225-
// invoke_9 | 10 | Committed
1235+
// proof_1 | 8 | Delayed invoke with proof
1236+
// invoke_7 | 9 | Evicted
1237+
// invoke_8 | 10 | Pending queue
1238+
// invoke_9 | 11 | Committed
12261239

12271240
let invoke_1 = add_tx_input!(tx_hash: 1, address: "0x0", tx_nonce: 0, account_nonce: 0);
12281241
let invoke_2 = add_tx_input!(tx_hash: 2, address: "0x1", tx_nonce: 0, account_nonce: 0);
@@ -1237,8 +1250,14 @@ fn metrics_correctness() {
12371250
let declare_2 = declare_add_tx_input(
12381251
declare_tx_args!(resource_bounds: valid_resource_bounds_for_testing(), sender_address: contract_address!("0x6"), tx_hash: tx_hash!(7)),
12391252
);
1240-
let invoke_7 = add_tx_input!(tx_hash: 8, address: "0x7", tx_nonce: 1, account_nonce: 0);
1241-
let invoke_8 = add_tx_input!(tx_hash: 9, address: "0x8", tx_nonce: 0, account_nonce: 0);
1253+
let proof_1 = proof_tx_add_tx_input(invoke_tx_args!(
1254+
resource_bounds: valid_resource_bounds_for_testing(),
1255+
sender_address: contract_address!("0x7"),
1256+
tx_hash: tx_hash!(8),
1257+
proof_facts: proof_facts![felt!("0x1")]
1258+
));
1259+
let invoke_7 = add_tx_input!(tx_hash: 9, address: "0x8", tx_nonce: 1, account_nonce: 0);
1260+
let invoke_8 = add_tx_input!(tx_hash: 10, address: "0x9", tx_nonce: 0, account_nonce: 0);
12421261

12431262
// Add invoke_1 and advance the clock so that it will be expired.
12441263
add_tx(&mut mempool, &invoke_1);
@@ -1266,6 +1285,7 @@ fn metrics_correctness() {
12661285
// another declare tx, that would be delayed.
12671286
fake_clock.advance(mempool.config.static_config.declare_delay + Duration::from_secs(1));
12681287
add_tx(&mut mempool, &declare_2);
1288+
add_tx(&mut mempool, &proof_1);
12691289

12701290
// Request 1 transaction from the mempool, so that we have a staged transaction. (The staged
12711291
// tx should be invoke_5, since it is in the priority queue and has the highest tip.)
@@ -1280,19 +1300,19 @@ fn metrics_correctness() {
12801300
add_tx(&mut mempool, &invoke_8);
12811301

12821302
// Add a long-delayed transaction to test time spent until committed.
1283-
let invoke_9 = add_tx_input!(tx_hash: 10, address: "0x9", tx_nonce: 0, account_nonce: 0);
1303+
let invoke_9 = add_tx_input!(tx_hash: 11, address: "0xa", tx_nonce: 0, account_nonce: 0);
12841304
mempool.config.static_config.capacity_in_bytes =
12851305
mempool.size_in_bytes() + invoke_9.tx.total_bytes();
12861306
add_tx(&mut mempool, &invoke_9);
12871307
fake_clock.advance(Duration::from_secs(20));
1288-
commit_block(&mut mempool, [("0x9", 1), ("0x3", 1)], []);
1308+
commit_block(&mut mempool, [("0xa", 1), ("0x3", 1)], []);
12891309

12901310
let mut metrics = MempoolMetrics::from_recorder(&recorder);
12911311
metrics.transaction_time_spent_until_batched.histogram = Default::default();
12921312
metrics.transaction_time_spent_until_committed.histogram = Default::default();
12931313
expect![[r#"
12941314
MempoolMetrics {
1295-
txs_received_invoke: 8,
1315+
txs_received_invoke: 9,
12961316
txs_received_declare: 2,
12971317
txs_received_deploy_account: 0,
12981318
txs_committed: 3,
@@ -1304,7 +1324,8 @@ fn metrics_correctness() {
13041324
pending_queue_size: 1,
13051325
get_txs_size: 1,
13061326
delayed_declares_size: 1,
1307-
total_size_in_bytes: 1600,
1327+
delayed_proof_txs_size: 1,
1328+
total_size_in_bytes: 2056,
13081329
transaction_time_spent_until_batched: HistogramValue {
13091330
sum: 2.0,
13101331
count: 1,
@@ -1354,80 +1375,97 @@ fn expired_staged_txs_are_not_deleted() {
13541375
expected_mempool_content.assert_eq(&mempool.content());
13551376
}
13561377

1378+
// Helpers for parameterized delayed tx tests.
1379+
1380+
fn make_delayed_declare(addr: &str, hash: u64) -> AddTransactionArgs {
1381+
declare_add_tx_input(declare_tx_args!(
1382+
resource_bounds: valid_resource_bounds_for_testing(),
1383+
sender_address: contract_address!(addr),
1384+
tx_hash: tx_hash!(hash)
1385+
))
1386+
}
1387+
1388+
fn make_delayed_proof_tx(addr: &str, hash: u64) -> AddTransactionArgs {
1389+
proof_tx_add_tx_input(invoke_tx_args!(
1390+
resource_bounds: valid_resource_bounds_for_testing(),
1391+
sender_address: contract_address!(addr),
1392+
tx_hash: tx_hash!(hash),
1393+
proof_facts: proof_facts![felt!("0x1")]
1394+
))
1395+
}
1396+
1397+
fn declare_delay_config(delay_secs: u64) -> MempoolStaticConfig {
1398+
MempoolStaticConfig { declare_delay: Duration::from_secs(delay_secs), ..Default::default() }
1399+
}
1400+
1401+
fn proof_tx_delay_config(delay_secs: u64) -> MempoolStaticConfig {
1402+
MempoolStaticConfig { proof_tx_delay: Duration::from_secs(delay_secs), ..Default::default() }
1403+
}
1404+
13571405
#[rstest]
1358-
fn delay_declare_txs() {
1359-
// Create a mempool with a fake clock.
1406+
#[case::declare(make_delayed_declare, declare_delay_config(5))]
1407+
#[case::proof_tx(make_delayed_proof_tx, proof_tx_delay_config(5))]
1408+
fn delay_txs(
1409+
#[case] make_delayed_tx: fn(&str, u64) -> AddTransactionArgs,
1410+
#[case] static_config: MempoolStaticConfig,
1411+
) {
13601412
let fake_clock = Arc::new(FakeClock::default());
1361-
let declare_delay = Duration::from_secs(5);
1362-
let mut mempool = Mempool::new(
1363-
MempoolConfig {
1364-
static_config: MempoolStaticConfig { declare_delay, ..Default::default() },
1365-
..Default::default()
1366-
},
1367-
fake_clock.clone(),
1368-
);
1369-
let first_declare = declare_add_tx_input(
1370-
declare_tx_args!(resource_bounds: valid_resource_bounds_for_testing(), sender_address: contract_address!("0x0"), tx_hash: tx_hash!(0)),
1371-
);
1372-
add_tx(&mut mempool, &first_declare);
1413+
let delay = Duration::from_secs(5);
1414+
let mut mempool =
1415+
Mempool::new(MempoolConfig { static_config, ..Default::default() }, fake_clock.clone());
1416+
let first_delayed = make_delayed_tx("0x0", 0);
1417+
add_tx(&mut mempool, &first_delayed);
13731418

13741419
fake_clock.advance(Duration::from_secs(1));
1375-
let second_declare = declare_add_tx_input(
1376-
declare_tx_args!(resource_bounds: valid_resource_bounds_for_testing(), sender_address: contract_address!("0x1"), tx_hash: tx_hash!(1)),
1377-
);
1378-
add_tx(&mut mempool, &second_declare);
1420+
let second_delayed = make_delayed_tx("0x1", 1);
1421+
add_tx(&mut mempool, &second_delayed);
13791422

13801423
assert_eq!(mempool.get_txs(2).unwrap(), vec![]);
13811424

1382-
// Complete the first declare's delay.
1383-
fake_clock.advance(declare_delay - Duration::from_secs(1));
1425+
// Complete the first delayed tx's delay.
1426+
fake_clock.advance(delay - Duration::from_secs(1));
13841427
// Add another transaction to trigger draining of ready delayed txs.
13851428
let another_tx_1 =
13861429
add_tx_input!(tx_hash: 123, address: "0x123", tx_nonce: 123, account_nonce: 0, tip: 123);
13871430
add_tx(&mut mempool, &another_tx_1);
13881431

1389-
// Assert only the first declare is in the mempool.
1390-
assert_eq!(mempool.get_txs(2).unwrap(), vec![first_declare.tx]);
1432+
assert_eq!(mempool.get_txs(2).unwrap(), vec![first_delayed.tx]);
13911433

1392-
// Complete the second declare's delay.
1434+
// Complete the second delayed tx's delay.
13931435
fake_clock.advance(Duration::from_secs(1));
13941436
// Add another transaction to trigger draining of ready delayed txs.
13951437
let another_tx_2 =
13961438
add_tx_input!(tx_hash: 2, address: "0x1", tx_nonce: 5, account_nonce: 0, tip: 100);
13971439
add_tx(&mut mempool, &another_tx_2);
13981440

1399-
// Assert the second declare was also added to the mempool.
1400-
assert_eq!(mempool.get_txs(2).unwrap(), vec![second_declare.tx]);
1441+
assert_eq!(mempool.get_txs(2).unwrap(), vec![second_delayed.tx]);
14011442
}
14021443

14031444
#[rstest]
1404-
fn no_delay_declare_front_run() {
1405-
// Create a mempool with a fake clock.
1445+
#[case::declare(
1446+
make_delayed_declare,
1447+
MempoolStaticConfig { declare_delay: Duration::from_secs(5), enable_fee_escalation: true, fee_escalation_percentage: 0, ..Default::default() },
1448+
)]
1449+
#[case::proof_tx(
1450+
make_delayed_proof_tx,
1451+
MempoolStaticConfig { proof_tx_delay: Duration::from_secs(5), enable_fee_escalation: true, fee_escalation_percentage: 0, ..Default::default() },
1452+
)]
1453+
fn no_delay_front_run(
1454+
#[case] make_delayed_tx: fn(&str, u64) -> AddTransactionArgs,
1455+
#[case] static_config: MempoolStaticConfig,
1456+
) {
14061457
let fake_clock = Arc::new(FakeClock::default());
1407-
let mut mempool = Mempool::new(
1408-
MempoolConfig {
1409-
static_config: MempoolStaticConfig {
1410-
declare_delay: Duration::from_secs(5),
1411-
// Always accept fee escalation to test only the delayed declare duplicate nonce.
1412-
enable_fee_escalation: true,
1413-
fee_escalation_percentage: 0,
1414-
..Default::default()
1415-
},
1416-
..Default::default()
1417-
},
1418-
fake_clock.clone(),
1419-
);
1420-
let declare = declare_add_tx_input(
1421-
declare_tx_args!(resource_bounds: valid_resource_bounds_for_testing(), sender_address: contract_address!("0x0"), tx_hash: tx_hash!(0)),
1422-
);
1423-
add_tx(&mut mempool, &declare);
1458+
let mut mempool =
1459+
Mempool::new(MempoolConfig { static_config, ..Default::default() }, fake_clock.clone());
1460+
let delayed_tx = make_delayed_tx("0x0", 0);
1461+
add_tx(&mut mempool, &delayed_tx);
14241462

14251463
let expected_error = MempoolError::DuplicateNonce {
1426-
address: declare.tx.contract_address(),
1427-
nonce: declare.tx.nonce(),
1464+
address: delayed_tx.tx.contract_address(),
1465+
nonce: delayed_tx.tx.nonce(),
14281466
};
1429-
add_tx_expect_error(&mut mempool, &declare, expected_error.clone());
1430-
validate_tx_expect_error(&mut mempool, &ValidationArgs::from(&declare), expected_error);
1467+
add_tx_expect_error(&mut mempool, &delayed_tx, expected_error.clone());
1468+
validate_tx_expect_error(&mut mempool, &ValidationArgs::from(&delayed_tx), expected_error);
14311469
}
14321470

14331471
#[rstest]
@@ -1658,61 +1696,72 @@ fn account_remains_evictable_after_tx_expiry() {
16581696
assert!(mempool.accounts_with_gap().contains(&tx_nonce_2.tx.contract_address()));
16591697
}
16601698

1661-
#[test]
1662-
fn delayed_declare_does_not_create_gap() {
1699+
#[rstest]
1700+
#[case::declare(make_delayed_declare, declare_delay_config(10))]
1701+
#[case::proof_tx(make_delayed_proof_tx, proof_tx_delay_config(10))]
1702+
fn delayed_tx_does_not_create_gap(
1703+
#[case] make_delayed_tx: fn(&str, u64) -> AddTransactionArgs,
1704+
#[case] static_config: MempoolStaticConfig,
1705+
) {
16631706
let fake_clock = Arc::new(FakeClock::default());
16641707
let mut mempool = Mempool::new(
16651708
MempoolConfig {
16661709
dynamic_config: MempoolDynamicConfig { transaction_ttl: Duration::from_secs(1000) },
1667-
static_config: MempoolStaticConfig {
1668-
declare_delay: Duration::from_secs(10),
1669-
..Default::default()
1670-
},
1710+
static_config,
16711711
},
16721712
fake_clock.clone(),
16731713
);
16741714

1675-
let declare_tx = declare_add_tx_input(declare_tx_args!(
1676-
tx_hash: tx_hash!(1),
1677-
sender_address: contract_address!("0x0"),
1678-
nonce: nonce!(0)
1679-
));
1715+
let delayed_tx = make_delayed_tx("0x0", 1);
16801716
let next_tx = add_tx_input!(tx_hash: 2, address: "0x0", tx_nonce: 1, account_nonce: 0);
1681-
for input in [&declare_tx, &next_tx] {
1717+
for input in [&delayed_tx, &next_tx] {
16821718
add_tx(&mut mempool, input);
16831719
}
16841720

1685-
assert!(!mempool.accounts_with_gap().contains(&declare_tx.tx.contract_address()));
1721+
assert!(!mempool.accounts_with_gap().contains(&delayed_tx.tx.contract_address()));
1722+
}
1723+
1724+
#[rstest]
1725+
#[case::declare(make_delayed_declare, declare_delay_config(100))]
1726+
#[case::proof_tx(make_delayed_proof_tx, proof_tx_delay_config(100))]
1727+
fn delayed_tx_closes_a_gap(
1728+
#[case] make_delayed_tx: fn(&str, u64) -> AddTransactionArgs,
1729+
#[case] static_config: MempoolStaticConfig,
1730+
) {
1731+
let mut mempool = Mempool::new(
1732+
MempoolConfig { static_config, ..Default::default() },
1733+
Arc::new(FakeClock::default()),
1734+
);
1735+
1736+
let gap_creating_tx = add_tx_input!(tx_hash: 1, address: "0x0", tx_nonce: 1, account_nonce: 0);
1737+
add_tx(&mut mempool, &gap_creating_tx);
1738+
assert!(mempool.accounts_with_gap().contains(&gap_creating_tx.tx.contract_address()));
1739+
1740+
let gap_closing_tx = make_delayed_tx("0x0", 2);
1741+
add_tx(&mut mempool, &gap_closing_tx);
1742+
assert!(!mempool.accounts_with_gap().contains(&gap_closing_tx.tx.contract_address()));
16861743
}
16871744

16881745
#[rstest]
1689-
fn declare_tx_closes_a_gap() {
1746+
fn invoke_without_proof_not_delayed() {
1747+
let fake_clock = Arc::new(FakeClock::default());
16901748
let mut mempool = Mempool::new(
16911749
MempoolConfig {
16921750
static_config: MempoolStaticConfig {
1693-
declare_delay: Duration::from_secs(100),
1751+
proof_tx_delay: Duration::from_secs(100),
16941752
..Default::default()
16951753
},
16961754
..Default::default()
16971755
},
1698-
Arc::new(FakeClock::default()),
1756+
fake_clock.clone(),
16991757
);
17001758

1701-
let gap_creating_tx = add_tx_input!(tx_hash: 1, address: "0x0", tx_nonce: 1, account_nonce: 0);
1702-
add_tx(&mut mempool, &gap_creating_tx);
1703-
assert!(mempool.accounts_with_gap().contains(&gap_creating_tx.tx.contract_address()));
1759+
// Invoke without proof_facts — should NOT be delayed.
1760+
let invoke_tx_input = add_tx_input!(tx_hash: 1, address: "0x0", tx_nonce: 0, account_nonce: 0);
1761+
add_tx(&mut mempool, &invoke_tx_input);
17041762

1705-
let delayed_declare_tx_closes_a_gap = declare_add_tx_input(declare_tx_args!(
1706-
tx_hash: tx_hash!(2),
1707-
sender_address: contract_address!("0x0"),
1708-
nonce: nonce!(0)
1709-
));
1710-
add_tx(&mut mempool, &delayed_declare_tx_closes_a_gap);
1711-
assert!(
1712-
!mempool
1713-
.accounts_with_gap()
1714-
.contains(&delayed_declare_tx_closes_a_gap.tx.contract_address())
1715-
);
1763+
// Should be immediately available.
1764+
assert_eq!(mempool.get_txs(1).unwrap(), vec![invoke_tx_input.tx]);
17161765
}
17171766

17181767
#[rstest]

0 commit comments

Comments
 (0)