From 232bdf7cafba93012f3bb8265a27acf0090e7dd2 Mon Sep 17 00:00:00 2001 From: Damir Vodenicarevic Date: Tue, 11 Jul 2023 17:33:57 +0200 Subject: [PATCH] incremental hash (#4197) * incremental hash * rebase * reformat * read/write to final state * factor prng * imrovements * remove comment * final_state_db corrrection * load initial trail hash * clippy * fix tests * debug tests * Fix bootstrap test * Fix execution tests * remove comment --------- Co-authored-by: Leo-Besancon --- massa-async-pool/src/message.rs | 4 +- massa-bootstrap/src/client.rs | 1 + massa-bootstrap/src/tests/scenarios.rs | 5 +- massa-bootstrap/src/tests/tools.rs | 18 ++- massa-db-exports/src/constants.rs | 3 +- massa-execution-worker/src/active_history.rs | 11 ++ massa-execution-worker/src/context.rs | 108 ++++++++++++------ massa-execution-worker/src/execution.rs | 14 ++- massa-execution-worker/src/interface_impl.rs | 1 + massa-execution-worker/src/tests/mock.rs | 1 + .../src/tests/tests_active_history.rs | 1 + massa-final-state/src/final_state.rs | 101 +++++++++++++--- massa-final-state/src/state_changes.rs | 40 +++++-- massa-hash/src/hash.rs | 16 +++ 14 files changed, 251 insertions(+), 73 deletions(-) diff --git a/massa-async-pool/src/message.rs b/massa-async-pool/src/message.rs index 393dd8c1b0c..b2dbfca4274 100644 --- a/massa-async-pool/src/message.rs +++ b/massa-async-pool/src/message.rs @@ -321,7 +321,7 @@ impl Default for AsyncMessage { destination: genesis_address, validity_start: slot_zero, validity_end: slot_zero, - hash: Hash::from_bytes(&[0; 32]), + hash: Hash::zero(), ..Default::default() } } @@ -362,7 +362,7 @@ impl AsyncMessage { can_be_executed: can_be_executed.unwrap_or(trigger.is_none()), trigger, // placeholder hash to serialize the message, replaced below - hash: Hash::from_bytes(&[0; 32]), + hash: Hash::zero(), }; async_message_ser .serialize(&message, &mut buffer) diff --git a/massa-bootstrap/src/client.rs b/massa-bootstrap/src/client.rs index da06eb429f1..0279f09c321 100644 --- a/massa-bootstrap/src/client.rs +++ b/massa-bootstrap/src/client.rs @@ -429,6 +429,7 @@ pub fn get_state( err )) })?; + final_state_guard.init_execution_trail_hash(); } // create the initial cycle of PoS cycle_history diff --git a/massa-bootstrap/src/tests/scenarios.rs b/massa-bootstrap/src/tests/scenarios.rs index bf8430da292..1da9f9b86f2 100644 --- a/massa-bootstrap/src/tests/scenarios.rs +++ b/massa-bootstrap/src/tests/scenarios.rs @@ -6,7 +6,8 @@ use super::tools::{ use crate::listener::PollEvent; use crate::tests::tools::{ assert_eq_bootstrap_graph, get_random_async_pool_changes, get_random_executed_de_changes, - get_random_executed_ops_changes, get_random_pos_changes, + get_random_executed_ops_changes, get_random_execution_trail_hash_change, + get_random_pos_changes, }; use crate::BootstrapError; use crate::{ @@ -307,6 +308,7 @@ fn test_bootstrap_server() { async_pool_changes: get_random_async_pool_changes(10, thread_count), executed_ops_changes: get_random_executed_ops_changes(10), executed_denunciations_changes: get_random_executed_de_changes(10), + execution_trail_hash_change: get_random_execution_trail_hash_change(true), }; let next = current_slot.get_next_slot(thread_count).unwrap(); @@ -452,6 +454,7 @@ fn test_bootstrap_server() { async_pool_changes: get_random_async_pool_changes(10, thread_count), executed_ops_changes: get_random_executed_ops_changes(10), executed_denunciations_changes: get_random_executed_de_changes(10), + execution_trail_hash_change: get_random_execution_trail_hash_change(true), }; let mut batch = DBBatch::new(); diff --git a/massa-bootstrap/src/tests/tools.rs b/massa-bootstrap/src/tests/tools.rs index 79fec33a010..7eba6fcbab7 100644 --- a/massa-bootstrap/src/tests/tools.rs +++ b/massa-bootstrap/src/tests/tools.rs @@ -18,7 +18,7 @@ use massa_executed_ops::{ use massa_final_state::test_exports::create_final_state; use massa_final_state::{FinalState, FinalStateConfig}; use massa_hash::Hash; -use massa_ledger_exports::{LedgerChanges, LedgerEntry, SetUpdateOrDelete}; +use massa_ledger_exports::{LedgerChanges, LedgerEntry, SetOrKeep, SetUpdateOrDelete}; use massa_ledger_worker::test_exports::create_final_ledger; use massa_models::block::BlockDeserializerArgs; use massa_models::bytecode::Bytecode; @@ -287,6 +287,15 @@ pub fn get_random_executed_de_changes(r_limit: u64) -> ExecutedDenunciationsChan de_changes } +/// generates a random execution trail hash change +pub fn get_random_execution_trail_hash_change(always_set: bool) -> SetOrKeep { + if always_set || rand::thread_rng().gen() { + SetOrKeep::Set(Hash::compute_from(&get_some_random_bytes())) + } else { + SetOrKeep::Keep + } +} + /// generates a random bootstrap state for the final state pub fn get_random_final_state_bootstrap( pos: PoSFinalState, @@ -344,7 +353,7 @@ pub fn get_random_final_state_bootstrap( )) .unwrap(); - create_final_state( + let mut final_state = create_final_state( config, Box::new(final_ledger), async_pool, @@ -353,7 +362,10 @@ pub fn get_random_final_state_bootstrap( executed_denunciations, mip_store, db, - ) + ); + + final_state.init_execution_trail_hash(); + final_state } pub fn get_dummy_block_id(s: &str) -> BlockId { diff --git a/massa-db-exports/src/constants.rs b/massa-db-exports/src/constants.rs index 05a1d634b46..e57c2e902d6 100644 --- a/massa-db-exports/src/constants.rs +++ b/massa-db-exports/src/constants.rs @@ -1,6 +1,4 @@ // Commons -pub const LSMTREE_NODES_CF: &str = "lsmtree_nodes"; -pub const LSMTREE_VALUES_CF: &str = "lsmtree_values"; pub const METADATA_CF: &str = "metadata"; pub const STATE_CF: &str = "state"; pub const VERSIONING_CF: &str = "versioning"; @@ -31,6 +29,7 @@ pub const EXECUTED_DENUNCIATIONS_PREFIX: &str = "executed_denunciations/"; pub const LEDGER_PREFIX: &str = "ledger/"; pub const MIP_STORE_PREFIX: &str = "versioning/"; pub const MIP_STORE_STATS_PREFIX: &str = "versioning_stats/"; +pub const EXECUTION_TRAIL_HASH_PREFIX: &str = "execution_trail_hash/"; // Async Pool pub const MESSAGE_DESER_ERROR: &str = "critical: message deserialization failed"; diff --git a/massa-execution-worker/src/active_history.rs b/massa-execution-worker/src/active_history.rs index 945687c33fa..e24a40c92ba 100644 --- a/massa-execution-worker/src/active_history.rs +++ b/massa-execution-worker/src/active_history.rs @@ -231,6 +231,17 @@ impl ActiveHistory { None } + /// Gets the execution trail hash + pub fn get_execution_trail_hash(&self) -> HistorySearchResult { + for history_element in self.0.iter().rev() { + if let SetOrKeep::Set(hash) = history_element.state_changes.execution_trail_hash_change + { + return HistorySearchResult::Present(hash); + } + } + HistorySearchResult::NoInfo + } + /// Gets the index of a slot in history pub fn get_slot_index(&self, slot: &Slot, thread_count: u8) -> SlotIndexPosition { let first_slot = match self.0.front() { diff --git a/massa-execution-worker/src/context.rs b/massa-execution-worker/src/context.rs index cf403d1c9b2..802109b0aa7 100644 --- a/massa-execution-worker/src/context.rs +++ b/massa-execution-worker/src/context.rs @@ -7,6 +7,7 @@ //! More generally, the context acts only on its own state //! and does not write anything persistent to the consensus state. +use crate::active_history::HistorySearchResult; use crate::speculative_async_pool::SpeculativeAsyncPool; use crate::speculative_executed_denunciations::SpeculativeExecutedDenunciations; use crate::speculative_executed_ops::SpeculativeExecutedOps; @@ -21,7 +22,7 @@ use massa_execution_exports::{ }; use massa_final_state::{FinalState, StateChanges}; use massa_hash::Hash; -use massa_ledger_exports::LedgerChanges; +use massa_ledger_exports::{LedgerChanges, SetOrKeep}; use massa_models::address::ExecutionAddressCycleInfo; use massa_models::bytecode::Bytecode; use massa_models::denunciation::DenunciationIndex; @@ -155,13 +156,16 @@ pub struct ExecutionContext { /// operation id that originally caused this execution (if any) pub origin_operation_id: Option, - // cache of compiled runtime modules + /// Execution trail hash + pub execution_trail_hash: Hash, + + /// cache of compiled runtime modules pub module_cache: Arc>, - // Vesting Manager + /// Vesting Manager pub vesting_manager: Arc, - // Address factory + /// Address factory pub address_factory: AddressFactory, } @@ -183,6 +187,7 @@ impl ExecutionContext { module_cache: Arc>, vesting_manager: Arc, mip_store: MipStore, + execution_trail_hash: massa_hash::Hash, ) -> Self { ExecutionContext { speculative_ledger: SpeculativeLedger::new( @@ -219,13 +224,14 @@ impl ExecutionContext { stack: Default::default(), read_only: Default::default(), events: Default::default(), - unsafe_rng: Xoshiro256PlusPlus::from_seed([0u8; 32]), + unsafe_rng: init_prng(&execution_trail_hash), creator_address: Default::default(), origin_operation_id: Default::default(), module_cache, config, vesting_manager, address_factory: AddressFactory { mip_store }, + execution_trail_hash, } } @@ -306,20 +312,14 @@ impl ExecutionContext { vesting_manager: Arc, mip_store: MipStore, ) -> Self { - // Deterministically seed the unsafe RNG to allow the bytecode to use it. - // Note that consecutive read-only calls for the same slot will get the same random seed. - - // Add the current slot to the seed to ensure different draws at every slot - let mut seed: Vec = slot.to_bytes_key().to_vec(); - // Add a marker to the seed indicating that we are in read-only mode - // to prevent random draw collisions with active executions - seed.push(0u8); // 0u8 = read-only - let seed = massa_hash::Hash::compute_from(&seed).into_bytes(); - // We use Xoshiro256PlusPlus because it is very fast, - // has a period long enough to ensure no repetitions will ever happen, - // of decent quality (given the unsafe constraints) - // but not cryptographically secure (and that's ok because the internal state is exposed anyways) - let unsafe_rng = Xoshiro256PlusPlus::from_seed(seed); + // Get the execution hash trail + let prev_execution_trail_hash = active_history.read().get_execution_trail_hash(); + let prev_execution_trail_hash = match prev_execution_trail_hash { + HistorySearchResult::Present(h) => h, + _ => final_state.read().get_execution_trail_hash(), + }; + let execution_trail_hash = + generate_execution_trail_hash(&prev_execution_trail_hash, &slot, None, true); // return readonly context ExecutionContext { @@ -327,7 +327,6 @@ impl ExecutionContext { slot, stack: call_stack, read_only: true, - unsafe_rng, ..ExecutionContext::new( config, final_state, @@ -335,6 +334,7 @@ impl ExecutionContext { module_cache, vesting_manager, mip_store, + execution_trail_hash, ) } } @@ -380,26 +380,23 @@ impl ExecutionContext { vesting_manager: Arc, mip_store: MipStore, ) -> Self { - // Deterministically seed the unsafe RNG to allow the bytecode to use it. - - // Add the current slot to the seed to ensure different draws at every slot - let mut seed: Vec = slot.to_bytes_key().to_vec(); - // Add a marker to the seed indicating that we are in active mode - // to prevent random draw collisions with read-only executions - seed.push(1u8); // 1u8 = active - - // For more deterministic entropy, seed with the block ID if any - if let Some(block_id) = &opt_block_id { - seed.extend(block_id.to_bytes()); // append block ID - } - let seed = massa_hash::Hash::compute_from(&seed).into_bytes(); - let unsafe_rng = Xoshiro256PlusPlus::from_seed(seed); + // Get the execution hash trail + let prev_execution_trail_hash = active_history.read().get_execution_trail_hash(); + let prev_execution_trail_hash = match prev_execution_trail_hash { + HistorySearchResult::Present(h) => h, + _ => final_state.read().get_execution_trail_hash(), + }; + let execution_trail_hash = generate_execution_trail_hash( + &prev_execution_trail_hash, + &slot, + opt_block_id.as_ref(), + false, + ); // return active slot execution context ExecutionContext { slot, opt_block_id, - unsafe_rng, ..ExecutionContext::new( config, final_state, @@ -407,6 +404,7 @@ impl ExecutionContext { module_cache, vesting_manager, mip_store, + execution_trail_hash, ) } } @@ -936,6 +934,7 @@ impl ExecutionContext { pos_changes: self.speculative_roll_state.take(), executed_ops_changes: self.speculative_executed_ops.take(), executed_denunciations_changes: self.speculative_executed_denunciations.take(), + execution_trail_hash_change: SetOrKeep::Set(self.execution_trail_hash), }; std::mem::take(&mut self.opt_block_id); @@ -1074,3 +1073,42 @@ impl ExecutionContext { .get_address_deferred_credits(address, min_slot) } } + +/// Generate the execution trail hash +fn generate_execution_trail_hash( + previous_execution_trail_hash: &massa_hash::Hash, + slot: &Slot, + opt_block_id: Option<&BlockId>, + read_only: bool, +) -> massa_hash::Hash { + match opt_block_id { + None => massa_hash::Hash::compute_from_tuple(&[ + previous_execution_trail_hash.to_bytes(), + &slot.to_bytes_key(), + &[if read_only { 1u8 } else { 0u8 }, 0u8], + ]), + Some(block_id) => massa_hash::Hash::compute_from_tuple(&[ + previous_execution_trail_hash.to_bytes(), + &slot.to_bytes_key(), + &[if read_only { 1u8 } else { 0u8 }, 1u8], + block_id.to_bytes(), + ]), + } +} + +/// Initializes and seeds the PRNG with the given execution trail hash. +fn init_prng(execution_trail_hash: &massa_hash::Hash) -> Xoshiro256PlusPlus { + // Deterministically seed the unsafe RNG to allow the bytecode to use it. + // Note that consecutive read-only calls for the same slot will get the same random seed. + let seed = massa_hash::Hash::compute_from_tuple(&[ + "PRNG_SEED".as_bytes(), + execution_trail_hash.to_bytes(), + ]) + .into_bytes(); + + // We use Xoshiro256PlusPlus because it is very fast, + // has a period long enough to ensure no repetitions will ever happen, + // of decent quality (given the unsafe constraints) + // but not cryptographically secure (and that's ok because the internal state is exposed anyways) + Xoshiro256PlusPlus::from_seed(seed) +} diff --git a/massa-execution-worker/src/execution.rs b/massa-execution-worker/src/execution.rs index 55693205a6f..cf7392424c0 100644 --- a/massa-execution-worker/src/execution.rs +++ b/massa-execution-worker/src/execution.rs @@ -118,12 +118,13 @@ impl ExecutionState { ) -> ExecutionState { // Get the slot at the output of which the final state is attached. // This should be among the latest final slots. - let last_final_slot = final_state - .read() - .db - .read() - .get_change_id() - .expect("Critical error: Final state has no slot attached"); + let last_final_slot; + let execution_trail_hash; + { + let final_state_read = final_state.read(); + last_final_slot = final_state_read.get_slot(); + execution_trail_hash = final_state_read.get_execution_trail_hash(); + } // Create default active history let active_history: Arc> = Default::default(); @@ -160,6 +161,7 @@ impl ExecutionState { module_cache.clone(), vesting_manager.clone(), mip_store.clone(), + execution_trail_hash, ))); // Instantiate the interface providing ABI access to the VM, share the execution context with it diff --git a/massa-execution-worker/src/interface_impl.rs b/massa-execution-worker/src/interface_impl.rs index b53f4a00fba..cecaf174ed3 100644 --- a/massa-execution-worker/src/interface_impl.rs +++ b/massa-execution-worker/src/interface_impl.rs @@ -119,6 +119,7 @@ impl InterfaceImpl { module_cache, vesting_manager, mip_store, + massa_hash::Hash::zero(), ); execution_context.stack = vec![ExecutionStackElement { address: sender_addr, diff --git a/massa-execution-worker/src/tests/mock.rs b/massa-execution-worker/src/tests/mock.rs index a99a6be5d34..e4ac566ee96 100644 --- a/massa-execution-worker/src/tests/mock.rs +++ b/massa-execution-worker/src/tests/mock.rs @@ -144,6 +144,7 @@ pub fn get_sample_state( ) .unwrap() }; + final_state.init_execution_trail_hash(); let mut batch: BTreeMap, Option>> = DBBatch::new(); final_state.pos_state.create_initial_cycle(&mut batch); final_state diff --git a/massa-execution-worker/src/tests/tests_active_history.rs b/massa-execution-worker/src/tests/tests_active_history.rs index 7291570af24..2f383c60bcc 100644 --- a/massa-execution-worker/src/tests/tests_active_history.rs +++ b/massa-execution-worker/src/tests/tests_active_history.rs @@ -55,6 +55,7 @@ mod tests { }, executed_ops_changes: Default::default(), executed_denunciations_changes: Default::default(), + execution_trail_hash_change: Default::default(), }, events: Default::default(), }; diff --git a/massa-final-state/src/final_state.rs b/massa-final-state/src/final_state.rs index 244892893ab..48a62cf0c0f 100644 --- a/massa-final-state/src/final_state.rs +++ b/massa-final-state/src/final_state.rs @@ -8,6 +8,7 @@ use crate::{config::FinalStateConfig, error::FinalStateError, state_changes::StateChanges}; use massa_async_pool::AsyncPool; +use massa_db_exports::EXECUTION_TRAIL_HASH_PREFIX; use massa_db_exports::{ DBBatch, MassaIteratorMode, ShareableMassaDBController, ASYNC_POOL_PREFIX, CHANGE_ID_DESER_ERROR, CYCLE_HISTORY_PREFIX, DEFERRED_CREDITS_PREFIX, @@ -16,6 +17,7 @@ use massa_db_exports::{ use massa_executed_ops::ExecutedDenunciations; use massa_executed_ops::ExecutedOps; use massa_ledger_exports::LedgerController; +use massa_ledger_exports::SetOrKeep; use massa_models::slot::Slot; use massa_pos_exports::{PoSFinalState, SelectorController}; use massa_versioning::versioning::MipStore; @@ -117,6 +119,11 @@ impl FinalState { }; if reset_final_state { + // delete the execution trail hash + final_state + .db + .write() + .delete_prefix(EXECUTION_TRAIL_HASH_PREFIX, STATE_CF, None); final_state.async_pool.reset(); final_state.pos_state.reset(); final_state.executed_ops.reset(); @@ -141,6 +148,30 @@ impl FinalState { massa_hash::Hash::compute_from(internal_hash.to_bytes()) } + /// Get the slot at the end of which the final state is attached + pub fn get_slot(&self) -> Slot { + self.db + .read() + .get_change_id() + .expect("Critical error: Final state has no slot attached") + } + + /// Gets the hash of the execution trail + pub fn get_execution_trail_hash(&self) -> massa_hash::Hash { + let hash_bytes = self + .db + .read() + .get_cf(STATE_CF, EXECUTION_TRAIL_HASH_PREFIX.as_bytes().to_vec()) + .expect("could not read execution trail hash from state DB") + .expect("could not find execution trail hash in state DB"); + massa_hash::Hash::from_bytes( + hash_bytes + .as_slice() + .try_into() + .expect("invalid execution trail hash in state DB"), + ) + } + /// Initializes a `FinalState` from a snapshot. Currently, we do not use the final_state from the ledger, /// we just create a new one. This will be changed in the follow-up. /// @@ -535,6 +566,10 @@ impl FinalState { self.executed_ops.reset(); self.executed_denunciations.reset(); self.mip_store.reset_db(self.db.clone()); + // delete the execution trail hash + self.db + .write() + .delete_prefix(EXECUTION_TRAIL_HASH_PREFIX, STATE_CF, None); } /// Performs the initial draws. @@ -616,6 +651,14 @@ impl FinalState { ) }); + // Update execution trail hash + if let SetOrKeep::Set(new_hash) = changes.execution_trail_hash_change { + db_batch.insert( + EXECUTION_TRAIL_HASH_PREFIX.as_bytes().to_vec(), + Some(new_hash.to_bytes().to_vec()), + ); + } + self.db .write() .write_batch(db_batch, db_versioning_batch, Some(slot)); @@ -666,23 +709,29 @@ impl FinalState { pub fn is_db_valid(&self) -> bool { let db = self.db.read(); - for (serialized_key, serialized_value) in db.iterator_cf(STATE_CF, MassaIteratorMode::Start) + // check if the execution trial hash is present and valid { - if !serialized_key.starts_with(CYCLE_HISTORY_PREFIX.as_bytes()) - && !serialized_key.starts_with(DEFERRED_CREDITS_PREFIX.as_bytes()) - && !serialized_key.starts_with(ASYNC_POOL_PREFIX.as_bytes()) - && !serialized_key.starts_with(EXECUTED_OPS_PREFIX.as_bytes()) - && !serialized_key.starts_with(EXECUTED_DENUNCIATIONS_PREFIX.as_bytes()) - && !serialized_key.starts_with(LEDGER_PREFIX.as_bytes()) - && !serialized_key.starts_with(MIP_STORE_PREFIX.as_bytes()) - { - warn!( - "Key/value does not correspond to any prefix: serialized_key: {:?}, serialized_value: {:?}", - serialized_key, serialized_value - ); + let execution_trail_hash_serialized = + match db.get_cf(STATE_CF, EXECUTION_TRAIL_HASH_PREFIX.as_bytes().to_vec()) { + Ok(Some(v)) => v, + Ok(None) => { + warn!("No execution trail hash found in DB"); + return false; + } + Err(err) => { + warn!("Error reading execution trail hash from DB: {}", err); + return false; + } + }; + if let Err(err) = massa_hash::Hash::try_from(&execution_trail_hash_serialized[..]) { + warn!("Invalid execution trail hash found in DB: {}", err); return false; } + } + for (serialized_key, serialized_value) in db.iterator_cf(STATE_CF, MassaIteratorMode::Start) + { + #[allow(clippy::if_same_then_else)] if serialized_key.starts_with(CYCLE_HISTORY_PREFIX.as_bytes()) { if !self .pos_state @@ -735,13 +784,21 @@ impl FinalState { warn!("Wrong key/value for EXECUTED_DENUNCIATIONS PREFIX serialized_key: {:?}, serialized_value: {:?}", serialized_key, serialized_value); return false; } - } else if serialized_key.starts_with(LEDGER_PREFIX.as_bytes()) - && !self + } else if serialized_key.starts_with(LEDGER_PREFIX.as_bytes()) { + if !self .ledger .is_key_value_valid(&serialized_key, &serialized_value) - { + { + warn!("Wrong key/value for LEDGER PREFIX serialized_key: {:?}, serialized_value: {:?}", serialized_key, serialized_value); + return false; + } + } else if serialized_key.starts_with(MIP_STORE_PREFIX.as_bytes()) { + // TODO: check MIP_STORE_PREFIX + } else if serialized_key.starts_with(EXECUTION_TRAIL_HASH_PREFIX.as_bytes()) { + // no checks here as they are performed above by direct reading + } else { warn!( - "Wrong key/value for LEDGER PREFIX serialized_key: {:?}, serialized_value: {:?}", + "Key/value does not correspond to any prefix: serialized_key: {:?}, serialized_value: {:?}", serialized_key, serialized_value ); return false; @@ -750,4 +807,14 @@ impl FinalState { true } + + /// Initialize the execution trail hash to zero. + pub fn init_execution_trail_hash(&mut self) { + let mut db_batch = DBBatch::new(); + db_batch.insert( + EXECUTION_TRAIL_HASH_PREFIX.as_bytes().to_vec(), + Some(massa_hash::Hash::zero().to_bytes().to_vec()), + ); + self.db.write().write_batch(db_batch, DBBatch::new(), None); + } } diff --git a/massa-final-state/src/state_changes.rs b/massa-final-state/src/state_changes.rs index d5bd67ffc3d..fa23b214af3 100644 --- a/massa-final-state/src/state_changes.rs +++ b/massa-final-state/src/state_changes.rs @@ -10,7 +10,11 @@ use massa_executed_ops::{ ExecutedDenunciationsChangesSerializer, ExecutedOpsChanges, ExecutedOpsChangesDeserializer, ExecutedOpsChangesSerializer, }; -use massa_ledger_exports::{LedgerChanges, LedgerChangesDeserializer, LedgerChangesSerializer}; +use massa_hash::{HashDeserializer, HashSerializer}; +use massa_ledger_exports::{ + LedgerChanges, LedgerChangesDeserializer, LedgerChangesSerializer, SetOrKeep, + SetOrKeepDeserializer, SetOrKeepSerializer, +}; use massa_pos_exports::{PoSChanges, PoSChangesDeserializer, PoSChangesSerializer}; use massa_serialization::{Deserializer, SerializeError, Serializer}; use nom::{ @@ -33,6 +37,8 @@ pub struct StateChanges { pub executed_ops_changes: ExecutedOpsChanges, /// executed denunciations changes pub executed_denunciations_changes: ExecutedDenunciationsChanges, + /// execution trail hash change + pub execution_trail_hash_change: SetOrKeep, } /// Basic `StateChanges` serializer. @@ -42,6 +48,7 @@ pub struct StateChangesSerializer { pos_changes_serializer: PoSChangesSerializer, ops_changes_serializer: ExecutedOpsChangesSerializer, de_changes_serializer: ExecutedDenunciationsChangesSerializer, + execution_trail_hash_change_serializer: SetOrKeepSerializer, } impl Default for StateChangesSerializer { @@ -59,6 +66,7 @@ impl StateChangesSerializer { pos_changes_serializer: PoSChangesSerializer::new(), ops_changes_serializer: ExecutedOpsChangesSerializer::new(), de_changes_serializer: ExecutedDenunciationsChangesSerializer::new(), + execution_trail_hash_change_serializer: SetOrKeepSerializer::new(HashSerializer::new()), } } } @@ -123,6 +131,8 @@ impl Serializer for StateChangesSerializer { .serialize(&value.executed_ops_changes, buffer)?; self.de_changes_serializer .serialize(&value.executed_denunciations_changes, buffer)?; + self.execution_trail_hash_change_serializer + .serialize(&value.execution_trail_hash_change, buffer)?; Ok(()) } } @@ -134,6 +144,8 @@ pub struct StateChangesDeserializer { pos_changes_deserializer: PoSChangesDeserializer, ops_changes_deserializer: ExecutedOpsChangesDeserializer, de_changes_deserializer: ExecutedDenunciationsChangesDeserializer, + execution_trail_hash_change_deserializer: + SetOrKeepDeserializer, } impl StateChangesDeserializer { @@ -182,6 +194,9 @@ impl StateChangesDeserializer { endorsement_count, max_de_changes_length, ), + execution_trail_hash_change_deserializer: SetOrKeepDeserializer::new( + HashDeserializer::new(), + ), } } } @@ -261,21 +276,30 @@ impl Deserializer for StateChangesDeserializer { context("Failed de_changes deserialization", |input| { self.de_changes_deserializer.deserialize(input) }), + context( + "Failed execution_trail_hash_change deserialization", + |input| { + self.execution_trail_hash_change_deserializer + .deserialize(input) + }, + ), )), ) .map( |( ledger_changes, async_pool_changes, - roll_state_changes, - executed_ops, - executed_denunciations, + pos_changes, + executed_ops_changes, + executed_denunciations_changes, + execution_trail_hash_change, )| StateChanges { ledger_changes, async_pool_changes, - pos_changes: roll_state_changes, - executed_ops_changes: executed_ops, - executed_denunciations_changes: executed_denunciations, + pos_changes, + executed_ops_changes, + executed_denunciations_changes, + execution_trail_hash_change, }, ) .parse(buffer) @@ -291,5 +315,7 @@ impl StateChanges { self.pos_changes.extend(changes.pos_changes); self.executed_ops_changes .extend(changes.executed_ops_changes); + self.execution_trail_hash_change + .apply(changes.execution_trail_hash_change); } } diff --git a/massa-hash/src/hash.rs b/massa-hash/src/hash.rs index a663691a8e9..ac9dd0bafbf 100644 --- a/massa-hash/src/hash.rs +++ b/massa-hash/src/hash.rs @@ -70,6 +70,11 @@ impl std::fmt::Debug for Hash { } impl Hash { + /// Creates a hash full of zeros bytes. + pub fn zero() -> Self { + Hash(blake3::Hash::from([0; HASH_SIZE_BYTES])) + } + /// Compute a hash from data. /// /// # Example @@ -179,6 +184,17 @@ impl Hash { } } +impl TryFrom<&[u8]> for Hash { + type Error = MassaHashError; + + /// Try parsing from byte slice. + fn try_from(value: &[u8]) -> Result { + Ok(Hash::from_bytes(value.try_into().map_err(|err| { + MassaHashError::ParsingError(format!("{}", err)) + })?)) + } +} + /// Serializer for `Hash` #[derive(Default, Clone)] pub struct HashSerializer;