diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 5ba86dfb89..56bfa32c25 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -25,8 +25,9 @@ use crate::chain::header_validation::Validator; #[allow(dead_code)] pub(crate) enum RevertTarget { - LastFinalizedState = 0, - LastEpoch = 1, + Commit([u8; 32]), + LastFinalizedState, + LastEpoch, } /// Implements block acceptance procedure. This includes block header, @@ -407,17 +408,31 @@ impl Acceptor { let target_state_hash = match target { RevertTarget::LastFinalizedState => { - info!(event = "vm_revert to last finalized state"); - let state_hash = self.vm.read().await.revert()?; + let vm = self.vm.read().await; + let state_hash = vm.revert_to_finalized()?; info!( event = "vm reverted", - state_root = hex::encode(state_hash) + state_root = hex::encode(state_hash), + is_final = "true", ); anyhow::Ok(state_hash) } - _ => unimplemented!(), + RevertTarget::Commit(state_hash) => { + let vm = self.vm.read().await; + let state_hash = vm.revert(state_hash)?; + let is_final = vm.get_finalized_state_root()? == state_hash; + + info!( + event = "vm reverted", + state_root = hex::encode(state_hash), + is_final, + ); + + anyhow::Ok(state_hash) + } + RevertTarget::LastEpoch => unimplemented!(), }?; // Delete any block until we reach the target_state_hash, the diff --git a/node/src/chain/fsm.rs b/node/src/chain/fsm.rs index d566a3ba7c..473b476023 100644 --- a/node/src/chain/fsm.rs +++ b/node/src/chain/fsm.rs @@ -299,7 +299,7 @@ impl InSyncImpl { // R_B.Iteration < L_B.Iteration // // Then we fallback to N_B.PrevBlock and accept N_B - let local_header = acc.db.read().await.view(|t| { + let result = acc.db.read().await.view(|t| { if let Some((prev_header, _)) = t.fetch_block_header(&remote_blk.header().prev_block_hash)? { @@ -308,7 +308,10 @@ impl InSyncImpl { if remote_blk.header().iteration < l_b.header().iteration { - return Ok(Some(l_b.header().clone())); + return Ok(Some(( + l_b.header().clone(), + prev_header.state_hash, + ))); } } } @@ -316,12 +319,12 @@ impl InSyncImpl { anyhow::Ok(None) })?; - if let Some(local_header) = local_header { + if let Some((local_header, state_hash)) = result { match fallback::WithContext::new(acc.deref()) .try_revert( &local_header, remote_blk.header(), - RevertTarget::LastFinalizedState, + RevertTarget::Commit(state_hash), ) .await { @@ -360,11 +363,28 @@ impl InSyncImpl { new_iter = remote_blk.header().iteration, ); + let state_hash = acc + .db + .read() + .await + .view(|t| { + let res = t + .fetch_block_header( + &remote_blk.header().prev_block_hash, + )? + .map(|(prev_header, _)| prev_header.state_hash); + + anyhow::Ok::>(res) + })? + .ok_or_else(|| { + anyhow::anyhow!("could not retrieve state_hash") + })?; + match fallback::WithContext::new(acc.deref()) .try_revert( &local_header, remote_blk.header(), - RevertTarget::LastFinalizedState, + RevertTarget::Commit(state_hash), ) .await { diff --git a/node/src/vm.rs b/node/src/vm.rs index 308b8cea86..9dadcb83d4 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -48,5 +48,9 @@ pub trait VMExecution: Send + Sync + 'static { fn get_state_root(&self) -> anyhow::Result<[u8; 32]>; - fn revert(&self) -> anyhow::Result<[u8; 32]>; + /// Returns last finalized state root + fn get_finalized_state_root(&self) -> anyhow::Result<[u8; 32]>; + + fn revert(&self, state_hash: [u8; 32]) -> anyhow::Result<[u8; 32]>; + fn revert_to_finalized(&self) -> anyhow::Result<[u8; 32]>; } diff --git a/node/testbed.sh b/node/testbed.sh index c7199c705b..9883539410 100755 --- a/node/testbed.sh +++ b/node/testbed.sh @@ -51,7 +51,7 @@ RUSK_STATE_PATH=${RUSK_STATE_PATH} cargo r --release -p rusk -- recovery-state --init $GENESIS_PATH echo "starting node $ID ..." echo "${KEYS_PATH}/node_$ID.keys" - RUSK_STATE_PATH=${RUSK_STATE_PATH} ./target/release/rusk --kadcast-bootstrap "$BOOTSTRAP_ADDR" --kadcast-public-address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" --log-filter="dusk_consensus=debug" --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="$NODE_FOLDER" --http-listen-addr "$WS_LISTEN_ADDR" --delay-on-resp-msg=10 > "${TEMPD}/node_${ID}.log" & + RUSK_STATE_PATH=${RUSK_STATE_PATH} ./target/release/rusk --kadcast-bootstrap "$BOOTSTRAP_ADDR" --kadcast-public-address "$PUBLIC_ADDR" --log-type="json" --log-level="$LOG_LEVEL" --log-filter="dusk_consensus=debug" --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="$NODE_FOLDER" --http-listen-addr "$WS_LISTEN_ADDR" --delay-on-resp-msg=10 > "${TEMPD}/node_${ID}.log" & } ## Use ~/.cargo/bin/tokio-console --retain-for 0s http://127.0.0.1:10000 to connect console to first node diff --git a/rusk/benches/block_ingestion.rs b/rusk/benches/block_ingestion.rs index 72f83db131..1d495d0cba 100644 --- a/rusk/benches/block_ingestion.rs +++ b/rusk/benches/block_ingestion.rs @@ -99,7 +99,8 @@ pub fn accept_benchmark(c: &mut Criterion) { ) .expect("Accepting transactions should succeed"); - rusk.revert().expect("Reverting should succeed"); + rusk.revert_to_base_root() + .expect("Reverting should succeed"); }) }, ); diff --git a/rusk/src/lib/error.rs b/rusk/src/lib/error.rs index d5986e2e75..d6b715aa70 100644 --- a/rusk/src/lib/error.rs +++ b/rusk/src/lib/error.rs @@ -49,6 +49,8 @@ pub enum Error { InconsistentState(VerificationOutput), /// Other Other(Box), + /// Commit not found amongst existing commits + CommitNotFound([u8; 32]), } impl std::error::Error for Error {} @@ -140,6 +142,9 @@ impl fmt::Display for Error { "Inconsistent state verification data {verification_output}", ) } + Error::CommitNotFound(commit_id) => { + write!(f, "Commit not found, id = {}", hex::encode(commit_id),) + } } } } diff --git a/rusk/src/lib/lib.rs b/rusk/src/lib/lib.rs index 4b5775e321..0b0bb6a05a 100644 --- a/rusk/src/lib/lib.rs +++ b/rusk/src/lib/lib.rs @@ -334,12 +334,22 @@ impl Rusk { Ok(()) } - pub fn revert(&self) -> Result<[u8; 32]> { + pub fn revert(&self, state_hash: [u8; 32]) -> Result<[u8; 32]> { let mut inner = self.inner.lock(); - inner.current_commit = inner.base_commit; + + let commits = &inner.vm.commits(); + if !commits.contains(&state_hash) { + return Err(Error::CommitNotFound(state_hash)); + } + + inner.current_commit = state_hash; Ok(inner.current_commit) } + pub fn revert_to_base_root(&self) -> Result<[u8; 32]> { + self.revert(self.base_root()) + } + /// Perform an action with the underlying data structure. pub fn with_inner<'a, F, T>(&'a self, closure: F) -> T where diff --git a/rusk/src/lib/vm.rs b/rusk/src/lib/vm.rs index 271a620584..30de5c7fe9 100644 --- a/rusk/src/lib/vm.rs +++ b/rusk/src/lib/vm.rs @@ -149,13 +149,25 @@ impl VMExecution for Rusk { Ok(self.state_root()) } - fn revert(&self) -> anyhow::Result<[u8; 32]> { + fn get_finalized_state_root(&self) -> anyhow::Result<[u8; 32]> { + Ok(self.base_root()) + } + + fn revert(&self, state_hash: [u8; 32]) -> anyhow::Result<[u8; 32]> { let state_hash = self - .revert() + .revert(state_hash) .map_err(|inner| anyhow::anyhow!("Cannot revert: {inner}"))?; Ok(state_hash) } + + fn revert_to_finalized(&self) -> anyhow::Result<[u8; 32]> { + let state_hash = self.revert_to_base_root().map_err(|inner| { + anyhow::anyhow!("Cannot revert to finalized: {inner}") + })?; + + Ok(state_hash) + } } impl Rusk { diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index 97c6302715..1a634cc267 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -102,7 +102,7 @@ pub fn rusk_state_accepted() -> Result<()> { "There should be two notes in the state now" ); - rusk.revert()?; + rusk.revert_to_base_root()?; let leaves = leaves_from_height(&rusk, 0)?; assert_eq!( @@ -134,7 +134,7 @@ pub fn rusk_state_finalized() -> Result<()> { "There should be two notes in the state now" ); - rusk.revert()?; + rusk.revert_to_base_root()?; let leaves = leaves_from_height(&rusk, 0)?; assert_eq!( diff --git a/rusk/tests/services/transfer.rs b/rusk/tests/services/transfer.rs index e0ef752cb4..98948fade0 100644 --- a/rusk/tests/services/transfer.rs +++ b/rusk/tests/services/transfer.rs @@ -185,7 +185,8 @@ pub async fn wallet() -> Result<()> { assert_ne!(original_root, new_root, "Root should have changed"); // Revert the state - rusk.revert().expect("Reverting should succeed"); + rusk.revert_to_base_root() + .expect("Reverting should succeed"); cache.write().unwrap().clear(); // Check the state's root is back to the original one