mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
blockchain: Cleanup and documentation.
This commit is contained in:
@@ -56,7 +56,7 @@ impl Darkfid {
|
||||
pub async fn merkle_roots(&self, id: Value, _params: &[Value]) -> JsonResult {
|
||||
let roots: Vec<MerkleNode> =
|
||||
match self.validator_state.read().await.blockchain.merkle_roots.get_all() {
|
||||
Ok(v) => v.iter().map(|x| x.unwrap()).collect(),
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Failed getting merkle roots from rootstore: {}", e);
|
||||
return jsonrpc::error(InternalError, None, id).into()
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use log::debug;
|
||||
use sled::Batch;
|
||||
|
||||
use crate::{
|
||||
consensus::{util::Timestamp, Block},
|
||||
consensus::{Block, Timestamp},
|
||||
util::serial::{deserialize, serialize},
|
||||
Error, Result,
|
||||
};
|
||||
@@ -10,6 +7,9 @@ use crate::{
|
||||
const SLED_BLOCK_TREE: &[u8] = b"_blocks";
|
||||
const SLED_BLOCK_ORDER_TREE: &[u8] = b"_block_order";
|
||||
|
||||
/// The `BlockStore` is a `sled` tree storing all the blockchain's blocks
|
||||
/// where the key is the block's hash, and value is the serialized block.
|
||||
#[derive(Clone)]
|
||||
pub struct BlockStore(sled::Tree);
|
||||
|
||||
impl BlockStore {
|
||||
@@ -18,9 +18,10 @@ impl BlockStore {
|
||||
let tree = db.open_tree(SLED_BLOCK_TREE)?;
|
||||
let store = Self(tree);
|
||||
|
||||
// In case the store is empty, create the genesis block.
|
||||
// In case the store is empty, initialize it with the genesis block.
|
||||
if store.0.is_empty() {
|
||||
store.insert(&[Block::genesis_block(genesis_ts, genesis_data)])?;
|
||||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data);
|
||||
store.insert(&[genesis_block])?;
|
||||
}
|
||||
|
||||
Ok(store)
|
||||
@@ -29,12 +30,14 @@ impl BlockStore {
|
||||
/// Insert a slice of [`Block`] into the blockstore. With sled, the
|
||||
/// operation is done as a batch.
|
||||
/// The blocks are hashed with BLAKE3 and this blockhash is used as
|
||||
/// the key, while value is the serialized block itself.
|
||||
/// the key, while value is the serialized [`Block`] itself.
|
||||
/// On success, the function returns the block hashes in the same order.
|
||||
pub fn insert(&self, blocks: &[Block]) -> Result<Vec<blake3::Hash>> {
|
||||
let mut ret = Vec::with_capacity(blocks.len());
|
||||
let mut batch = Batch::default();
|
||||
for i in blocks {
|
||||
let serialized = serialize(i);
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for block in blocks {
|
||||
let serialized = serialize(block);
|
||||
let blockhash = blake3::hash(&serialized);
|
||||
batch.insert(blockhash.as_bytes(), serialized);
|
||||
ret.push(blockhash);
|
||||
@@ -44,19 +47,26 @@ impl BlockStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Check if the blockstore contains a given blockhash.
|
||||
pub fn contains(&self, blockhash: &blake3::Hash) -> Result<bool> {
|
||||
Ok(self.0.contains_key(blockhash.as_bytes())?)
|
||||
}
|
||||
|
||||
/// Fetch given blockhashes from the blockstore.
|
||||
/// The resulting vector contains `Option` which is `Some` if the block
|
||||
/// was found in the blockstore, and `None`, if it has not.
|
||||
/// The resulting vector contains `Option`, which is `Some` if the block
|
||||
/// was found in the blockstore, 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 block was not found.
|
||||
pub fn get(&self, blockhashes: &[blake3::Hash], strict: bool) -> Result<Vec<Option<Block>>> {
|
||||
let mut ret = Vec::with_capacity(blockhashes.len());
|
||||
|
||||
for i in blockhashes {
|
||||
if let Some(found) = self.0.get(i.as_bytes())? {
|
||||
for hash in blockhashes {
|
||||
if let Some(found) = self.0.get(hash.as_bytes())? {
|
||||
let block = deserialize(&found)?;
|
||||
ret.push(Some(block));
|
||||
} else {
|
||||
if strict {
|
||||
let s = i.to_hex().as_str().to_string();
|
||||
let s = hash.to_hex().as_str().to_string();
|
||||
return Err(Error::BlockNotFound(s))
|
||||
}
|
||||
ret.push(None);
|
||||
@@ -66,50 +76,52 @@ impl BlockStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Check if the blockstore contains a given blockhash.
|
||||
pub fn contains(&self, blockhash: blake3::Hash) -> Result<bool> {
|
||||
Ok(self.0.contains_key(blockhash.as_bytes())?)
|
||||
}
|
||||
|
||||
/// Retrieve all blocks.
|
||||
/// Retrieve all blocks from the blockstore in the form of a tuple
|
||||
/// (`blockhash`, `block`).
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<Option<(blake3::Hash, Block)>>> {
|
||||
pub fn get_all(&self) -> Result<Vec<(blake3::Hash, Block)>> {
|
||||
let mut blocks = vec![];
|
||||
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, v) = r.unwrap();
|
||||
let hash_bytes: [u8; 32] = k.as_ref().try_into().unwrap();
|
||||
let block = deserialize(&v)?;
|
||||
blocks.push(Some((hash_bytes.into(), block)));
|
||||
blocks.push((hash_bytes.into(), block));
|
||||
}
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
}
|
||||
|
||||
/// The `BlockOrderStore` is a `sled` tree storing the order of the
|
||||
/// blockchain's slots, where the key is the slot uid, and the value is
|
||||
/// the block's hash. [`BlockStore`] can be queried with this hash.
|
||||
pub struct BlockOrderStore(sled::Tree);
|
||||
|
||||
impl BlockOrderStore {
|
||||
/// Opens a new or existing `BlockOderStore` on the given sled database.
|
||||
/// Opens a new or existing `BlockOrderStore` on the given sled database.
|
||||
pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> {
|
||||
let tree = db.open_tree(SLED_BLOCK_ORDER_TREE)?;
|
||||
let store = Self(tree);
|
||||
|
||||
// In case the store is empty, create the genesis block.
|
||||
// In case the store is empty, initialize it with the genesis block.
|
||||
if store.0.is_empty() {
|
||||
let block = Block::genesis_block(genesis_ts, genesis_data);
|
||||
let blockhash = blake3::hash(&serialize(&block));
|
||||
store.insert(&[block.sl], &[blockhash])?;
|
||||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data);
|
||||
let blockhash = blake3::hash(&serialize(&genesis_block));
|
||||
store.insert(&[genesis_block.sl], &[blockhash])?;
|
||||
}
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// Insert a slice of slots and blockhashes into the store.
|
||||
/// The block slot is used as the key, and the hash as value.
|
||||
/// Insert a slice of slots and blockhashes into the store. With sled, the
|
||||
/// operation is done as a batch.
|
||||
/// The block slot is used as the key, and the blockhash is used as value.
|
||||
pub fn insert(&self, slots: &[u64], hashes: &[blake3::Hash]) -> Result<()> {
|
||||
assert_eq!(slots.len(), hashes.len());
|
||||
let mut batch = Batch::default();
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for (i, sl) in slots.iter().enumerate() {
|
||||
batch.insert(&sl.to_be_bytes(), hashes[i].as_bytes());
|
||||
}
|
||||
@@ -118,20 +130,27 @@ impl BlockOrderStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve all hashes given slots.
|
||||
pub fn get(&self, slots: &[u64], strict: bool) -> Result<Vec<Option<blake3::Hash>>> {
|
||||
//let mut ret = Vec::with_capacity(slots.len());
|
||||
let mut ret = vec![];
|
||||
/// Check if the blockorderstore contains a given slot.
|
||||
pub fn contains(&self, slot: u64) -> Result<bool> {
|
||||
Ok(self.0.contains_key(slot.to_be_bytes())?)
|
||||
}
|
||||
|
||||
for i in slots {
|
||||
if let Some(found) = self.0.get(i.to_be_bytes())? {
|
||||
/// Fetch given slots from the blockorderstore.
|
||||
/// The resulting vector contains `Option`, which is `Some` if the slot
|
||||
/// was found in the blockstore, 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<Vec<Option<blake3::Hash>>> {
|
||||
let mut ret = Vec::with_capacity(slots.len());
|
||||
|
||||
for slot in slots {
|
||||
if let Some(found) = self.0.get(slot.to_be_bytes())? {
|
||||
let hash_bytes: [u8; 32] = found.as_ref().try_into().unwrap();
|
||||
let hash = blake3::Hash::from(hash_bytes);
|
||||
ret.push(Some(hash));
|
||||
} else {
|
||||
if strict {
|
||||
debug!("BlockOrderStore::get() Slot {} not found", i);
|
||||
return Err(Error::SlotNotFound(*i))
|
||||
return Err(Error::SlotNotFound(*slot))
|
||||
}
|
||||
ret.push(None);
|
||||
}
|
||||
@@ -140,7 +159,28 @@ impl BlockOrderStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Retrieve n hashes after given slot.
|
||||
/// Retrieve all slots from the blockorderstore in the form of a tuple
|
||||
/// (`slot`, `blockhash`).
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<(u64, blake3::Hash)>> {
|
||||
let mut slots = vec![];
|
||||
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, v) = r.unwrap();
|
||||
let slot_bytes: [u8; 8] = k.as_ref().try_into().unwrap();
|
||||
let hash_bytes: [u8; 32] = v.as_ref().try_into().unwrap();
|
||||
let slot = u64::from_be_bytes(slot_bytes);
|
||||
let hash = blake3::Hash::from(hash_bytes);
|
||||
slots.push((slot, hash));
|
||||
}
|
||||
|
||||
Ok(slots)
|
||||
}
|
||||
|
||||
/// Fetch n hashes 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 `BlockOrderStore`.
|
||||
pub fn get_after(&self, slot: u64, n: u64) -> Result<Vec<blake3::Hash>> {
|
||||
let mut ret = vec![];
|
||||
|
||||
@@ -153,42 +193,25 @@ impl BlockOrderStore {
|
||||
let block_hash = deserialize(&found.1)?;
|
||||
ret.push(block_hash);
|
||||
counter += 1;
|
||||
} else {
|
||||
break
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Retrieve the last block hash in the tree, based on the Ord
|
||||
/// implementation for Vec<u8>.
|
||||
pub fn get_last(&self) -> Result<Option<(u64, blake3::Hash)>> {
|
||||
if let Some(found) = self.0.last()? {
|
||||
let slot_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap();
|
||||
let hash_bytes: [u8; 32] = found.1.as_ref().try_into().unwrap();
|
||||
let slot = u64::from_be_bytes(slot_bytes);
|
||||
let hash = blake3::Hash::from(hash_bytes);
|
||||
return Ok(Some((slot, hash)))
|
||||
}
|
||||
/// Fetch the last block hash in the tree, based on the `Ord`
|
||||
/// implementation for `Vec<u8>`. This should not be able to
|
||||
/// fail because we initialize the store with the genesis block.
|
||||
pub fn get_last(&self) -> Result<(u64, blake3::Hash)> {
|
||||
let found = self.0.last()?.unwrap();
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
let slot_bytes: [u8; 8] = found.0.as_ref().try_into().unwrap();
|
||||
let hash_bytes: [u8; 32] = found.1.as_ref().try_into().unwrap();
|
||||
let slot = u64::from_be_bytes(slot_bytes);
|
||||
let hash = blake3::Hash::from(hash_bytes);
|
||||
|
||||
/// Retrieve all block hashes.
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<Option<(u64, blake3::Hash)>>> {
|
||||
let mut ret = vec![];
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, v) = r.unwrap();
|
||||
let slot_bytes: [u8; 8] = k.as_ref().try_into().unwrap();
|
||||
let hash_bytes: [u8; 32] = v.as_ref().try_into().unwrap();
|
||||
let slot = u64::from_be_bytes(slot_bytes);
|
||||
let hash = blake3::Hash::from(hash_bytes);
|
||||
ret.push(Some((slot, hash)));
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
Ok((slot, hash))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use sled::Batch;
|
||||
|
||||
use crate::{
|
||||
consensus::{Block, StreamletMetadata, Timestamp},
|
||||
util::serial::{deserialize, serialize},
|
||||
@@ -8,14 +6,19 @@ use crate::{
|
||||
|
||||
const SLED_STREAMLET_METADATA_TREE: &[u8] = b"_streamlet_metadata";
|
||||
|
||||
/// The `StreamletMetadataStore` is a `sled` tree storing all the blockchain's
|
||||
/// blocks' metadata used by the Streamlet consensus protocol, where the key
|
||||
/// is the block's hash, and the value is the serialized metadata.
|
||||
#[derive(Clone)]
|
||||
pub struct StreamletMetadataStore(sled::Tree);
|
||||
|
||||
impl StreamletMetadataStore {
|
||||
/// Opens a new or existing `StreamletMetadataStore` on the given sled database.
|
||||
pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> {
|
||||
let tree = db.open_tree(SLED_STREAMLET_METADATA_TREE)?;
|
||||
let store = Self(tree);
|
||||
|
||||
// In case the store is empty, add genesis metadata.
|
||||
// In case the store is empty, initialize it with the genesis block.
|
||||
if store.0.is_empty() {
|
||||
let genesis_block = Block::genesis_block(genesis_ts, genesis_data);
|
||||
let genesis_hash = blake3::hash(&serialize(&genesis_block));
|
||||
@@ -33,14 +36,14 @@ impl StreamletMetadataStore {
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
/// Insert [`StreamletMetadata`] into the `MetadataStore`.
|
||||
/// The blockhash for the metadata is used as the key,
|
||||
/// where value is the serialized metadata.
|
||||
pub fn insert(&self, blocks: &[blake3::Hash], metadatas: &[StreamletMetadata]) -> Result<()> {
|
||||
assert_eq!(blocks.len(), metadatas.len());
|
||||
let mut batch = Batch::default();
|
||||
/// Insert a slice of blockhashes and respective metadata into the store.
|
||||
/// With sled, the operation is done as a batch.
|
||||
/// The block hash is used as the key, and the metadata is used as value.
|
||||
pub fn insert(&self, hashes: &[blake3::Hash], metadatas: &[StreamletMetadata]) -> Result<()> {
|
||||
assert_eq!(hashes.len(), metadatas.len());
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for (i, hash) in blocks.iter().enumerate() {
|
||||
for (i, hash) in hashes.iter().enumerate() {
|
||||
batch.insert(hash.as_bytes(), serialize(&metadatas[i]));
|
||||
}
|
||||
|
||||
@@ -48,21 +51,30 @@ impl StreamletMetadataStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve `StreamletMetadata` by given blockhashes.
|
||||
/// Check if the metadata store contains a given block hash
|
||||
pub fn contains(&self, hash: &blake3::Hash) -> Result<bool> {
|
||||
Ok(self.0.contains_key(hash.as_bytes())?)
|
||||
}
|
||||
|
||||
/// Fetch given blockhashes from the store. The resulting vector contains
|
||||
/// `Option`, which is `Some` if the slot was found in the blockstore, 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 blockhash was not
|
||||
/// found.
|
||||
pub fn get(
|
||||
&self,
|
||||
blockhashes: &[blake3::Hash],
|
||||
hashes: &[blake3::Hash],
|
||||
strict: bool,
|
||||
) -> Result<Vec<Option<StreamletMetadata>>> {
|
||||
let mut ret = Vec::with_capacity(blockhashes.len());
|
||||
let mut ret = Vec::with_capacity(hashes.len());
|
||||
|
||||
for i in blockhashes {
|
||||
if let Some(found) = self.0.get(i.as_bytes())? {
|
||||
for hash in hashes {
|
||||
if let Some(found) = self.0.get(hash.as_bytes())? {
|
||||
let sm = deserialize(&found)?;
|
||||
ret.push(Some(sm));
|
||||
} else {
|
||||
if strict {
|
||||
let s = i.to_hex().as_str().to_string();
|
||||
let s = hash.to_hex().as_str().to_string();
|
||||
return Err(Error::BlockMetadataNotFound(s))
|
||||
}
|
||||
ret.push(None);
|
||||
@@ -72,18 +84,20 @@ impl StreamletMetadataStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Retrieve all `StreamletMetadata`.
|
||||
/// Be careful as this will try to lead everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<Option<(blake3::Hash, StreamletMetadata)>>> {
|
||||
let mut metadata = vec![];
|
||||
/// Retrieve all metadata from the store in the form of a tuple
|
||||
/// (`hash`, `metadata`).
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<(blake3::Hash, StreamletMetadata)>> {
|
||||
let mut ret = vec![];
|
||||
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, v) = r.unwrap();
|
||||
let hash_bytes: [u8; 32] = k.as_ref().try_into().unwrap();
|
||||
let m = deserialize(&v)?;
|
||||
metadata.push(Some((hash_bytes.into(), m)));
|
||||
ret.push((hash_bytes.into(), m));
|
||||
}
|
||||
|
||||
Ok(metadata)
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::io;
|
||||
use log::debug;
|
||||
|
||||
use crate::{
|
||||
consensus::{block::BlockInfo, util::Timestamp, Block, BlockProposal},
|
||||
consensus::{Block, BlockInfo, Timestamp},
|
||||
impl_vec,
|
||||
util::serial::{Decodable, Encodable, ReadExt, VarInt, WriteExt},
|
||||
Result,
|
||||
@@ -24,6 +24,7 @@ pub use rootstore::RootStore;
|
||||
pub mod txstore;
|
||||
pub use txstore::TxStore;
|
||||
|
||||
/// Structure holding all sled trees that comprise the concept of Blockchain.
|
||||
pub struct Blockchain {
|
||||
/// Blocks sled tree
|
||||
pub blocks: BlockStore,
|
||||
@@ -40,19 +41,24 @@ pub struct Blockchain {
|
||||
}
|
||||
|
||||
impl Blockchain {
|
||||
/// Instantiate a new `Blockchain` with the given `sled` database.
|
||||
pub fn new(db: &sled::Db, genesis_ts: Timestamp, genesis_data: blake3::Hash) -> Result<Self> {
|
||||
let blocks = BlockStore::new(db, genesis_ts, genesis_data)?;
|
||||
let order = BlockOrderStore::new(db, genesis_ts, genesis_data)?;
|
||||
let transactions = TxStore::new(db)?;
|
||||
let streamlet_metadata = StreamletMetadataStore::new(db, genesis_ts, genesis_data)?;
|
||||
let transactions = TxStore::new(db)?;
|
||||
let nullifiers = NullifierStore::new(db)?;
|
||||
let merkle_roots = RootStore::new(db)?;
|
||||
|
||||
Ok(Self { blocks, order, transactions, streamlet_metadata, nullifiers, merkle_roots })
|
||||
}
|
||||
|
||||
/// Batch insert [`BlockInfo`]s.
|
||||
pub fn add(&mut self, blocks: &[BlockInfo]) -> Result<Vec<blake3::Hash>> {
|
||||
/// Insert a given slice of [`BlockInfo`] into the blockchain database.
|
||||
/// This functions wraps all the logic of separating the block into specific
|
||||
/// data that can be fed into the different trees of the database.
|
||||
/// Upon success, the functions returns a vector of the block hashes that
|
||||
/// were given and appended to the ledger.
|
||||
pub fn add(&self, blocks: &[BlockInfo]) -> Result<Vec<blake3::Hash>> {
|
||||
let mut ret = Vec::with_capacity(blocks.len());
|
||||
|
||||
for block in blocks {
|
||||
@@ -67,19 +73,35 @@ impl Blockchain {
|
||||
// Store block order
|
||||
self.order.insert(&[block.sl], &[blockhash[0]])?;
|
||||
|
||||
// Store Streamlet metadata
|
||||
// Store streamlet metadata
|
||||
self.streamlet_metadata.insert(&[blockhash[0]], &[block.sm.clone()])?;
|
||||
|
||||
// NOTE: The nullifiers and Merkle roots are applied in the state
|
||||
// transition apply function.
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Retrieve blocks by given hashes. Fails if any of them are not found.
|
||||
pub fn get_blocks_by_hash(&self, blockhashes: &[blake3::Hash]) -> Result<Vec<BlockInfo>> {
|
||||
let mut ret = Vec::with_capacity(blockhashes.len());
|
||||
/// Check if the given [`BlockInfo`] is in the database and all trees.
|
||||
pub fn has_block(&self, block: &BlockInfo) -> Result<bool> {
|
||||
let blockhash = match self.order.get(&[block.sl], true) {
|
||||
Ok(v) => v[0].unwrap(),
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
|
||||
let blocks = self.blocks.get(blockhashes, true)?;
|
||||
let metadata = self.streamlet_metadata.get(blockhashes, true)?;
|
||||
// TODO: Check if we have all transactions
|
||||
|
||||
// Check provided info produces the same hash
|
||||
return Ok(blockhash == block.blockhash())
|
||||
}
|
||||
|
||||
/// Retrieve [`BlockInfo`]s by given hashes. Fails if any of them are not found.
|
||||
pub fn get_blocks_by_hash(&self, hashes: &[blake3::Hash]) -> Result<Vec<BlockInfo>> {
|
||||
let mut ret = Vec::with_capacity(hashes.len());
|
||||
|
||||
let blocks = self.blocks.get(hashes, true)?;
|
||||
let metadata = self.streamlet_metadata.get(hashes, true)?;
|
||||
|
||||
for (i, block) in blocks.iter().enumerate() {
|
||||
let block = block.clone().unwrap();
|
||||
@@ -95,7 +117,7 @@ impl Blockchain {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Retrieve blocks by given slots.
|
||||
/// Retrieve [`BlockInfo`]s by given slots. Does not fail if any of them are not found.
|
||||
pub fn get_blocks_by_slot(&self, slots: &[u64]) -> Result<Vec<BlockInfo>> {
|
||||
debug!("get_blocks_by_slot(): {:?}", slots);
|
||||
let blockhashes = self.order.get(slots, false)?;
|
||||
@@ -108,35 +130,15 @@ impl Blockchain {
|
||||
self.get_blocks_by_hash(&hashes)
|
||||
}
|
||||
|
||||
/// Retrieve n blocks after start slot.
|
||||
/// Retrieve n blocks after given start slot.
|
||||
pub fn get_blocks_after(&self, slot: u64, n: u64) -> Result<Vec<BlockInfo>> {
|
||||
debug!("get_blocks_after(): {:?} - {:?}", slot, n);
|
||||
debug!("get_blocks_after(): {} -> {}", slot, n);
|
||||
let hashes = self.order.get_after(slot, n)?;
|
||||
|
||||
self.get_blocks_by_hash(&hashes)
|
||||
}
|
||||
|
||||
/// Check if the given [`BlockInfo`] is in the database
|
||||
pub fn has_block(&self, info: &BlockInfo) -> Result<bool> {
|
||||
let hashes = match self.order.get(&[info.sl], true) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Ok(false),
|
||||
};
|
||||
|
||||
if let Some(found) = &hashes[0] {
|
||||
// Check provided info produces the same hash
|
||||
// TODO: This BlockProposal::to_proposal_hash function should be in a better place.
|
||||
let blockhash =
|
||||
BlockProposal::to_proposal_hash(info.st, info.sl, &info.txs, &info.metadata);
|
||||
|
||||
return Ok(&blockhash == found)
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Retrieve the last block slot and hash
|
||||
pub fn last(&self) -> Result<Option<(u64, blake3::Hash)>> {
|
||||
/// Retrieve the last block slot and hash.
|
||||
pub fn last(&self) -> Result<(u64, blake3::Hash)> {
|
||||
self.order.get_last()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use sled::Batch;
|
||||
|
||||
use crate::{
|
||||
crypto::nullifier::Nullifier,
|
||||
util::serial::{deserialize, serialize},
|
||||
@@ -8,6 +6,10 @@ use crate::{
|
||||
|
||||
const SLED_NULLIFIER_TREE: &[u8] = b"_nullifiers";
|
||||
|
||||
/// The `NullifierStore` is a `sled` tree storing all the nullifiers seen
|
||||
/// in existing blocks. The key is the nullifier itself, while the value
|
||||
/// is an empty vector that's not used. As a sidenote, perhaps we could
|
||||
/// hold the transaction hash where the nullifier was seen in the value.
|
||||
#[derive(Clone)]
|
||||
pub struct NullifierStore(sled::Tree);
|
||||
|
||||
@@ -18,31 +20,35 @@ impl NullifierStore {
|
||||
Ok(Self(tree))
|
||||
}
|
||||
|
||||
/// Insert a slice of [`Nullifier`] into the nullifier store.
|
||||
pub fn insert(&self, nullifiers: &[Nullifier]) -> Result<()> {
|
||||
let mut batch = Batch::default();
|
||||
for i in nullifiers {
|
||||
batch.insert(serialize(i), vec![] as Vec<u8>);
|
||||
/// Insert a slice of [`Nullifier`] into the store. With sled, the
|
||||
/// operation is done as a batch. The nullifier is used as a key,
|
||||
/// while the value is an empty vector.
|
||||
pub fn insert(&self, nfs: &[Nullifier]) -> Result<()> {
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for nf in nfs {
|
||||
batch.insert(serialize(nf), vec![] as Vec<u8>);
|
||||
}
|
||||
|
||||
self.0.apply_batch(batch)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check whether given nullifier is in the database
|
||||
/// Check if the nullifierstore contains a given nullifier.
|
||||
pub fn contains(&self, nullifier: &Nullifier) -> Result<bool> {
|
||||
Ok(self.0.contains_key(serialize(nullifier))?)
|
||||
}
|
||||
|
||||
/// Retrieve all nullifiers.
|
||||
/// Retrieve all nullifiers from the store.
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<Option<Nullifier>>> {
|
||||
pub fn get_all(&self) -> Result<Vec<Nullifier>> {
|
||||
let mut nfs = vec![];
|
||||
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, _) = r.unwrap();
|
||||
let nullifier = deserialize(&k)?;
|
||||
nfs.push(Some(nullifier))
|
||||
nfs.push(nullifier);
|
||||
}
|
||||
|
||||
Ok(nfs)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use sled::Batch;
|
||||
|
||||
use crate::{
|
||||
crypto::merkle_node::MerkleNode,
|
||||
util::serial::{deserialize, serialize},
|
||||
@@ -8,6 +6,9 @@ use crate::{
|
||||
|
||||
const SLED_ROOTS_TREE: &[u8] = b"_merkleroots";
|
||||
|
||||
/// The `RootStore` is a `sled` tree storing all the Merkle roots seen
|
||||
/// in existing blocks. The key is the Merkle root itself, while the value
|
||||
/// is an empty vector that's not used.
|
||||
#[derive(Clone)]
|
||||
pub struct RootStore(sled::Tree);
|
||||
|
||||
@@ -18,31 +19,35 @@ impl RootStore {
|
||||
Ok(Self(tree))
|
||||
}
|
||||
|
||||
/// Insert a slice of [`MerkleNode`] on the given sled database.
|
||||
/// Insert a slice of [`MerkleNode`] into the store. With sled, the
|
||||
/// operation is done as a batch. The Merkle root is used as a key,
|
||||
/// while the value is an empty vector.
|
||||
pub fn insert(&self, roots: &[MerkleNode]) -> Result<()> {
|
||||
let mut batch = Batch::default();
|
||||
for i in roots {
|
||||
batch.insert(serialize(i), vec![] as Vec<u8>);
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for root in roots {
|
||||
batch.insert(serialize(root), vec![] as Vec<u8>);
|
||||
}
|
||||
|
||||
self.0.apply_batch(batch)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check whether given root is in the database
|
||||
/// Check if the rootstore contains a given Merkle root.
|
||||
pub fn contains(&self, root: &MerkleNode) -> Result<bool> {
|
||||
Ok(self.0.contains_key(serialize(root))?)
|
||||
}
|
||||
|
||||
/// Retrieve all merkle roots.
|
||||
/// Retrieve all Merkle roots from the store.
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<Option<MerkleNode>>> {
|
||||
pub fn get_all(&self) -> Result<Vec<MerkleNode>> {
|
||||
let mut roots = vec![];
|
||||
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, _) = r.unwrap();
|
||||
let root = deserialize(&k)?;
|
||||
roots.push(Some(root));
|
||||
roots.push(root);
|
||||
}
|
||||
|
||||
Ok(roots)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use sled::Batch;
|
||||
|
||||
use crate::{
|
||||
tx::Transaction,
|
||||
util::serial::{deserialize, serialize},
|
||||
@@ -8,6 +6,10 @@ use crate::{
|
||||
|
||||
const SLED_TX_TREE: &[u8] = b"_transactions";
|
||||
|
||||
/// The `TxStore` is a `sled` tree storing all the blockchain's
|
||||
/// transactions where the key is the transaction hash, and the value is
|
||||
/// the serialized transaction.
|
||||
#[derive(Clone)]
|
||||
pub struct TxStore(sled::Tree);
|
||||
|
||||
impl TxStore {
|
||||
@@ -19,13 +21,16 @@ impl TxStore {
|
||||
|
||||
/// Insert a slice of [`Transaction`] into the txstore. With sled, the
|
||||
/// operation is done as a batch.
|
||||
/// The transactions are hashed with BLAKE3 and this hash is
|
||||
/// used as the key, while value is the serialized tx itself.
|
||||
pub fn insert(&self, txs: &[Transaction]) -> Result<Vec<blake3::Hash>> {
|
||||
let mut ret = Vec::with_capacity(txs.len());
|
||||
let mut batch = Batch::default();
|
||||
for i in txs {
|
||||
let serialized = serialize(i);
|
||||
/// The transactions are hashed with BLAKE3 and this hash is used as
|
||||
/// the key, while the value is the serialized [`Transaction`] itself.
|
||||
/// On success, the function returns the transaction hashes in the same
|
||||
/// order as the input transactions.
|
||||
pub fn insert(&self, transactions: &[Transaction]) -> Result<Vec<blake3::Hash>> {
|
||||
let mut ret = Vec::with_capacity(transactions.len());
|
||||
let mut batch = sled::Batch::default();
|
||||
|
||||
for tx in transactions {
|
||||
let serialized = serialize(tx);
|
||||
let txhash = blake3::hash(&serialized);
|
||||
batch.insert(txhash.as_bytes(), serialized);
|
||||
ret.push(txhash);
|
||||
@@ -35,27 +40,26 @@ impl TxStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Check if the txstore contains a given transaction.
|
||||
pub fn contains(&self, txid: blake3::Hash) -> Result<bool> {
|
||||
/// Check if the txstore contains a given transaction hash.
|
||||
pub fn contains(&self, txid: &blake3::Hash) -> Result<bool> {
|
||||
Ok(self.0.contains_key(txid.as_bytes())?)
|
||||
}
|
||||
|
||||
/// Fetch requested transactions from the txstore. The `strict` param
|
||||
/// will make the function fail if a transaction has not been found.
|
||||
pub fn get(
|
||||
&self,
|
||||
tx_hashes: &[blake3::Hash],
|
||||
strict: bool,
|
||||
) -> Result<Vec<Option<Transaction>>> {
|
||||
let mut ret: Vec<Option<Transaction>> = Vec::with_capacity(tx_hashes.len());
|
||||
/// Fetch given tx hashes from the txstore.
|
||||
/// The resulting vector contains `Option`, which is `Some` if the tx
|
||||
/// was found in the txstore, 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 block was not found.
|
||||
pub fn get(&self, txids: &[blake3::Hash], strict: bool) -> Result<Vec<Option<Transaction>>> {
|
||||
let mut ret = Vec::with_capacity(txids.len());
|
||||
|
||||
for i in tx_hashes {
|
||||
if let Some(found) = self.0.get(i.as_bytes())? {
|
||||
for txid in txids {
|
||||
if let Some(found) = self.0.get(txid.as_bytes())? {
|
||||
let tx = deserialize(&found)?;
|
||||
ret.push(Some(tx));
|
||||
} else {
|
||||
if strict {
|
||||
let s = i.to_hex().as_str().to_string();
|
||||
let s = txid.to_hex().as_str().to_string();
|
||||
return Err(Error::TransactionNotFound(s))
|
||||
}
|
||||
ret.push(None);
|
||||
@@ -65,16 +69,18 @@ impl TxStore {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Retrieve all transactions.
|
||||
/// Retrieve all transactions from the txstore in the form of a tuple
|
||||
/// (`tx_hash`, `tx`).
|
||||
/// Be careful as this will try to load everything in memory.
|
||||
pub fn get_all(&self) -> Result<Vec<Option<(blake3::Hash, Transaction)>>> {
|
||||
pub fn get_all(&self) -> Result<Vec<(blake3::Hash, Transaction)>> {
|
||||
let mut txs = vec![];
|
||||
|
||||
let iterator = self.0.into_iter().enumerate();
|
||||
for (_, r) in iterator {
|
||||
let (k, v) = r.unwrap();
|
||||
let hash_bytes: [u8; 32] = k.as_ref().try_into().unwrap();
|
||||
let tx = deserialize(&v)?;
|
||||
txs.push(Some((hash_bytes.into(), tx)));
|
||||
txs.push((hash_bytes.into(), tx));
|
||||
}
|
||||
|
||||
Ok(txs)
|
||||
|
||||
@@ -38,6 +38,11 @@ impl Block {
|
||||
|
||||
Self::new(genesis_data, 0, vec![], metadata)
|
||||
}
|
||||
|
||||
/// Calculate the block hash
|
||||
pub fn blockhash(&self) -> blake3::Hash {
|
||||
blake3::hash(&serialize(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Auxiliary structure used for blockchain syncing.
|
||||
@@ -80,6 +85,19 @@ impl BlockInfo {
|
||||
) -> Self {
|
||||
Self { st, sl, txs, metadata, sm }
|
||||
}
|
||||
|
||||
/// Calculate the block hash
|
||||
pub fn blockhash(&self) -> blake3::Hash {
|
||||
let block: Block = self.clone().into();
|
||||
block.blockhash()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockInfo> for Block {
|
||||
fn from(b: BlockInfo) -> Self {
|
||||
let txids = b.txs.iter().map(|x| blake3::hash(&serialize(x))).collect();
|
||||
Self { st: b.st, sl: b.sl, txs: txids, metadata: b.metadata }
|
||||
}
|
||||
}
|
||||
|
||||
impl net::Message for BlockInfo {
|
||||
|
||||
@@ -64,7 +64,7 @@ impl ProtocolTx {
|
||||
let tx_hash = blake3::hash(&serialize(&tx_copy));
|
||||
|
||||
let tx_in_txstore =
|
||||
match self.state.read().await.blockchain.transactions.contains(tx_hash) {
|
||||
match self.state.read().await.blockchain.transactions.contains(&tx_hash) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("handle_receive_tx(): Failed querying txstore: {}", e);
|
||||
|
||||
@@ -207,7 +207,7 @@ impl ValidatorState {
|
||||
return Ok(epoch)
|
||||
}
|
||||
|
||||
let (last_sl, _) = self.blockchain.last()?.unwrap();
|
||||
let (last_sl, _) = self.blockchain.last()?;
|
||||
Ok(last_sl)
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ impl ValidatorState {
|
||||
|
||||
let hash = match longest_notarized_chain {
|
||||
Some(chain) => chain.proposals.last().unwrap().hash(),
|
||||
None => self.blockchain.last()?.unwrap().1,
|
||||
None => self.blockchain.last()?.1,
|
||||
};
|
||||
|
||||
Ok((hash, index))
|
||||
@@ -476,7 +476,7 @@ impl ValidatorState {
|
||||
None => (),
|
||||
}
|
||||
|
||||
let (last_sl, last_block) = self.blockchain.last()?.unwrap();
|
||||
let (last_sl, last_block) = self.blockchain.last()?;
|
||||
if proposal.block.st != last_block || proposal.block.sl <= last_sl {
|
||||
debug!("find_extended_chain_index(): Proposal doesn't extend any known chain");
|
||||
return Ok(-2)
|
||||
|
||||
@@ -29,7 +29,7 @@ pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Resu
|
||||
// Node sends the last known block hash of the canonical blockchain
|
||||
// and loops until the response is the same block (used to utilize
|
||||
// batch requests).
|
||||
let mut last = state.read().await.blockchain.last()?.unwrap();
|
||||
let mut last = state.read().await.blockchain.last()?;
|
||||
info!("Last known block: {:?} - {:?}", last.0, last.1);
|
||||
|
||||
loop {
|
||||
@@ -63,7 +63,7 @@ pub async fn block_sync_task(p2p: net::P2pPtr, state: ValidatorStatePtr) -> Resu
|
||||
debug!("block_sync_task(): Appending blocks to ledger");
|
||||
state.write().await.blockchain.add(&resp.blocks)?;
|
||||
|
||||
let last_received = state.read().await.blockchain.last()?.unwrap();
|
||||
let last_received = state.read().await.blockchain.last()?;
|
||||
info!("Last received block: {:?} - {:?}", last_received.0, last_received.1);
|
||||
|
||||
if last == last_received {
|
||||
|
||||
Reference in New Issue
Block a user