diff --git a/sequencer_core/src/block_store.rs b/sequencer_core/src/block_store.rs index 3b97653d..53518a70 100644 --- a/sequencer_core/src/block_store.rs +++ b/sequencer_core/src/block_store.rs @@ -68,8 +68,8 @@ impl SequencerStore { None } - pub fn insert(&mut self, tx: &NSSATransaction, block_id: u64) { - self.tx_hash_to_block_map.insert(tx.hash(), block_id); + pub fn latest_block_hash(&self) -> Result { + Ok(self.dbio.latest_block_hash()?) } pub fn genesis_id(&self) -> u64 { @@ -144,4 +144,60 @@ mod tests { let retrieved_tx = node_store.get_transaction_by_hash(tx.hash()); assert_eq!(Some(tx), retrieved_tx); } + + #[test] + fn test_latest_block_hash_returns_genesis_hash_initially() { + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path(); + + let signing_key = sequencer_sign_key_for_testing(); + + let genesis_block_hashable_data = HashableBlockData { + block_id: 0, + prev_block_hash: HashType([0; 32]), + timestamp: 0, + transactions: vec![], + }; + + let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key, [0; 32]); + let genesis_hash = genesis_block.header.hash; + + let node_store = + SequencerStore::open_db_with_genesis(path, Some(&genesis_block), signing_key).unwrap(); + + // Verify that initially the latest block hash equals genesis hash + let latest_hash = node_store.latest_block_hash().unwrap(); + assert_eq!(latest_hash, genesis_hash); + } + + #[test] + fn test_latest_block_hash_updates_after_new_block() { + let temp_dir = tempdir().unwrap(); + let path = temp_dir.path(); + + let signing_key = sequencer_sign_key_for_testing(); + + let genesis_block_hashable_data = HashableBlockData { + block_id: 0, + prev_block_hash: HashType([0; 32]), + timestamp: 0, + transactions: vec![], + }; + + let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key, [0; 32]); + let mut node_store = + SequencerStore::open_db_with_genesis(path, Some(&genesis_block), signing_key).unwrap(); + + // Add a new block + let tx = common::test_utils::produce_dummy_empty_transaction(); + let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]); + let block_hash = block.header.hash; + + let dummy_state = V02State::new_with_genesis_accounts(&[], &[]); + node_store.update(&block, &dummy_state).unwrap(); + + // Verify that the latest block hash now equals the new block's hash + let latest_hash = node_store.latest_block_hash().unwrap(); + assert_eq!(latest_hash, block_hash); + } } diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index c662c167..8d719d65 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, path::Path, time::Instant}; -use anyhow::{Result, anyhow}; +use anyhow::{Context as _, Result, anyhow}; #[cfg(feature = "testnet")] use common::PINATA_BASE58; use common::{ @@ -214,7 +214,10 @@ impl SequencerCore SequencerCore DbResult<()> { + let cf_meta = self.meta_column(); + batch.put_cf( + &cf_meta, + borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_string()), + ) + })?, + borsh::to_vec(&block_id).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize last block id".to_string()), + ) + })?, + ); + Ok(()) + } + pub fn put_meta_last_finalized_block_id(&self, block_id: Option) -> DbResult<()> { let cf_meta = self.meta_column(); self.db @@ -301,6 +328,81 @@ impl RocksDBIO { Ok(()) } + fn put_meta_latest_block_hash(&self, block_hash: common::HashType) -> DbResult<()> { + let cf_meta = self.meta_column(); + self.db + .put_cf( + &cf_meta, + borsh::to_vec(&DB_META_LATEST_BLOCK_HASH_KEY).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize DB_META_LATEST_BLOCK_HASH_KEY".to_string()), + ) + })?, + borsh::to_vec(&block_hash).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize latest block hash".to_string()), + ) + })?, + ) + .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; + Ok(()) + } + + fn put_meta_latest_block_hash_batch( + &self, + block_hash: common::HashType, + batch: &mut WriteBatch, + ) -> DbResult<()> { + let cf_meta = self.meta_column(); + batch.put_cf( + &cf_meta, + borsh::to_vec(&DB_META_LATEST_BLOCK_HASH_KEY).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize DB_META_LATEST_BLOCK_HASH_KEY".to_string()), + ) + })?, + borsh::to_vec(&block_hash).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize latest block hash".to_string()), + ) + })?, + ); + Ok(()) + } + + pub fn latest_block_hash(&self) -> DbResult { + let cf_meta = self.meta_column(); + let res = self + .db + .get_cf( + &cf_meta, + borsh::to_vec(&DB_META_LATEST_BLOCK_HASH_KEY).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to serialize DB_META_LATEST_BLOCK_HASH_KEY".to_string()), + ) + })?, + ) + .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; + + if let Some(data) = res { + Ok(borsh::from_slice::(&data).map_err(|err| { + DbError::borsh_cast_message( + err, + Some("Failed to deserialize latest block hash".to_string()), + ) + })?) + } else { + Err(DbError::db_interaction_error( + "Latest block hash not found".to_string(), + )) + } + } + pub fn put_block(&self, block: &Block, first: bool, batch: &mut WriteBatch) -> DbResult<()> { let cf_block = self.block_column(); @@ -308,7 +410,8 @@ impl RocksDBIO { let last_curr_block = self.get_meta_last_block_in_db()?; if block.header.block_id > last_curr_block { - self.put_meta_last_block_in_db(block.header.block_id)?; + self.put_meta_last_block_in_db_batch(block.header.block_id, batch)?; + self.put_meta_latest_block_hash_batch(block.header.hash, batch)?; } }