Skip to content

Commit bf81ef7

Browse files
apollo_consensus_orchestrator: add SNIP-35 proposer-validator symmetry tests
1 parent 810e35f commit bf81ef7

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

0 commit comments

Comments
 (0)