Skip to content

Commit 92e8d61

Browse files
committed
chore(wire): use hintsfile crate
1 parent 93eb84a commit 92e8d61

File tree

6 files changed

+82
-127
lines changed

6 files changed

+82
-127
lines changed

Cargo.lock

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

crates/floresta-chain/src/pruned_utreexo/consensus.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl Consensus {
224224
pub fn verify_block_transactions_swiftsync(
225225
transactions: &[Transaction],
226226
txids: Vec<Txid>,
227-
unspent_indexes: HashSet<u64>,
227+
unspent_indexes: HashSet<u32>,
228228
salt: &SipHashKeys,
229229
) -> Result<(SwiftSyncAgg, Amount), BlockchainError> {
230230
assert_eq!(transactions.len(), txids.len());
@@ -263,14 +263,20 @@ impl Consensus {
263263

264264
let mut spent_vouts = Vec::new();
265265
for (vout, out) in transaction.output.iter().enumerate() {
266+
// Special case: unspendable outputs do not count for the block `output_index`
267+
if Self::is_unspendable(&out.script_pubkey) {
268+
unspent_amount += out.value;
269+
continue;
270+
}
271+
272+
// According to the hints, is this output unspent? If not, add it to the aggregator
266273
let hinted_unspent = unspent_indexes.contains(&output_index);
267274

268-
if Self::is_unspendable(&out.script_pubkey) || hinted_unspent {
275+
if hinted_unspent {
269276
unspent_amount += out.value;
270277
} else {
271278
spent_vouts.push(vout as u32);
272279
}
273-
274280
output_index += 1;
275281
}
276282
// Only add spent outputs to the aggregator
@@ -518,7 +524,7 @@ impl Consensus {
518524
&self,
519525
block: &Block,
520526
height: u32,
521-
unspent_indexes: HashSet<u64>,
527+
unspent_indexes: HashSet<u32>,
522528
salt: &SipHashKeys,
523529
) -> Result<(SwiftSyncAgg, Amount), BlockchainError> {
524530
let txids = self.check_block(block, height)?;

crates/floresta-wire/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ categories = ["cryptography::cryptocurrencies", "network-programming"]
1818
bip324 = { version = "=0.7.0", features = [ "tokio" ] }
1919
bitcoin = { workspace = true }
2020
dns-lookup = { workspace = true }
21+
hintsfile = "0.1.1"
2122
rand = { workspace = true }
2223
rustls = "=0.23.27"
2324
rustreexo = { workspace = true }

crates/floresta-wire/src/p2p_wire/node/swift_sync_ctx.rs

Lines changed: 32 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
//! A node that downloads and validates the blockchain, but skips utreexo proofs as they aren't
22
//! needed to validate the UTXO set with the SwiftSync method.
33
4-
use std::collections::BTreeMap;
54
use std::collections::HashSet;
65
use std::fs::File;
7-
use std::io::Read;
8-
use std::io::Seek;
9-
use std::io::SeekFrom;
106
use std::sync::Arc;
117
use std::time::Duration;
128
use std::time::Instant;
@@ -23,6 +19,7 @@ use floresta_chain::BlockValidationErrors;
2319
use floresta_chain::BlockchainError;
2420
use floresta_chain::ThreadSafeChain;
2521
use floresta_common::service_flags;
22+
use hintsfile::Hintsfile;
2623
use rand::rngs::OsRng;
2724
use rand::RngCore;
2825
use rustreexo::accumulator::stump::Stump;
@@ -67,6 +64,9 @@ pub struct SwiftSync {
6764
/// equal than the theoretical supply limit at that height.
6865
supply: Amount,
6966

67+
/// The target height for the currently used SwiftSync hints.
68+
stop_height: u32,
69+
7070
/// Height at which SwiftSync was aborted, if any.
7171
///
7272
/// We abort when either the hints are found to be invalid or the current chain is invalid (we
@@ -100,12 +100,12 @@ where
100100
Chain: ThreadSafeChain,
101101
WireError: From<Chain::Error>,
102102
{
103-
/// Parses the SwiftSync hints file and returns the [`Hints`] struct.
104-
fn parse_hints_file(datadir: &str, network: Network) -> Hints {
103+
/// Parses the SwiftSync hints file and returns an in-memory [`Hintsfile`] representation.
104+
fn parse_hints_file(datadir: &str, network: Network) -> Hintsfile {
105105
let path = format!("{datadir}/{network}.hints");
106106

107-
let hints_file = File::open(path).expect("invalid hints file path");
108-
Hints::from_file(hints_file)
107+
let mut file = File::open(path).expect("invalid hints file path");
108+
Hintsfile::from_reader(&mut file).expect("couldn't read hints file")
109109
}
110110

111111
/// Generates a random salt for this SwiftSync session.
@@ -127,7 +127,7 @@ where
127127

128128
/// Computes the next blocks to request, and sends a GETDATA request, advancing
129129
/// `last_block_request` up to the SwiftSync hints `stop_height`.
130-
fn get_blocks_to_download(&mut self, stop_height: u32) {
130+
fn get_blocks_to_download(&mut self) {
131131
// If this request would make our inflight queue too long, postpone it
132132
if !self.can_request_more_blocks() || self.was_aborted() {
133133
return;
@@ -138,7 +138,8 @@ where
138138

139139
for _ in 0..SwiftSync::BLOCKS_PER_GETDATA {
140140
let next_height = self.last_block_request + 1;
141-
if next_height > stop_height {
141+
142+
if next_height > self.context.stop_height {
142143
// We need to reach it but not exceed it
143144
break;
144145
}
@@ -162,7 +163,7 @@ where
162163
}
163164

164165
/// Starts SwiftSync processing for up to `MAX_PARALLEL_WORKERS` pending blocks.
165-
fn pump_swiftsync(&mut self, hints: &mut Hints) -> Result<(), WireError> {
166+
fn pump_swiftsync(&mut self, hints: &mut Hintsfile) -> Result<(), WireError> {
166167
let processing = self
167168
.blocks
168169
.values()
@@ -202,7 +203,7 @@ where
202203
&mut self,
203204
block_hash: BlockHash,
204205
block_height: u32,
205-
hints: &mut Hints,
206+
hints: &mut Hintsfile,
206207
) -> Result<(), WireError> {
207208
debug!("processing block {block_hash}");
208209
let entry = self
@@ -213,7 +214,12 @@ where
213214
if entry.processing_since.is_some() {
214215
return Ok(()); // already being processed
215216
}
216-
let unspent_indexes: HashSet<u64> = hints.get_indexes(block_height).into_iter().collect();
217+
218+
let Some(block_hints) = hints.take_indices(block_height) else {
219+
error!("We tried processing block {block_height} but its hints are missing");
220+
return Ok(());
221+
};
222+
let unspent_indexes: HashSet<u32> = block_hints.into_iter().collect();
217223

218224
// Start the processing timer
219225
entry.processing_since = Some(Instant::now());
@@ -260,10 +266,10 @@ where
260266
// Parse the hints file and randomly fill the SwiftSync salt for this session
261267
let mut hints = Self::parse_hints_file(&self.datadir, self.network);
262268

263-
// Generate the random salt
269+
self.context.stop_height = hints.stop_height();
264270
self.context.salt = Self::generate_salt();
265271

266-
info!("Performing SwiftSync up to height {}", hints.stop_height);
272+
info!("Performing SwiftSync up to height {}", hints.stop_height());
267273

268274
let mut ticker = time::interval(SwiftSync::MAINTENANCE_TICK);
269275
// If we fall behind, don't "catch up" by running maintenance repeatedly
@@ -308,7 +314,7 @@ where
308314
/// Returns `LoopControl::Break` if we need to break the main `SwiftSync` loop, which may
309315
/// happen if the kill signal was set, we successfully finished SwiftSync, or we need to abort
310316
/// operation due to a validation error.
311-
async fn maintenance_tick(&mut self, hints: &mut Hints) -> LoopControl {
317+
async fn maintenance_tick(&mut self, hints: &mut Hintsfile) -> LoopControl {
312318
if *self.kill_signal.read().await {
313319
return LoopControl::Break;
314320
}
@@ -322,8 +328,10 @@ where
322328

323329
// If we have reached the SwiftSync stop height, we aren't waiting for inflight requested
324330
// blocks, and there's no in-memory block being processed, we have finished.
325-
if self.last_block_request == hints.stop_height && self.unprocessed_blocks() == 0 {
326-
self.handle_stop_height_reached(hints.stop_height);
331+
let finished_requesting = self.last_block_request == self.context.stop_height;
332+
333+
if finished_requesting && self.unprocessed_blocks() == 0 {
334+
self.handle_stop_height_reached();
327335
return LoopControl::Break;
328336
}
329337

@@ -355,15 +363,16 @@ where
355363

356364
try_and_log!(self.pump_swiftsync(hints));
357365

358-
self.get_blocks_to_download(hints.stop_height);
366+
self.get_blocks_to_download();
359367
LoopControl::Continue
360368
}
361369

362370
/// Called when we process the last SwiftSync block. Verifies that the produced aggregator is
363371
/// zero and supply is correct. On success marks the chain assumed and exits IBD.
364372
///
365373
/// If one of the two invariants fails, it sets the `abort_height` field.
366-
fn handle_stop_height_reached(&mut self, stop_height: u32) {
374+
fn handle_stop_height_reached(&mut self) {
375+
let stop_height = self.context.stop_height;
367376
let final_agg = self.context.agg;
368377
let final_supply = self.context.supply;
369378

@@ -395,7 +404,7 @@ where
395404
async fn handle_message(
396405
&mut self,
397406
msg: NodeNotification,
398-
hints: &mut Hints,
407+
hints: &mut Hintsfile,
399408
) -> Result<(), WireError> {
400409
match msg {
401410
NodeNotification::FromUser(request, responder) => {
@@ -446,7 +455,7 @@ where
446455
self.blocks.insert(hash, inflight_block);
447456

448457
self.pump_swiftsync(hints)?;
449-
self.get_blocks_to_download(hints.stop_height);
458+
self.get_blocks_to_download();
450459
}
451460

452461
PeerMessages::Ready(version) => {
@@ -479,7 +488,7 @@ where
479488
result: WorkerResult,
480489
block_hash: BlockHash,
481490
height: u32,
482-
hints: &mut Hints,
491+
hints: &mut Hintsfile,
483492
) -> Result<(), WireError> {
484493
// This block has already been processed: open space for a new worker
485494
let block = self
@@ -602,91 +611,3 @@ where
602611
}
603612
}
604613
}
605-
606-
#[derive(Debug)]
607-
pub struct Hints {
608-
pub(crate) map: BTreeMap<u32, u64>,
609-
pub(crate) file: File,
610-
pub(crate) stop_height: u32,
611-
}
612-
613-
impl Hints {
614-
// # Panics
615-
//
616-
// Panics when expected data is not present, or the hintfile overflows the maximum blockheight
617-
pub fn from_file(mut file: File) -> Self {
618-
let mut map = BTreeMap::new();
619-
let mut magic = [0; 4];
620-
file.read_exact(&mut magic).unwrap();
621-
assert_eq!(magic, [0x55, 0x54, 0x58, 0x4f]);
622-
let mut ver = [0; 1];
623-
file.read_exact(&mut ver).unwrap();
624-
if u8::from_le_bytes(ver) != 0x00 {
625-
core::panic!("Unsupported file version.");
626-
}
627-
let mut stop_height = [0; 4];
628-
file.read_exact(&mut stop_height).expect("empty file");
629-
let stop_height = u32::from_le_bytes(stop_height);
630-
for _ in 1..=stop_height {
631-
let mut height = [0; 4];
632-
file.read_exact(&mut height)
633-
.expect("expected kv pair does not exist.");
634-
let height = u32::from_le_bytes(height);
635-
let mut file_pos = [0; 8];
636-
file.read_exact(&mut file_pos)
637-
.expect("expected kv pair does not exist.");
638-
let file_pos = u64::from_le_bytes(file_pos);
639-
map.insert(height, file_pos);
640-
}
641-
Self {
642-
map,
643-
file,
644-
stop_height,
645-
}
646-
}
647-
648-
/// Get the stop height of the hint file.
649-
pub fn stop_height(&self) -> u32 {
650-
self.stop_height
651-
}
652-
653-
/// # Panics
654-
///
655-
/// If there are no offset present at that height, aka an overflow, or the entry has already
656-
/// been fetched.
657-
pub fn get_indexes(&mut self, height: u32) -> Vec<u64> {
658-
let file_pos = self
659-
.map
660-
.get(&height)
661-
.cloned()
662-
.expect("block height overflow");
663-
664-
// Move the file cursor to the correct byte offset
665-
self.file
666-
.seek(SeekFrom::Start(file_pos))
667-
.expect("missing file position.");
668-
669-
// Read the next 4 bytes (little-endian) which store how many bits follow
670-
let mut bits_arr = [0; 4];
671-
self.file.read_exact(&mut bits_arr).unwrap();
672-
let num_bits = u32::from_le_bytes(bits_arr);
673-
674-
let mut unspents = Vec::new();
675-
676-
let mut curr_byte: u8 = 0;
677-
for bit_pos in 0..num_bits {
678-
let leftovers = bit_pos % 8;
679-
if leftovers == 0 {
680-
let mut single_byte_arr = [0; 1];
681-
self.file.read_exact(&mut single_byte_arr).unwrap();
682-
curr_byte = u8::from_le_bytes(single_byte_arr);
683-
}
684-
685-
// Check current bit in curr_byte; if it's 1, push this txout index
686-
if ((curr_byte >> leftovers) & 0x01) == 0x01 {
687-
unspents.push(bit_pos as u64);
688-
}
689-
}
690-
unspents
691-
}
692-
}
-1.55 KB
Binary file not shown.

0 commit comments

Comments
 (0)