Skip to content

Commit 619d629

Browse files
committed
Use OpenMina in consensus check
1 parent c4afc9e commit 619d629

2 files changed

Lines changed: 98 additions & 106 deletions

File tree

Lines changed: 32 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,47 @@
11
use blake2::{Blake2b512, Digest};
2-
use serde::Deserialize;
2+
use kimchi::o1_utils::FieldHelpers;
3+
use mina_p2p_messages::{hash::MinaHash, v2::MinaStateProtocolStateValueStableV2};
34

4-
#[derive(Clone, Debug, PartialEq)]
5-
pub struct ConsensusState {
6-
pub block_height: u32,
7-
pub last_vrf_output: String,
5+
#[derive(PartialEq)]
6+
pub enum LongerChainResult {
7+
Tip,
8+
Candidate,
89
}
910

10-
#[derive(Clone, Deserialize)]
11-
#[serde(rename_all = "camelCase")]
12-
pub struct ConsensusStateQuery {
13-
pub block_height: String,
14-
pub last_vrf_output: String,
15-
}
16-
17-
impl From<ConsensusStateQuery> for ConsensusState {
18-
fn from(value: ConsensusStateQuery) -> Self {
19-
Self {
20-
block_height: u32::from_str_radix(&value.block_height, 10).unwrap(),
21-
last_vrf_output: value.last_vrf_output,
22-
}
23-
}
24-
}
11+
pub fn select_longer_chain(
12+
tip: &MinaStateProtocolStateValueStableV2,
13+
candidate: &MinaStateProtocolStateValueStableV2,
14+
) -> LongerChainResult {
15+
let tip_block_height = &tip.body.consensus_state.blockchain_length.as_u32();
16+
let candidate_block_height = &candidate.body.consensus_state.blockchain_length.as_u32();
2517

26-
impl ConsensusState {
27-
pub fn from_json(mina_consensus_state_query: &str) -> Result<Self, String> {
28-
let mina_consensus_state_query: serde_json::Map<String, serde_json::Value> =
29-
serde_json::from_str(mina_consensus_state_query)
30-
.map_err(|err| format!("Could not parse mina state consensus query: {err}"))?;
31-
32-
let consensus_state_query_value = mina_consensus_state_query
33-
.get("data")
34-
.and_then(|d| d.get("block"))
35-
.and_then(|d| d.get("protocolState"))
36-
.and_then(|d| d.get("consensusState"))
37-
.ok_or("Could not parse consensus state: JSON structure is unexpected")?
38-
.to_owned();
39-
40-
let consensus_state_query: ConsensusStateQuery =
41-
serde_json::from_value(consensus_state_query_value)
42-
.map_err(|err| format!("Could not parse mina consensus state: {err}"))?;
43-
44-
Ok(consensus_state_query.into())
18+
if tip_block_height < candidate_block_height {
19+
return LongerChainResult::Candidate;
4520
}
46-
47-
pub fn select_longer_chain(&self, other: &Self) -> Self {
48-
if self.block_height < other.block_height {
49-
return other.clone();
50-
}
51-
// tiebreak logic
52-
else if self.block_height == other.block_height {
53-
// compare last VRF digests lexicographically
54-
if other.hash_last_vrf() > self.hash_last_vrf() {
55-
return other.clone();
56-
} else if self.hash_last_vrf() == self.hash_last_vrf() {
57-
// compare consensus state hashes lexicographically
58-
// if other.hash_state() > self.hash_state() {
59-
// return other.clone();
60-
// }
61-
// FIXME: replace with logic defined above
62-
return other.clone();
21+
// tiebreak logic
22+
else if tip_block_height == candidate_block_height {
23+
// compare last VRF digests lexicographically
24+
if hash_last_vrf(tip) < hash_last_vrf(candidate) {
25+
return LongerChainResult::Candidate;
26+
} else if hash_last_vrf(tip) == hash_last_vrf(candidate) {
27+
// compare consensus state hashes lexicographically
28+
if hash_state(tip) < hash_state(candidate) {
29+
return LongerChainResult::Candidate;
6330
}
6431
}
65-
66-
self.clone()
6732
}
6833

69-
fn hash_last_vrf(&self) -> String {
70-
let mut hasher = Blake2b512::new();
71-
hasher.update(self.last_vrf_output.clone());
72-
let digest = hasher.finalize().to_vec();
73-
74-
String::from_utf8(digest).unwrap()
75-
}
76-
77-
fn hash_state(&self) -> String {
78-
todo!()
79-
}
34+
LongerChainResult::Tip
8035
}
8136

82-
#[cfg(test)]
83-
mod tests {
84-
use super::ConsensusState;
37+
fn hash_last_vrf(chain: &MinaStateProtocolStateValueStableV2) -> String {
38+
let mut hasher = Blake2b512::new();
39+
hasher.update(chain.body.consensus_state.last_vrf_output.as_slice());
40+
let digest = hasher.finalize().to_vec();
8541

86-
const MINA_CONSENSUS_STATE_QUERY: &str = include_str!(
87-
"../../../../batcher/aligned/test_files/mina/mina_mainnet_protocol_query.json"
88-
);
89-
90-
#[test]
91-
fn check_consensus_rules() {
92-
let consensus_state_query = ConsensusState::from_json(MINA_CONSENSUS_STATE_QUERY).unwrap();
93-
let consensus_state: ConsensusState = consensus_state_query.into();
94-
dbg!(consensus_state.block_height);
95-
let fake_chain_state = ConsensusState {
96-
block_height: 1,
97-
last_vrf_output: String::new(),
98-
};
99-
100-
let best_chain = consensus_state.select_longer_chain(&fake_chain_state);
42+
String::from_utf8(digest).unwrap()
43+
}
10144

102-
assert_eq!(best_chain, consensus_state);
103-
}
45+
fn hash_state(chain: &MinaStateProtocolStateValueStableV2) -> String {
46+
MinaHash::hash(chain).to_hex()
10447
}

operator/mina/lib/src/lib.rs

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod consensus_state;
22

33
use ark_ec::short_weierstrass_jacobian::GroupAffine;
44
use base64::prelude::*;
5+
use consensus_state::{select_longer_chain, LongerChainResult};
56
use kimchi::mina_curves::pasta::{Fp, PallasParameters};
67
use kimchi::o1_utils::FieldHelpers;
78
use kimchi::verifier_index::VerifierIndex;
@@ -11,7 +12,6 @@ use mina_p2p_messages::hash::MinaHash;
1112
use mina_p2p_messages::v2::{MinaBaseProofStableV2, MinaStateProtocolStateValueStableV2};
1213
use mina_tree::proofs::verification::verify_block;
1314
use mina_tree::verifier::get_srs;
14-
use std::str::FromStr;
1515
use verifier_index::deserialize_blockchain_vk;
1616

1717
mod verifier_index;
@@ -24,6 +24,9 @@ lazy_static! {
2424
// TODO(xqft): check proof size
2525
const MAX_PROOF_SIZE: usize = 16 * 1024;
2626
const MAX_PUB_INPUT_SIZE: usize = 3 * 1024;
27+
const PROTOCOL_STATE_HASH_SIZE: usize = 32;
28+
// TODO(gabrielbosio): check that this length is always the same for every block
29+
const PROTOCOL_STATE_SIZE: usize = 2060;
2730

2831
#[no_mangle]
2932
pub extern "C" fn verify_protocol_state_proof_ffi(
@@ -40,18 +43,26 @@ pub extern "C" fn verify_protocol_state_proof_ffi(
4043
}
4144
};
4245

43-
let (protocol_state_hash, protocol_state) =
44-
match parse_protocol_state_pub(&public_input_bytes[..public_input_len]) {
45-
Ok(protocol_state_pub) => protocol_state_pub,
46-
Err(err) => {
47-
eprintln!("Failed to parse protocol state public inputs: {}", err);
48-
return false;
49-
}
50-
};
46+
let (
47+
tip_protocol_state_hash,
48+
tip_protocol_state,
49+
candidate_protocol_state_hash,
50+
candidate_protocol_state,
51+
) = match parse_protocol_state_pub(&public_input_bytes[..public_input_len]) {
52+
Ok(protocol_state_pub) => protocol_state_pub,
53+
Err(err) => {
54+
eprintln!("Failed to parse protocol state public inputs: {}", err);
55+
return false;
56+
}
57+
};
5158

5259
// TODO(xqft): this can be a batcher's pre-verification check (but don't remove it from here)
53-
if MinaHash::hash(&protocol_state) != protocol_state_hash {
54-
eprintln!("The protocol state doesn't match the hash provided as public input");
60+
if MinaHash::hash(&tip_protocol_state) != tip_protocol_state_hash {
61+
eprintln!("The tip's protocol state doesn't match the hash provided as public input");
62+
return false;
63+
}
64+
if MinaHash::hash(&candidate_protocol_state) != candidate_protocol_state_hash {
65+
eprintln!("The candidate's protocol state doesn't match the hash provided as public input");
5566
return false;
5667
}
5768

@@ -60,14 +71,19 @@ pub extern "C" fn verify_protocol_state_proof_ffi(
6071
let srs = get_srs::<Fp>();
6172
let srs = srs.lock().unwrap();
6273

63-
verify_block(
74+
if !verify_block(
6475
&protocol_state_proof,
65-
protocol_state_hash,
76+
tip_protocol_state_hash,
6677
&VERIFIER_INDEX,
6778
&srs,
68-
)
79+
) {
80+
return false;
81+
}
82+
83+
// Consensus check: Short fork rule
84+
let longer_chain = select_longer_chain(&tip_protocol_state, &candidate_protocol_state);
6985

70-
// TODO(xqft): consensus checks
86+
longer_chain == LongerChainResult::Candidate
7187
}
7288

7389
pub fn parse_protocol_state_proof(
@@ -84,10 +100,43 @@ pub fn parse_protocol_state_proof(
84100

85101
pub fn parse_protocol_state_pub(
86102
protocol_state_pub: &[u8],
87-
) -> Result<(Fp, MinaStateProtocolStateValueStableV2), String> {
103+
) -> Result<
104+
(
105+
Fp,
106+
MinaStateProtocolStateValueStableV2,
107+
Fp,
108+
MinaStateProtocolStateValueStableV2,
109+
),
110+
String,
111+
> {
112+
let (tip_protocol_state_hash, tip_protocol_state) = parse_protocol_state_with_hash(
113+
&protocol_state_pub[..(PROTOCOL_STATE_HASH_SIZE + PROTOCOL_STATE_SIZE)],
114+
)?;
115+
116+
let (candidate_protocol_state_hash, candidate_protocol_state) = parse_protocol_state_with_hash(
117+
&protocol_state_pub[(PROTOCOL_STATE_HASH_SIZE + PROTOCOL_STATE_SIZE)
118+
..((PROTOCOL_STATE_HASH_SIZE + PROTOCOL_STATE_SIZE) * 2)],
119+
)?;
120+
121+
Ok((
122+
tip_protocol_state_hash,
123+
tip_protocol_state,
124+
candidate_protocol_state_hash,
125+
candidate_protocol_state,
126+
))
127+
}
128+
129+
fn parse_protocol_state_with_hash(
130+
protocol_state_pub: &[u8],
131+
) -> Result<
132+
(
133+
ark_ff::Fp256<mina_curves::pasta::fields::FpParameters>,
134+
MinaStateProtocolStateValueStableV2,
135+
),
136+
String,
137+
> {
88138
let protocol_state_hash =
89139
Fp::from_bytes(&protocol_state_pub[..32]).map_err(|err| err.to_string())?;
90-
91140
let protocol_state_base64 =
92141
std::str::from_utf8(&protocol_state_pub[32..]).map_err(|err| err.to_string())?;
93142
let protocol_state_binprot = BASE64_STANDARD

0 commit comments

Comments
 (0)