Skip to content

Commit 79973eb

Browse files
authored
Merge pull request #23 from lambdaclass/long_range_consensus
Add long-range fork consensus rule
2 parents 281967f + 6554ca0 commit 79973eb

15 files changed

Lines changed: 171 additions & 60 deletions
-47.2 KB
Binary file not shown.
-1.03 KB
Binary file not shown.
-4.09 KB
Binary file not shown.
-4.09 KB
Binary file not shown.

operator/mina/lib/Cargo.lock

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

operator/mina/lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ bs58 = "0.5.1"
2626
lazy_static = "1.5.0"
2727
blake2 = "0.10.6"
2828
once_cell = "1.19.0"
29-
mina_bridge_core = { git = "https://github.com/lambdaclass/mina_bridge", branch = "relative_finalization" }
29+
mina_bridge_core = { git = "https://github.com/lambdaclass/mina_bridge", branch = "new_account_proof" }
3030
bincode = "1.3.3"
3131

3232
[patch.crates-io]
Lines changed: 141 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,174 @@
11
use blake2::{Blake2b512, Digest};
22
use kimchi::o1_utils::FieldHelpers;
3-
use mina_p2p_messages::{hash::MinaHash, v2::MinaStateProtocolStateValueStableV2};
3+
use mina_p2p_messages::{
4+
hash::MinaHash,
5+
v2::{
6+
ConsensusProofOfStakeDataConsensusStateValueStableV2 as MinaConsensusState,
7+
MinaStateProtocolStateValueStableV2 as MinaProtocolState,
8+
},
9+
};
10+
use std::cmp::{max, min, Ordering};
411

