Skip to content

Commit f58e293

Browse files
authored
Merge pull request #536 from openmina/dump-reconstruct
Dump staged ledger reconstruction when it fails
2 parents 0dbc15e + 59fe201 commit f58e293

File tree

2 files changed

+140
-6
lines changed

2 files changed

+140
-6
lines changed

ledger/src/staged_ledger/staged_ledger.rs

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,7 @@ impl StagedLedger {
306306
if staged_ledger_hash != expected_merkle_root {
307307
return Err(format!(
308308
"Mismatching merkle root Expected:{:?} Got:{:?}",
309-
expected_merkle_root.to_string(),
310-
staged_ledger_hash.to_string()
309+
expected_merkle_root, staged_ledger_hash
311310
));
312311
}
313312

@@ -5851,7 +5850,7 @@ mod tests {
58515850
use std::{collections::BTreeMap, fs::File};
58525851

58535852
use mina_hasher::Fp;
5854-
use mina_p2p_messages::binprot;
5853+
use mina_p2p_messages::{binprot, list::List};
58555854

58565855
use crate::{
58575856
proofs::public_input::protocol_state::MinaHash,
@@ -6186,4 +6185,62 @@ mod tests {
61866185

61876186
dbg!(stmt.to_field_elements_owned());
61886187
}
6188+
6189+
#[test]
6190+
fn reconstruct_staged_ledger() {
6191+
#[allow(unused)]
6192+
use binprot::{
6193+
macros::{BinProtRead, BinProtWrite},
6194+
BinProtRead, BinProtWrite,
6195+
};
6196+
6197+
#[derive(BinProtRead, BinProtWrite)]
6198+
struct ReconstructContext {
6199+
accounts: Vec<v2::MinaBaseAccountBinableArgStableV2>,
6200+
scan_state: v2::TransactionSnarkScanStateStableV2,
6201+
pending_coinbase: v2::MinaBasePendingCoinbaseStableV2,
6202+
staged_ledger_hash: v2::LedgerHash,
6203+
states: List<v2::MinaStateProtocolStateValueStableV2>,
6204+
}
6205+
6206+
let Ok(file) = std::fs::read("/tmp/failed_reconstruct_ctx.binprot") else {
6207+
eprintln!("no reconstruct context found");
6208+
return;
6209+
};
6210+
6211+
let ReconstructContext {
6212+
accounts,
6213+
scan_state,
6214+
pending_coinbase,
6215+
staged_ledger_hash,
6216+
states,
6217+
} = ReconstructContext::binprot_read(&mut file.as_slice()).unwrap();
6218+
6219+
let states = states
6220+
.iter()
6221+
.map(|state| (state.hash().to_field::<Fp>(), state.clone()))
6222+
.collect::<BTreeMap<_, _>>();
6223+
6224+
const LEDGER_DEPTH: usize = 35;
6225+
let mut ledger = Mask::create(LEDGER_DEPTH);
6226+
for account in &accounts {
6227+
let account: Account = account.into();
6228+
let id = account.id();
6229+
ledger.get_or_create_account(id, account).unwrap();
6230+
}
6231+
assert_eq!(ledger.num_accounts(), accounts.len());
6232+
6233+
StagedLedger::of_scan_state_pending_coinbases_and_snarked_ledger(
6234+
(),
6235+
openmina_core::constants::constraint_constants(),
6236+
Verifier,
6237+
(&scan_state).into(),
6238+
ledger,
6239+
LocalState::empty(),
6240+
staged_ledger_hash.0.to_field(),
6241+
(&pending_coinbase).into(),
6242+
|key| states.get(&key).cloned().unwrap(),
6243+
)
6244+
.unwrap();
6245+
}
61896246
}

node/src/ledger/ledger_service.rs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use ledger::{
2828
use mina_hasher::Fp;
2929
use mina_p2p_messages::{
3030
binprot::BinProtRead,
31+
list::List,
3132
v2::{
3233
self, DataHashLibStateHashStableV1, LedgerHash, MinaBaseLedgerHash0StableV1,
3334
MinaBasePendingCoinbaseStableV2, MinaBasePendingCoinbaseWitnessStableV2,
@@ -1109,7 +1110,9 @@ fn staged_ledger_reconstruct(
11091110
.map(|p| p.staged_ledger_hash.clone())
11101111
.unwrap_or_else(|| snarked_ledger_hash.clone());
11111112

1112-
let result = if let Some(parts) = parts {
1113+
let ledger = snarked_ledger.make_child();
1114+
1115+
let mut result = if let Some(parts) = &parts {
11131116
let states = parts
11141117
.needed_blocks
11151118
.iter()
@@ -1121,16 +1124,31 @@ fn staged_ledger_reconstruct(
11211124
constraint_constants(),
11221125
Verifier,
11231126
(&parts.scan_state).into(),
1124-
snarked_ledger,
1127+
ledger,
11251128
LocalState::empty(),
11261129
parts.staged_ledger_hash.0.to_field(),
11271130
(&parts.pending_coinbase).into(),
11281131
|key| states.get(&key).cloned().unwrap(),
11291132
)
11301133
} else {
1131-
StagedLedger::create_exn(constraint_constants().clone(), snarked_ledger)
1134+
StagedLedger::create_exn(constraint_constants().clone(), ledger)
11321135
};
11331136

1137+
match result.as_mut() {
1138+
Ok(staged_ledger) => {
1139+
staged_ledger.commit_and_reparent_to_root();
1140+
}
1141+
Err(_) => {
1142+
if let Err(e) = dump_reconstruct_to_file(&snarked_ledger, &parts) {
1143+
openmina_core::error!(
1144+
openmina_core::log::system_time();
1145+
kind = "LedgerService::dump - Failed reconstruct",
1146+
summary = format!("Failed to save reconstruction to file: {e:?}")
1147+
);
1148+
}
1149+
}
1150+
}
1151+
11341152
(staged_ledger_hash, result)
11351153
}
11361154

@@ -1159,6 +1177,65 @@ pub trait LedgerService: redux::Service {
11591177
}
11601178
}
11611179

1180+
/// Save reconstruction to file, when it fails.
1181+
/// So we can easily reproduce the application both in Rust and OCaml, to compare them.
1182+
fn dump_reconstruct_to_file(
1183+
snarked_ledger: &Mask,
1184+
parts: &Option<Arc<StagedLedgerAuxAndPendingCoinbasesValid>>,
1185+
) -> std::io::Result<()> {
1186+
use mina_p2p_messages::binprot::{
1187+
self,
1188+
macros::{BinProtRead, BinProtWrite},
1189+
};
1190+
1191+
#[derive(BinProtRead, BinProtWrite)]
1192+
struct ReconstructContext {
1193+
accounts: Vec<v2::MinaBaseAccountBinableArgStableV2>,
1194+
scan_state: v2::TransactionSnarkScanStateStableV2,
1195+
pending_coinbase: v2::MinaBasePendingCoinbaseStableV2,
1196+
staged_ledger_hash: LedgerHash,
1197+
states: List<v2::MinaStateProtocolStateValueStableV2>,
1198+
}
1199+
1200+
let Some(parts) = parts else {
1201+
return Err(std::io::ErrorKind::Other.into());
1202+
};
1203+
1204+
let StagedLedgerAuxAndPendingCoinbasesValid {
1205+
scan_state,
1206+
staged_ledger_hash,
1207+
pending_coinbase,
1208+
needed_blocks,
1209+
} = &**parts;
1210+
1211+
let reconstruct_context = ReconstructContext {
1212+
accounts: snarked_ledger
1213+
.to_list()
1214+
.iter()
1215+
.map(v2::MinaBaseAccountBinableArgStableV2::from)
1216+
.collect(),
1217+
scan_state: scan_state.clone(),
1218+
pending_coinbase: pending_coinbase.clone(),
1219+
staged_ledger_hash: staged_ledger_hash.clone(),
1220+
states: needed_blocks.clone(),
1221+
};
1222+
1223+
const FILENAME: &str = "/tmp/failed_reconstruct_ctx.binprot";
1224+
1225+
use mina_p2p_messages::binprot::BinProtWrite;
1226+
let mut file = std::fs::File::create(FILENAME)?;
1227+
reconstruct_context.binprot_write(&mut file)?;
1228+
file.sync_all()?;
1229+
1230+
openmina_core::info!(
1231+
openmina_core::log::system_time();
1232+
kind = "LedgerService::dump - Failed reconstruct",
1233+
summary = format!("Reconstruction saved to: {FILENAME:?}")
1234+
);
1235+
1236+
Ok(())
1237+
}
1238+
11621239
/// Save staged ledger and block to file, when the application fail.
11631240
/// So we can easily reproduce the application both in Rust and OCaml, to compare them.
11641241
/// - https://github.com/openmina/openmina/blob/8e68037aafddd43842a54c8439baeafee4c6e1eb/ledger/src/staged_ledger/staged_ledger.rs#L5959

0 commit comments

Comments
 (0)