Skip to content

Commit 2a30b4a

Browse files
apollo_consensus_orchestrator: add SNIP-35 proposer-validator symmetry tests
1 parent 5634250 commit 2a30b4a

3 files changed

Lines changed: 89 additions & 35 deletions

File tree

Cargo.lock

Lines changed: 23 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apollo_consensus_orchestrator/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ metrics-exporter-prometheus.workspace = true
7777
mockall.workspace = true
7878
mockito.workspace = true
7979
num-bigint.workspace = true
80+
rand.workspace = true
81+
rand_chacha.workspace = true
8082
rstest.workspace = true
8183
serde_json.workspace = true
8284

crates/apollo_consensus_orchestrator/src/snip35/test.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1+
use rand::{Rng, SeedableRng};
2+
use rand_chacha::ChaCha8Rng;
13
use starknet_api::block::GasPrice;
24

3-
use crate::snip35::{compute_fee_actual, compute_fee_proposal, compute_fee_target};
5+
use crate::snip35::{
6+
compute_fee_actual,
7+
compute_fee_proposal,
8+
compute_fee_target,
9+
FEE_PROPOSAL_MARGIN_PPT,
10+
ORACLE_L2_GAS_FLOOR_MAX_FRI,
11+
ORACLE_L2_GAS_FLOOR_MIN_FRI,
12+
PPT_DENOMINATOR,
13+
TARGET_ATTO_USD_PER_L2_GAS,
14+
};
415

516
#[test]
617
fn test_compute_fee_actual_with_10_identical_values() {
@@ -189,3 +200,55 @@ fn test_compute_fee_actual_lone_adversary_cannot_skew_median() {
189200
window.push(GasPrice(u128::MAX / 2));
190201
assert_eq!(compute_fee_actual(&window, 10), Some(GasPrice(1_000_000)));
191202
}
203+
204+
/// The validator's accept predicate. Must stay in sync with
205+
/// `validate_proposal::is_proposal_init_valid` SNIP-35 bounds check.
206+
fn validator_accepts(fee_actual: GasPrice, fee_proposal: GasPrice, margin_ppt: u128) -> bool {
207+
let lower = fee_actual.0.saturating_mul(PPT_DENOMINATOR) / (PPT_DENOMINATOR + margin_ppt);
208+
let upper = fee_actual.0.saturating_mul(PPT_DENOMINATOR + margin_ppt) / PPT_DENOMINATOR;
209+
fee_proposal.0 >= lower && fee_proposal.0 <= upper
210+
}
211+
212+
#[test]
213+
fn test_malicious_high_fee_proposal_rejected() {
214+
// Upper bound for fee_actual=1_000_000 with margin=2ppt is 1_002_000.
215+
let fee_actual = GasPrice(1_000_000);
216+
assert!(validator_accepts(fee_actual, GasPrice(1_002_000), 2));
217+
for proposal in [1_002_001u128, 1_003_000, 2_000_000, u128::MAX] {
218+
assert!(!validator_accepts(fee_actual, GasPrice(proposal), 2), "accepted {proposal}");
219+
}
220+
}
221+
222+
#[test]
223+
fn test_malicious_low_fee_proposal_rejected() {
224+
// Lower bound for fee_actual=1_000_000 with margin=2ppt is 998_003.
225+
let fee_actual = GasPrice(1_000_000);
226+
assert!(validator_accepts(fee_actual, GasPrice(998_003), 2));
227+
for proposal in [998_002u128, 500_000, 1, 0] {
228+
assert!(!validator_accepts(fee_actual, GasPrice(proposal), 2), "accepted {proposal}");
229+
}
230+
}
231+
232+
#[test]
233+
fn test_honest_proposer_always_passes_validation_fuzzed() {
234+
// Consensus safety: whatever compute_fee_proposal produces, the validator accepts.
235+
let mut rng = ChaCha8Rng::seed_from_u64(0xDEADBEEF);
236+
for _ in 0..10_000 {
237+
let fee_actual_value = rng.gen_range(1u128..1_000_000_000_000_000_000);
238+
let strk_usd_rate = rng.gen_range(1u128..2 * 10u128.pow(18));
239+
let fee_actual = GasPrice(fee_actual_value);
240+
let target = compute_fee_target(
241+
TARGET_ATTO_USD_PER_L2_GAS,
242+
strk_usd_rate,
243+
ORACLE_L2_GAS_FLOOR_MIN_FRI,
244+
ORACLE_L2_GAS_FLOOR_MAX_FRI,
245+
);
246+
let oracle_result = if rng.gen_bool(0.1) { None } else { Some(target) };
247+
let proposal = compute_fee_proposal(oracle_result, fee_actual, FEE_PROPOSAL_MARGIN_PPT);
248+
assert!(
249+
validator_accepts(fee_actual, proposal, FEE_PROPOSAL_MARGIN_PPT),
250+
"fee_actual={fee_actual_value} rate={strk_usd_rate} proposal={}",
251+
proposal.0
252+
);
253+
}
254+
}

0 commit comments

Comments
 (0)