lssa/sequencer_core/src/block_store.rs

135 lines
4.2 KiB
Rust
Raw Normal View History

2025-07-22 10:23:52 -03:00
use std::{collections::HashMap, path::Path};
2024-11-25 07:26:16 +02:00
use anyhow::Result;
2025-10-17 16:04:09 -03:00
use common::{HashType, block::Block, transaction::EncodedTransaction};
2025-04-16 16:17:53 +03:00
use storage::RocksDBIO;
2024-11-25 07:26:16 +02:00
2025-11-18 19:31:03 +03:00
pub struct SequencerBlockStore {
2024-11-25 07:26:16 +02:00
dbio: RocksDBIO,
2025-07-24 08:21:20 -03:00
// TODO: Consider adding the hashmap to the database for faster recovery.
2025-11-18 19:31:03 +03:00
tx_hash_to_block_map: HashMap<HashType, u64>,
genesis_id: u64,
signing_key: nssa::PrivateKey,
2024-11-25 07:26:16 +02:00
}
2025-11-18 19:31:03 +03:00
impl SequencerBlockStore {
2025-11-26 00:27:20 +03:00
/// Starting database at the start of new chain.
2024-11-25 07:26:16 +02:00
/// Creates files if necessary.
///
/// ATTENTION: Will overwrite genesis block.
2025-09-03 10:29:51 +03:00
pub fn open_db_with_genesis(
location: &Path,
genesis_block: Option<Block>,
signing_key: nssa::PrivateKey,
) -> Result<Self> {
2025-07-22 10:23:52 -03:00
let tx_hash_to_block_map = if let Some(block) = &genesis_block {
block_to_transactions_map(block)
} else {
HashMap::new()
};
2025-10-25 00:30:04 -03:00
let dbio = RocksDBIO::open_or_create(location, genesis_block)?;
2024-12-05 13:05:58 +02:00
let genesis_id = dbio.get_meta_first_block_in_db()?;
2025-07-22 10:23:52 -03:00
Ok(Self {
dbio,
genesis_id,
tx_hash_to_block_map,
2025-09-03 10:29:51 +03:00
signing_key,
2025-07-22 10:23:52 -03:00
})
2024-11-25 07:26:16 +02:00
}
2025-11-26 00:27:20 +03:00
/// Reopening existing database
2025-09-03 10:29:51 +03:00
pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result<Self> {
2025-11-18 19:31:03 +03:00
SequencerBlockStore::open_db_with_genesis(location, None, signing_key)
2024-11-25 07:26:16 +02:00
}
pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
2025-09-03 10:29:51 +03:00
Ok(self.dbio.get_block(id)?.into_block(&self.signing_key))
2024-11-25 07:26:16 +02:00
}
2025-07-22 10:23:52 -03:00
pub fn put_block_at_id(&mut self, block: Block) -> Result<()> {
let new_transactions_map = block_to_transactions_map(&block);
self.dbio.put_block(block, false)?;
self.tx_hash_to_block_map.extend(new_transactions_map);
Ok(())
}
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
2025-10-17 16:04:09 -03:00
pub fn get_transaction_by_hash(&self, hash: HashType) -> Option<EncodedTransaction> {
2025-07-22 10:23:52 -03:00
let block_id = self.tx_hash_to_block_map.get(&hash);
let block = block_id.map(|&id| self.get_block_at_id(id));
if let Some(Ok(block)) = block {
2025-09-03 10:29:51 +03:00
for transaction in block.body.transactions.into_iter() {
2025-08-07 15:19:06 -03:00
if transaction.hash() == hash {
2025-07-22 10:23:52 -03:00
return Some(transaction);
}
}
}
None
2024-11-25 07:26:16 +02:00
}
2025-11-18 19:31:03 +03:00
pub fn insert(&mut self, tx: &EncodedTransaction, block_id: u64) {
self.tx_hash_to_block_map.insert(tx.hash(), block_id);
}
pub fn genesis_id(&self) -> u64 {
self.genesis_id
}
pub fn signing_key(&self) -> &nssa::PrivateKey {
&self.signing_key
}
2024-11-25 07:26:16 +02:00
}
2025-07-22 10:23:52 -03:00
2025-10-25 00:30:04 -03:00
pub(crate) fn block_to_transactions_map(block: &Block) -> HashMap<HashType, u64> {
2025-07-22 10:23:52 -03:00
block
2025-09-03 10:29:51 +03:00
.body
2025-07-22 10:23:52 -03:00
.transactions
.iter()
2025-09-03 10:29:51 +03:00
.map(|transaction| (transaction.hash(), block.header.block_id))
2025-07-22 10:23:52 -03:00
.collect()
}
2025-07-22 12:27:42 -03:00
#[cfg(test)]
mod tests {
2025-09-03 10:29:51 +03:00
use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing};
2025-07-22 12:27:42 -03:00
use tempfile::tempdir;
2025-08-10 00:53:53 -03:00
2025-11-26 00:27:20 +03:00
use super::*;
2025-07-22 12:27:42 -03:00
#[test]
fn test_get_transaction_by_hash() {
2025-07-22 12:27:42 -03:00
let temp_dir = tempdir().unwrap();
let path = temp_dir.path();
2025-08-28 12:00:04 +03:00
2025-09-03 10:29:51 +03:00
let signing_key = sequencer_sign_key_for_testing();
let genesis_block_hashable_data = HashableBlockData {
block_id: 0,
prev_block_hash: [0; 32],
timestamp: 0,
transactions: vec![],
};
2025-09-03 10:29:51 +03:00
let genesis_block = genesis_block_hashable_data.into_block(&signing_key);
// Start an empty node store
let mut node_store =
2025-11-18 19:31:03 +03:00
SequencerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key)
2025-09-03 10:29:51 +03:00
.unwrap();
2025-08-05 14:59:20 +03:00
let tx = common::test_utils::produce_dummy_empty_transaction();
2025-08-12 12:18:13 -03:00
let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]);
2025-08-05 14:59:20 +03:00
// Try retrieve a tx that's not in the chain yet.
2025-08-07 15:19:06 -03:00
let retrieved_tx = node_store.get_transaction_by_hash(tx.hash());
2025-07-22 12:27:42 -03:00
assert_eq!(None, retrieved_tx);
// Add the block with the transaction
node_store.put_block_at_id(block).unwrap();
// Try again
2025-08-07 15:19:06 -03:00
let retrieved_tx = node_store.get_transaction_by_hash(tx.hash());
assert_eq!(Some(tx), retrieved_tx);
2025-07-22 12:27:42 -03:00
}
}