From 8a16f7ba83fd200618814b2eaf66c88c5b1dfb79 Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Fri, 16 Feb 2024 11:33:11 +0200 Subject: [PATCH] feat: check chain metadata (#6146) Description --- Checks chain metadata for correctness Motivation and Context --- The base node makes certain logic desisions based on what the chain meta data is, but no where do we make checks to ensure that the data is valid inside of this. How Has This Been Tested? --- unit tests --- base_layer/common_types/src/chain_metadata.rs | 20 ++++++++++++++++--- .../src/base_node/proto/chain_metadata.rs | 5 +++-- .../state_machine_service/states/listening.rs | 5 +++-- .../core/src/base_node/sync/sync_peer.rs | 3 +-- base_layer/core/src/chain_storage/error.rs | 5 ++++- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 2 +- .../core/tests/tests/node_state_machine.rs | 2 +- .../tests/support/base_node_service_mock.rs | 4 ++-- .../transaction_service_tests/service.rs | 2 +- .../wallet_ffi/src/callback_handler_tests.rs | 3 ++- 10 files changed, 35 insertions(+), 16 deletions(-) diff --git a/base_layer/common_types/src/chain_metadata.rs b/base_layer/common_types/src/chain_metadata.rs index 0ab3d7285b..c9aa217d02 100644 --- a/base_layer/common_types/src/chain_metadata.rs +++ b/base_layer/common_types/src/chain_metadata.rs @@ -47,6 +47,13 @@ pub struct ChainMetadata { /// Timestamp of the tip block in the longest valid chain timestamp: u64, } +#[derive(Debug, thiserror::Error)] +pub enum ChainMetaDataError { + #[error("Pruning Height is higher than the Best Block height")] + PruningHeightAboveBestBlock, + #[error("The total accumulated difficulty is zero")] + AccumulatedDifficultyZero, +} impl ChainMetadata { pub fn new( @@ -56,15 +63,22 @@ impl ChainMetadata { pruned_height: u64, accumulated_difficulty: U256, timestamp: u64, - ) -> ChainMetadata { - ChainMetadata { + ) -> Result { + let chain_meta_data = ChainMetadata { best_block_height, best_block_hash, pruning_horizon, pruned_height, accumulated_difficulty, timestamp, - } + }; + if chain_meta_data.accumulated_difficulty == 0.into() { + return Err(ChainMetaDataError::AccumulatedDifficultyZero); + }; + if chain_meta_data.pruned_height > chain_meta_data.best_block_height { + return Err(ChainMetaDataError::PruningHeightAboveBestBlock); + }; + Ok(chain_meta_data) } /// The block height at the pruning horizon, given the chain height of the network. Typically database backends diff --git a/base_layer/core/src/base_node/proto/chain_metadata.rs b/base_layer/core/src/base_node/proto/chain_metadata.rs index 333f80aceb..2a876fbe63 100644 --- a/base_layer/core/src/base_node/proto/chain_metadata.rs +++ b/base_layer/core/src/base_node/proto/chain_metadata.rs @@ -56,14 +56,15 @@ impl TryFrom for ChainMetadata { .best_block_hash .try_into() .map_err(|e| format!("Malformed best block: {}", e))?; - Ok(ChainMetadata::new( + ChainMetadata::new( best_block_height, hash, pruning_horizon, metadata.pruned_height, accumulated_difficulty, metadata.timestamp, - )) + ) + .map_err(|e| e.to_string()) } } diff --git a/base_layer/core/src/base_node/state_machine_service/states/listening.rs b/base_layer/core/src/base_node/state_machine_service/states/listening.rs index 20d307fc08..9e097d8d9b 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/listening.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/listening.rs @@ -456,7 +456,7 @@ mod test { let archival_node = PeerChainMetadata::new( random_node_id(), - ChainMetadata::new(NETWORK_TIP_HEIGHT, block_hash, 0, 0, accumulated_difficulty, 0), + ChainMetadata::new(NETWORK_TIP_HEIGHT, block_hash, 0, 0, accumulated_difficulty, 0).unwrap(), None, ); @@ -469,7 +469,8 @@ mod test { 0, accumulated_difficulty - U256::from(1000), 0, - ), + ) + .unwrap(), None, ); diff --git a/base_layer/core/src/base_node/sync/sync_peer.rs b/base_layer/core/src/base_node/sync/sync_peer.rs index 52877c627c..4594f78aa5 100644 --- a/base_layer/core/src/base_node/sync/sync_peer.rs +++ b/base_layer/core/src/base_node/sync/sync_peer.rs @@ -135,7 +135,6 @@ mod test { use super::*; mod sort_by_latency { - use primitive_types::U256; use tari_common_types::types::FixedHash; use tari_comms::types::{CommsPublicKey, CommsSecretKey}; use tari_crypto::keys::{PublicKey, SecretKey}; @@ -151,7 +150,7 @@ mod test { let latency_option = latency.map(|latency| Duration::from_millis(latency as u64)); PeerChainMetadata::new( node_id, - ChainMetadata::new(0, FixedHash::zero(), 0, 0, U256::zero(), 0), + ChainMetadata::new(0, FixedHash::zero(), 0, 0, 1.into(), 0).unwrap(), latency_option, ) .into() diff --git a/base_layer/core/src/chain_storage/error.rs b/base_layer/core/src/chain_storage/error.rs index 47f5217697..f9551e336c 100644 --- a/base_layer/core/src/chain_storage/error.rs +++ b/base_layer/core/src/chain_storage/error.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use lmdb_zero::error; -use tari_common_types::types::FixedHashSizeError; +use tari_common_types::{chain_metadata::ChainMetaDataError, types::FixedHashSizeError}; use tari_mmr::{error::MerkleMountainRangeError, sparse_merkle_tree::SMTError, MerkleProofError}; use tari_storage::lmdb_store::LMDBError; use thiserror::Error; @@ -139,6 +139,8 @@ pub enum ChainStorageError { FromKeyBytesFailed(String), #[error("Sparse Merkle Tree error: {0}")] SMTError(#[from] SMTError), + #[error("Invalid ChainMetaData: {0}")] + InvalidChainMetaData(#[from] ChainMetaDataError), } impl ChainStorageError { @@ -190,6 +192,7 @@ impl ChainStorageError { _err @ ChainStorageError::FixedHashSizeError(_) | _err @ ChainStorageError::CompositeKeyLengthExceeded | _err @ ChainStorageError::FromKeyBytesFailed(_) | + _err @ ChainStorageError::InvalidChainMetaData(_) | _err @ ChainStorageError::OutOfRange => None, } } diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 35c8a3a532..2b4faffe2a 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -2449,7 +2449,7 @@ fn fetch_metadata(txn: &ConstTransaction<'_>, db: &Database) -> Result::digest(node_identity.node_id().as_bytes()).into(); - let metadata = ChainMetadata::new(10, block_hash, 2800, 0, 5000.into(), 0); + let metadata = ChainMetadata::new(10, block_hash, 2800, 0, 5000.into(), 0).unwrap(); node.comms .peer_manager() diff --git a/base_layer/wallet/tests/support/base_node_service_mock.rs b/base_layer/wallet/tests/support/base_node_service_mock.rs index 46f755d12d..ebfb37f9ef 100644 --- a/base_layer/wallet/tests/support/base_node_service_mock.rs +++ b/base_layer/wallet/tests/support/base_node_service_mock.rs @@ -79,7 +79,7 @@ impl MockBaseNodeService { pub fn set_base_node_state(&mut self, height: Option) { let (chain_metadata, is_synced) = match height { Some(height) => { - let metadata = ChainMetadata::new(height, FixedHash::zero(), 0, 0, 0.into(), 0); + let metadata = ChainMetadata::new(height, FixedHash::zero(), 0, 0, 1.into(), 0).unwrap(); (Some(metadata), Some(true)) }, None => (None, None), @@ -95,7 +95,7 @@ impl MockBaseNodeService { } pub fn set_default_base_node_state(&mut self) { - let metadata = ChainMetadata::new(i64::MAX as u64, FixedHash::zero(), 0, 0, 0.into(), 0); + let metadata = ChainMetadata::new(i64::MAX as u64, FixedHash::zero(), 0, 0, 1.into(), 0).unwrap(); self.state = BaseNodeState { node_id: None, chain_metadata: Some(metadata), diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index f12b8d67be..efba77a934 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -195,7 +195,7 @@ async fn setup_transaction_service>( let passphrase = SafePassword::from("My lovely secret passphrase"); let db = WalletDatabase::new(WalletSqliteDatabase::new(db_connection.clone(), passphrase).unwrap()); - let metadata = ChainMetadata::new(std::i64::MAX as u64, FixedHash::zero(), 0, 0, 0.into(), 0); + let metadata = ChainMetadata::new(std::i64::MAX as u64, FixedHash::zero(), 0, 0, 1.into(), 0).unwrap(); db.set_chain_metadata(metadata).unwrap(); diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 184fe38b08..377f1ecf75 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -507,7 +507,8 @@ mod test { 0, 123.into(), ts_now.timestamp_millis() as u64, - ); + ) + .unwrap(); base_node_event_sender .send(Arc::new(BaseNodeEvent::BaseNodeStateChanged(BaseNodeState {