5-
#[derive(PartialEq)]
6-
pub enum LongerChainResult {
12+
const GRACE_PERIOD_END: u32 = 1440;
13+
const SUB_WINDOWS_PER_WINDOW: u32 = 11;
14+
const SLOTS_PER_SUB_WINDOW: u32 = 7;
15+
16+
#[derive(Debug, PartialEq)]
17+
pub enum ChainResult {
718
Bridge,
819
Candidate,
920
}
1021

11-
pub fn select_longer_chain(
12-
candidate: &MinaStateProtocolStateValueStableV2,
13-
tip: &MinaStateProtocolStateValueStableV2,
14-
) -> LongerChainResult {
22+
pub fn select_secure_chain(
23+
candidate: &MinaProtocolState,
24+
tip: &MinaProtocolState,
25+
) -> Result<ChainResult, String> {
26+
if is_short_range(candidate, tip)? {
27+
Ok(select_longer_chain(candidate, tip))
28+
} else {
29+
let tip_density = relative_min_window_density(candidate, tip);
30+
let candidate_density = relative_min_window_density(candidate, tip);
31+
Ok(match candidate_density.cmp(&tip_density) {
32+
Ordering::Less => ChainResult::Bridge,
33+
Ordering::Equal => select_longer_chain(candidate, tip),
34+
Ordering::Greater => ChainResult::Candidate,
35+
})
36+
}
37+
}
38+
39+
fn select_longer_chain(candidate: &MinaProtocolState, tip: &MinaProtocolState) -> ChainResult {
1540
let candidate_block_height = &candidate.body.consensus_state.blockchain_length.as_u32();
1641
let tip_block_height = &tip.body.consensus_state.blockchain_length.as_u32();
1742

1843
if candidate_block_height > tip_block_height {
19-
return LongerChainResult::Candidate;
44+
return ChainResult::Candidate;
2045
}
2146
// tiebreak logic
2247
else if candidate_block_height == tip_block_height {
2348
// compare last VRF digests lexicographically
2449
if hash_last_vrf(candidate) > hash_last_vrf(tip) {
25-
return LongerChainResult::Candidate;
50+
return ChainResult::Candidate;
2651
} else if hash_last_vrf(candidate) == hash_last_vrf(tip) {
2752
// compare consensus state hashes lexicographically
2853
if hash_state(candidate) > hash_state(tip) {
29-
return LongerChainResult::Candidate;
54+
return ChainResult::Candidate;
3055
}
3156
}
3257
}
3358

34-
LongerChainResult::Bridge
59+
ChainResult::Bridge
60+
}
61+
62+
/// Returns true if the fork is short-range, else the fork is long-range.
63+
fn is_short_range(candidate: &MinaProtocolState, tip: &MinaProtocolState) -> Result<bool, String> {
64+
// TODO(xqft): verify constants are correct
65+
if tip.body.constants != candidate.body.constants {
66+
return Err("Protocol constants on candidate and tip state are not equal".to_string());
67+
}
68+
let slots_per_epoch = tip.body.constants.slots_per_epoch.as_u32();
69+
70+
let candidate = &candidate.body.consensus_state;
71+
let tip = &tip.body.consensus_state;
72+
73+
let check = |s1: &MinaConsensusState, s2: &MinaConsensusState| {
74+
let s2_epoch_slot = s2.global_slot() % slots_per_epoch;
75+
if s1.epoch_count.as_u32() == s2.epoch_count.as_u32() + 1
76+
&& s2_epoch_slot >= slots_per_epoch * 2 / 3
77+
{
78+
s1.staking_epoch_data.lock_checkpoint == s2.next_epoch_data.lock_checkpoint
79+
} else {
80+
false
81+
}
82+
};
83+
84+
Ok(if candidate.epoch_count == tip.epoch_count {
85+
candidate.staking_epoch_data.lock_checkpoint == tip.staking_epoch_data.lock_checkpoint
86+
} else {
87+
check(candidate, tip) || check(tip, candidate)
88+
})
89+
}
90+
91+
fn relative_min_window_density(candidate: &MinaProtocolState, tip: &MinaProtocolState) -> u32 {
92+
let candidate = &candidate.body.consensus_state;
93+
let tip = &tip.body.consensus_state;
94+
95+
let max_slot = max(candidate.global_slot(), tip.global_slot());
96+
97+
if max_slot < GRACE_PERIOD_END {
98+
return candidate.min_window_density.as_u32();
99+
}
100+
101+
let projected_window = {
102+
let shift_count = (max_slot - candidate.global_slot() - 1).clamp(0, SUB_WINDOWS_PER_WINDOW);
103+
let mut projected_window: Vec<_> = candidate
104+
.sub_window_densities
105+
.iter()
106+
.map(|d| d.as_u32())
107+
.collect();
108+
109+
let mut i = relative_sub_window(candidate);
110+
for _ in 0..shift_count {
111+
i = (i + 1) % SUB_WINDOWS_PER_WINDOW;
112+
projected_window[i as usize] = 0
113+
}
114+
115+
projected_window
116+
};
117+
118+
let projected_window_density = projected_window.iter().sum();
119+
120+
min(
121+
candidate.min_window_density.as_u32(),
122+
projected_window_density,
123+
)
35124
}
36125

37-
fn hash_last_vrf(chain: &MinaStateProtocolStateValueStableV2) -> String {
126+
fn relative_sub_window(state: &MinaConsensusState) -> u32 {
127+
(state.global_slot() / SLOTS_PER_SUB_WINDOW) % SUB_WINDOWS_PER_WINDOW
128+
}
129+
130+
fn hash_last_vrf(chain: &MinaProtocolState) -> String {
38131
let mut hasher = Blake2b512::new();
39132
hasher.update(chain.body.consensus_state.last_vrf_output.as_slice());
40133
let digest = hasher.finalize().to_vec();
41134

42-
hex::encode(&digest)
135+
hex::encode(digest)
43136
}
44137

45-
fn hash_state(chain: &MinaStateProtocolStateValueStableV2) -> String {
138+
fn hash_state(chain: &MinaProtocolState) -> String {
46139
MinaHash::hash(chain).to_hex()
47140
}
141+
142+
#[cfg(test)]
143+
mod test {
144+
use mina_bridge_core::proof::state_proof::MinaStateProof;
145+
146+
use super::*;
147+
148+
const PROOF_BYTES: &[u8] =
149+
include_bytes!("../../../../scripts/test_files/mina/mina_state.proof");
150+
151+
#[test]
152+
fn new_mina_state_passes_consensus_checks() {
153+
let valid_proof: MinaStateProof = bincode::deserialize(PROOF_BYTES).unwrap();
154+
let old_tip = valid_proof.bridge_tip_state;
155+
let new_tip = valid_proof.candidate_chain_states.last().unwrap();
156+
157+
assert_eq!(
158+
select_secure_chain(new_tip, &old_tip).unwrap(),
159+
ChainResult::Candidate
160+
);
161+
}
162+
163+
#[test]
164+
fn old_mina_state_fails_consensus_checks() {
165+
let valid_proof: MinaStateProof = bincode::deserialize(PROOF_BYTES).unwrap();
166+
let old_tip = valid_proof.bridge_tip_state;
167+
let new_tip = valid_proof.candidate_chain_states.last().unwrap();
168+
169+
assert_eq!(
170+
select_secure_chain(&old_tip, new_tip).unwrap(),
171+
ChainResult::Bridge
172+
);
173+
}
174+
}

operator/mina/lib/src/lib.rs

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
/// Consensus chain selection algorithms. The [`official specification`] was taken as a reference.
2+
///
3+
/// [`official specification`]: https://github.com/MinaProtocol/mina/blob/develop/docs/specs/consensus/README.md
14
mod consensus_state;
5+
mod verifier_index;
26

37
use mina_bridge_core::proof::state_proof::{MinaStateProof, MinaStatePubInputs};
48

59
use ark_ec::short_weierstrass_jacobian::GroupAffine;
6-
use consensus_state::{select_longer_chain, LongerChainResult};
10+
use consensus_state::{select_secure_chain, ChainResult};
711
use kimchi::mina_curves::pasta::{Fp, PallasParameters};
812
use kimchi::verifier_index::VerifierIndex;
913
use lazy_static::lazy_static;
@@ -13,8 +17,6 @@ use mina_tree::proofs::verification::verify_block;
1317
use mina_tree::verifier::get_srs;
1418
use verifier_index::deserialize_blockchain_vk;
1519

16-
mod verifier_index;
17-
1820
lazy_static! {
1921
static ref VERIFIER_INDEX: VerifierIndex<GroupAffine<PallasParameters>> =
2022
deserialize_blockchain_vk().unwrap();
@@ -62,10 +64,16 @@ pub extern "C" fn verify_mina_state_ffi(
6264
let srs = get_srs::<Fp>();
6365
let srs = srs.lock().unwrap();
6466

65-
// Consensus check: Short fork rule
66-
let longer_chain = select_longer_chain(&candidate_tip_state, &bridge_tip_state);
67-
if longer_chain == LongerChainResult::Bridge {
68-
eprintln!("Failed consensus checks for candidate tip state against bridge's tip");
67+
// Consensus checks
68+
let secure_chain = match select_secure_chain(&candidate_tip_state, &bridge_tip_state) {
69+
Ok(res) => res,
70+
Err(err) => {
71+
eprintln!("Failed consensus checks for candidate tip: {err}");
72+
return false;
73+
}
74+
};
75+
if secure_chain == ChainResult::Bridge {
76+
eprintln!("Failed consensus checks for candidate tip: bridge's tip is more secure");
6977
return false;
7078
}
7179

@@ -187,17 +195,14 @@ mod test {
187195
use super::*;
188196

189197
const PROOF_BYTES: &[u8] =
190-
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.proof");
198+
include_bytes!("../../../../scripts/test_files/mina/mina_state.proof");
191199
const PUB_INPUT_BYTES: &[u8] =
192-
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state.pub");
193-
const PROTOCOL_STATE_BAD_HASH_PUB_BYTES: &[u8] =
194-
include_bytes!("../../../../batcher/aligned/test_files/mina/protocol_state_bad_hash.pub");
195-
const PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES: &[u8] = include_bytes!(
196-
"../../../../batcher/aligned/test_files/mina/protocol_state_bad_consensus.pub"
197-
);
200+
include_bytes!("../../../../scripts/test_files/mina/mina_state.pub");
201+
const BAD_HASH_PUB_INPUT_BYTES: &[u8] =
202+
include_bytes!("../../../../scripts/test_files/mina/mina_state_bad_hash.pub");
198203

199204
#[test]
200-
fn protocol_state_proof_verifies() {
205+
fn valid_mina_state_proof_verifies() {
201206
let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE];
202207
let proof_size = PROOF_BYTES.len();
203208
assert!(proof_size <= proof_buffer.len());
@@ -214,40 +219,19 @@ mod test {
214219
}
215220

216221
#[test]
217-
fn proof_of_protocol_state_with_bad_hash_does_not_verify() {
222+
fn mina_state_proof_with_bad_bridge_tip_hash_does_not_verify() {
218223
let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE];
219224
let proof_size = PROOF_BYTES.len();
220225
assert!(proof_size <= proof_buffer.len());
221226
proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES);
222227

223228
let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE];
224-
let pub_input_size = PROTOCOL_STATE_BAD_HASH_PUB_BYTES.len();
229+
let pub_input_size = BAD_HASH_PUB_INPUT_BYTES.len();
225230
assert!(pub_input_size <= pub_input_buffer.len());
226-
pub_input_buffer[..pub_input_size].clone_from_slice(PROTOCOL_STATE_BAD_HASH_PUB_BYTES);
231+
pub_input_buffer[..pub_input_size].clone_from_slice(BAD_HASH_PUB_INPUT_BYTES);
227232

228233
let result =
229234
verify_mina_state_ffi(&proof_buffer, proof_size, &pub_input_buffer, pub_input_size);
230235
assert!(!result);
231236
}
232-
233-
#[test]
234-
fn proof_of_protocol_state_with_bad_consensus_does_not_verify() {
235-
let mut proof_buffer = [0u8; super::MAX_PROOF_SIZE];
236-
let proof_size = PROOF_BYTES.len();
237-
assert!(proof_size <= proof_buffer.len());
238-
proof_buffer[..proof_size].clone_from_slice(PROOF_BYTES);
239-
240-
let mut pub_input_buffer = [0u8; super::MAX_PUB_INPUT_SIZE];
241-
let pub_input_size = PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES.len();
242-
assert!(pub_input_size <= pub_input_buffer.len());
243-
pub_input_buffer[..pub_input_size].clone_from_slice(PROTOCOL_STATE_BAD_CONSENSUS_PUB_BYTES);
244-
245-
let result = verify_protocol_state_proof_ffi(
246-
&proof_buffer,
247-
proof_size,
248-
&pub_input_buffer,
249-
pub_input_size,
250-
);
251-
assert!(!result);
252-
}
253237
}

operator/mina/mina_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
func TestMinaStateProofVerifies(t *testing.T) {
1212
fmt.Println(os.Getwd())
13-
proofFile, err := os.Open("../../batcher/aligned/test_files/mina/protocol_state.proof")
13+
proofFile, err := os.Open("../../scripts/test_files/mina/mina_state.proof")
1414
if err != nil {
1515
t.Errorf("could not open mina state proof file")
1616
}
@@ -21,7 +21,7 @@ func TestMinaStateProofVerifies(t *testing.T) {
2121
t.Errorf("could not read bytes from mina state proof file")
2222
}
2323

24-
pubInputFile, err := os.Open("../../batcher/aligned/test_files/mina/protocol_state.pub")
24+
pubInputFile, err := os.Open("../../scripts/test_files/mina/mina_state.pub")
2525
if err != nil {
2626
t.Errorf("could not open mina state hash file")
2727
}

operator/mina_account/mina_account_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
func TestMinaStateProofVerifies(t *testing.T) {
1212
fmt.Println(os.Getwd())
13-
proofFile, err := os.Open("../../batcher/aligned/test_files/mina/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.proof")
13+
proofFile, err := os.Open("../../scripts/test_files/mina_account/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.proof")
1414
if err != nil {
1515
t.Errorf("could not open mina account proof file")
1616
}
@@ -21,7 +21,7 @@ func TestMinaStateProofVerifies(t *testing.T) {
2121
t.Errorf("could not read bytes from mina account proof file")
2222
}
2323

24-
pubInputFile, err := os.Open("../../batcher/aligned/test_files/mina/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.pub")
24+
pubInputFile, err := os.Open("../../scripts/test_files/mina_account/account_B62qrQKS9ghd91shs73TCmBJRW9GzvTJK443DPx2YbqcyoLc56g1ny9.pub")
2525
if err != nil {
2626
t.Errorf("could not open mina account pub inputs file")
2727
}

0 commit comments

Comments
 (0)