diff --git a/src/blockchain/blockstore.rs b/src/blockchain/blockstore.rs index 763ac7e75..e3110a973 100644 --- a/src/blockchain/blockstore.rs +++ b/src/blockchain/blockstore.rs @@ -40,8 +40,8 @@ impl HeaderStore { let store = Self(tree); // In case the store is empty, initialize it with the genesis header. - let genesis_header = Header::genesis_header(genesis_ts, genesis_data); if store.0.is_empty() { + let genesis_header = Header::genesis_header(genesis_ts, genesis_data); store.insert(&[genesis_header])?; } diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 1868736ff..8671d260b 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -20,7 +20,7 @@ use darkfi_serial::serialize; use log::debug; use crate::{ - consensus::{Block, BlockInfo}, + consensus::{Block, BlockInfo, SlotCheckpoint}, util::time::Timestamp, Error, Result, }; @@ -28,6 +28,9 @@ use crate::{ pub mod blockstore; pub use blockstore::{BlockOrderStore, BlockStore, HeaderStore}; +pub mod slotcheckpointstore; +pub use slotcheckpointstore::SlotCheckpointStore; + pub mod nfstore; pub use nfstore::NullifierStore; @@ -51,6 +54,8 @@ pub struct Blockchain { pub blocks: BlockStore, /// Block order sled tree pub order: BlockOrderStore, + /// Slot checkpoints sled tree + pub slot_checkpoints: SlotCheckpointStore, /// Transactions sled tree pub transactions: TxStore, /// Nullifiers sled tree @@ -69,6 +74,7 @@ impl Blockchain { let headers = HeaderStore::new(db, genesis_ts, genesis_data)?; let blocks = BlockStore::new(db, genesis_ts, genesis_data)?; let order = BlockOrderStore::new(db, genesis_ts, genesis_data)?; + let slot_checkpoints = SlotCheckpointStore::new(db)?; let transactions = TxStore::new(db)?; let nullifiers = NullifierStore::new(db)?; let merkle_roots = RootStore::new(db)?; @@ -80,6 +86,7 @@ impl Blockchain { headers, blocks, order, + slot_checkpoints, transactions, nullifiers, merkle_roots, @@ -211,4 +218,10 @@ impl Blockchain { let block = blocks[0].clone().unwrap(); Ok((slot, block.lead_info.offset)) } + + /// Retrieve n checkpoints after given start slot. + pub fn get_slot_checkpoints_after(&self, slot: u64, n: u64) -> Result> { + debug!("get_slot_checkpoints_after(): {} -> {}", slot, n); + self.slot_checkpoints.get_after(slot, n) + } } diff --git a/src/blockchain/slotcheckpointstore.rs b/src/blockchain/slotcheckpointstore.rs new file mode 100644 index 000000000..867ea0ef8 --- /dev/null +++ b/src/blockchain/slotcheckpointstore.rs @@ -0,0 +1,130 @@ +/* This file is part of DarkFi (https://dark.fi) + * + * Copyright (C) 2020-2022 Dyne.org foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use darkfi_serial::{deserialize, serialize}; + +use crate::{consensus::SlotCheckpoint, Error, Result}; + +const SLED_SLOT_CHECKPOINT_TREE: &[u8] = b"_slot_checkpoints"; + +/// The `SlotCheckpointStore` is a `sled` tree storing the checkpoints of the +/// blockchain's slots, where the key is the slot uid, and the value is +/// is the serialized checkpoint. +#[derive(Clone)] +pub struct SlotCheckpointStore(sled::Tree); + +impl SlotCheckpointStore { + /// Opens a new or existing `SlotCheckpointStore` on the given sled database. + pub fn new(db: &sled::Db) -> Result { + let tree = db.open_tree(SLED_SLOT_CHECKPOINT_TREE)?; + let store = Self(tree); + + // In case the store is empty, initialize it with the genesis checkpoint. + if store.0.is_empty() { + let genesis_checkpoint = SlotCheckpoint::genesis_slot_checkpoint(); + store.insert(&[genesis_checkpoint])?; + } + + Ok(store) + } + + /// Insert a slice of [`SlotCheckpoint`] into the slotcheckpointstore. + /// With sled, the operation is done as a batch. + /// The block slot is used as the key, while value is the serialized [`SlotCheckpoint`] itself. + pub fn insert(&self, checkpoints: &[SlotCheckpoint]) -> Result<()> { + let mut batch = sled::Batch::default(); + + for checkpoint in checkpoints { + let serialized = serialize(checkpoint); + batch.insert(&checkpoint.slot.to_be_bytes(), serialized); + } + + self.0.apply_batch(batch)?; + Ok(()) + } + + /// Check if the slotcheckpointstore contains a given slot. + pub fn contains(&self, slot: u64) -> Result { + Ok(self.0.contains_key(slot.to_be_bytes())?) + } + + /// Fetch given slots from the slotcheckpointstore. + /// The resulting vector contains `Option`, which is `Some` if the slot + /// was found in the slotcheckpointstore, and otherwise it is `None`, if it has not. + /// The second parameter is a boolean which tells the function to fail in + /// case at least one slot was not found. + pub fn get(&self, slots: &[u64], strict: bool) -> Result>> { + let mut ret = Vec::with_capacity(slots.len()); + + for slot in slots { + if let Some(found) = self.0.get(slot.to_be_bytes())? { + let checkpoint = deserialize(&found)?; + ret.push(Some(checkpoint)); + } else { + if strict { + return Err(Error::SlotNotFound(*slot)) + } + ret.push(None); + } + } + + Ok(ret) + } + + /// Retrieve all slot checkpointss from the slotcheckpointstore. + /// Be careful as this will try to load everything in memory. + pub fn get_all(&self) -> Result> { + let mut slots = vec![]; + + for slot in self.0.iter() { + let (_, value) = slot.unwrap(); + let checkpoint = deserialize(&value)?; + slots.push(checkpoint); + } + + Ok(slots) + } + + /// Fetch n slot checkpoints after given slot. In the iteration, if a slot is not + /// found, the iteration stops and the function returns what it has found + /// so far in the `SlotCheckpointStore`. + pub fn get_after(&self, slot: u64, n: u64) -> Result> { + let mut ret = vec![]; + + let mut key = slot; + let mut counter = 0; + while counter <= n { + if let Some(found) = self.0.get_gt(key.to_be_bytes())? { + let key_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap(); + key = u64::from_be_bytes(key_bytes); + let checkpoint = deserialize(&found.1)?; + ret.push(checkpoint); + counter += 1; + continue + } + break + } + + Ok(ret) + } + + /// Retrieve records count + pub fn len(&self) -> usize { + self.0.len() + } +} diff --git a/src/consensus/mod.rs b/src/consensus/mod.rs index 0fd6ad7ae..4206a4a87 100644 --- a/src/consensus/mod.rs +++ b/src/consensus/mod.rs @@ -29,6 +29,7 @@ pub use lead_info::{LeadInfo, LeadProof}; /// Consensus state pub mod state; +pub use state::SlotCheckpoint; /// Consensus validator state pub mod validator; diff --git a/src/consensus/state.rs b/src/consensus/state.rs index 69f032c23..fcc6482d2 100644 --- a/src/consensus/state.rs +++ b/src/consensus/state.rs @@ -120,3 +120,31 @@ impl net::Message for ConsensusResponse { "consensusresponse" } } + +/// Auxiliary structure used to keep track of slot validation parameters. +#[derive(Debug, SerialEncodable, SerialDecodable)] +pub struct SlotCheckpoint { + /// Slot UID + pub slot: u64, + /// Slot eta + pub eta: pallas::Base, + /// Slot sigma1 + pub sigma1: pallas::Base, + /// Slot sigma2 + pub sigma2: pallas::Base, +} + +impl SlotCheckpoint { + pub fn new(slot: u64, eta: pallas::Base, sigma1: pallas::Base, sigma2: pallas::Base) -> Self { + Self { slot, eta, sigma1, sigma2 } + } + + /// Generate the genesis slot checkpoint. + pub fn genesis_slot_checkpoint() -> Self { + let eta = pallas::Base::zero(); + let sigma1 = pallas::Base::zero(); + let sigma2 = pallas::Base::zero(); + + Self::new(0, eta, sigma1, sigma2) + } +}