openmina_bootstrap_sandbox/
record.rs

1use std::{
2    collections::{BTreeMap, VecDeque},
3    fs::{self, File},
4    io,
5    path::Path,
6};
7
8use binprot::{BinProtRead, BinProtWrite};
9use libp2p::Swarm;
10use mina_p2p_messages::{
11    hash::MinaHash,
12    list::List,
13    rpc::{
14        GetAncestryV2, GetBestTipV2, GetStagedLedgerAuxAndPendingCoinbasesAtHashV2,
15        GetTransitionChainProofV1ForV2, GetTransitionChainV2, WithHashV1,
16    },
17    v2,
18};
19
20use super::{
21    behaviour::Behaviour, bootstrap::Storage, client::Client, snarked_ledger::SnarkedLedger,
22};
23
24pub async fn run(swarm: Swarm<Behaviour>, path_main: &Path, bootstrap: bool) {
25    let mut client = Client::new(swarm);
26
27    fs::create_dir_all(path_main).unwrap();
28
29    let best_tip = client.rpc::<GetBestTipV2>(()).await.unwrap().unwrap();
30
31    let head_height = best_tip
32        .data
33        .header
34        .protocol_state
35        .body
36        .consensus_state
37        .blockchain_length
38        .as_u32();
39
40    log::info!("will record {head_height}");
41    let path = path_main.join(head_height.to_string());
42    fs::create_dir_all(path.join("ledgers")).unwrap();
43
44    let mut file = File::create(path.join("best_tip")).unwrap();
45    Some(best_tip.clone()).binprot_write(&mut file).unwrap();
46
47    // let mut file = File::create(path.join("best_tip.json")).unwrap();
48    // file.write_all(serde_json::to_string(&best_tip).unwrap().as_bytes())
49    //     .unwrap();
50
51    let q = best_tip
52        .data
53        .header
54        .protocol_state
55        .body
56        .consensus_state
57        .clone();
58    let hash = MinaHash::try_hash(&best_tip.data.header.protocol_state)
59        .unwrap()
60        .into();
61    let q = WithHashV1 { data: q, hash };
62    let ancestry = client.rpc::<GetAncestryV2>(q).await.unwrap().unwrap();
63
64    let mut file = File::create(path.join("ancestry")).unwrap();
65    Some(ancestry.clone()).binprot_write(&mut file).unwrap();
66
67    let snarked_protocol_state = best_tip.proof.1.header.protocol_state;
68
69    {
70        let staking_epoch_ledger_hash = snarked_protocol_state
71            .body
72            .consensus_state
73            .staking_epoch_data
74            .ledger
75            .hash
76            .clone();
77        let staking_epoch_ledger_hash_str =
78            match serde_json::to_value(&staking_epoch_ledger_hash).unwrap() {
79                serde_json::Value::String(s) => s,
80                _ => panic!(),
81            };
82        let mut epoch_ledger = match File::open(path.join("staking_epoch_ledger.bin")) {
83            Ok(file) => SnarkedLedger::load_bin(file).unwrap(),
84            Err(_) => {
85                match File::open(path.parent().expect("msg").join("staking_epoch_ledger.bin")) {
86                    Ok(file) => SnarkedLedger::load_bin(file).unwrap(),
87                    Err(_) => SnarkedLedger::empty(),
88                }
89            }
90        };
91        epoch_ledger
92            .sync_new(&mut client, &staking_epoch_ledger_hash)
93            .await;
94        epoch_ledger
95            .store_bin(
96                File::create(path.join("ledgers").join(staking_epoch_ledger_hash_str)).unwrap(),
97            )
98            .unwrap();
99        epoch_ledger
100            .store_bin(File::create(path.join("staking_epoch_ledger.bin")).unwrap())
101            .unwrap();
102        fs::copy(
103            path.join("staking_epoch_ledger.bin"),
104            path.parent()
105                .expect("must have parent")
106                .join("staking_epoch_ledger.bin"),
107        )
108        .unwrap();
109    }
110    let next_epoch_ledger_hash = snarked_protocol_state
111        .body
112        .consensus_state
113        .next_epoch_data
114        .ledger
115        .hash
116        .clone();
117    let next_epoch_ledger_hash_str = match serde_json::to_value(&next_epoch_ledger_hash).unwrap() {
118        serde_json::Value::String(s) => s,
119        _ => panic!(),
120    };
121    let mut epoch_ledger = match File::open(path.join("epoch_ledger.bin")) {
122        Ok(file) => SnarkedLedger::load_bin(file).unwrap(),
123        Err(_) => match File::open(path.parent().expect("msg").join("epoch_ledger.bin")) {
124            Ok(file) => SnarkedLedger::load_bin(file).unwrap(),
125            Err(_) => SnarkedLedger::empty(),
126        },
127    };
128    epoch_ledger
129        .sync_new(&mut client, &next_epoch_ledger_hash)
130        .await;
131    epoch_ledger
132        .store_bin(File::create(path.join("ledgers").join(next_epoch_ledger_hash_str)).unwrap())
133        .unwrap();
134    epoch_ledger
135        .store_bin(File::create(path.join("epoch_ledger.bin")).unwrap())
136        .unwrap();
137    fs::copy(
138        path.join("epoch_ledger.bin"),
139        path.parent()
140            .expect("must have parent")
141            .join("epoch_ledger.bin"),
142    )
143    .unwrap();
144
145    let snarked_ledger_hash = snarked_protocol_state
146        .body
147        .blockchain_state
148        .ledger_proof_statement
149        .target
150        .first_pass_ledger
151        .clone();
152    let snarked_ledger_hash_str = match serde_json::to_value(&snarked_ledger_hash).unwrap() {
153        serde_json::Value::String(s) => s,
154        _ => panic!(),
155    };
156    log::info!("snarked_ledger_hash: {snarked_ledger_hash_str}");
157    let mut snarked_ledger = match File::open(path.join("current_ledger.bin")) {
158        Ok(file) => SnarkedLedger::load_bin(file).unwrap(),
159        Err(_) => match File::open(path.parent().expect("msg").join("current_ledger.bin")) {
160            Ok(file) => SnarkedLedger::load_bin(file).unwrap(),
161            Err(_) => SnarkedLedger::empty(),
162        },
163    };
164    snarked_ledger
165        .sync_new(&mut client, &snarked_ledger_hash)
166        .await;
167    snarked_ledger
168        .store_bin(File::create(path.join("ledgers").join(snarked_ledger_hash_str)).unwrap())
169        .unwrap();
170    snarked_ledger
171        .store_bin(File::create(path.join("current_ledger.bin")).unwrap())
172        .unwrap();
173    fs::copy(
174        path.join("current_ledger.bin"),
175        path.parent()
176            .expect("must have parent")
177            .join("current_ledger.bin"),
178    )
179    .unwrap();
180
181    let expected_hash = snarked_protocol_state
182        .body
183        .blockchain_state
184        .staged_ledger_hash
185        .clone();
186
187    let snarked_block_hash = MinaHash::try_hash(&snarked_protocol_state).unwrap();
188    let snarked_block_hash =
189        v2::StateHash::from(v2::DataHashLibStateHashStableV1(snarked_block_hash.into()));
190    log::info!("downloading staged_ledger_aux and pending_coinbases at {snarked_block_hash}");
191    let info = client
192        .rpc::<GetStagedLedgerAuxAndPendingCoinbasesAtHashV2>(snarked_block_hash.0.clone())
193        .await
194        .unwrap();
195    let mut file = File::create(path.join("staged_ledger_aux")).unwrap();
196    info.binprot_write(&mut file).unwrap();
197
198    let snarked_height = snarked_protocol_state
199        .body
200        .consensus_state
201        .blockchain_length
202        .as_u32();
203    log::info!("will bootstrap: {}..={head_height}", snarked_height);
204
205    let mut blocks = VecDeque::new();
206    blocks.push_back(best_tip.data);
207    download_blocks(
208        &mut client,
209        &mut blocks,
210        &path_main.join("blocks"),
211        head_height,
212        snarked_height,
213    )
214    .await;
215
216    if bootstrap {
217        let mut storage = Storage::new(snarked_ledger.inner, info, expected_hash);
218
219        let mut prev_protocol_state = snarked_protocol_state;
220        while let Some(block) = blocks.pop_back() {
221            storage.apply_block(&block, &prev_protocol_state);
222            prev_protocol_state = block.header.protocol_state.clone();
223        }
224    }
225}
226
227async fn download_blocks(
228    engine: &mut Client,
229    blocks: &mut VecDeque<v2::MinaBlockBlockStableV2>,
230    dir: &Path,
231    head_height: u32,
232    snarked_height: u32,
233) {
234    let create_dir = |dir: &Path| {
235        fs::create_dir_all(dir)
236            .or_else(|e| {
237                if e.kind() == io::ErrorKind::AlreadyExists {
238                    Ok(())
239                } else {
240                    Err(e)
241                }
242            })
243            .unwrap()
244    };
245    create_dir(dir);
246
247    let mut table = match File::open(dir.join("table.json")) {
248        Ok(f) => serde_json::from_reader(f).unwrap(),
249        Err(_) => BTreeMap::<String, u32>::new(),
250    };
251
252    log::info!("need blocks {}..{head_height}", snarked_height + 1);
253    for i in ((snarked_height + 1)..head_height).rev() {
254        let last_protocol_state = &blocks.back().unwrap().header.protocol_state;
255        let this_hash = &last_protocol_state.previous_state_hash;
256        let this_height = last_protocol_state
257            .body
258            .consensus_state
259            .blockchain_length
260            .as_u32()
261            - 1;
262        let dir = dir.join(this_height.to_string());
263        create_dir(&dir);
264        table.insert(this_hash.to_string(), this_height);
265        let new = if let Ok(mut file) = File::open(dir.join(this_hash.to_string())) {
266            v2::MinaBlockBlockStableV2::binprot_read(&mut file).unwrap()
267        } else {
268            log::info!("downloading block {i}");
269            let new: Vec<_> = engine
270                .rpc::<GetTransitionChainV2>(List::one(this_hash.0.clone()))
271                .await
272                .unwrap()
273                .unwrap()
274                .into_iter()
275                .collect();
276            let mut file = File::create(dir.join(this_hash.to_string())).unwrap();
277            new[0].binprot_write(&mut file).unwrap();
278            if let Ok(new_proof) = engine
279                .rpc::<GetTransitionChainProofV1ForV2>(this_hash.0.clone())
280                .await
281            {
282                let mut file = File::create(dir.join(format!("proof_{this_hash}"))).unwrap();
283                new_proof.binprot_write(&mut file).unwrap();
284            }
285            new[0].clone()
286        };
287        blocks.push_back(new);
288    }
289    let file = File::create(dir.join("table.json")).unwrap();
290    serde_json::to_writer(file, &table).unwrap();
291    log::info!("have blocks {}..{head_height}", snarked_height + 1);
292}