diff --git a/offchain/advance-runner/src/runner.rs b/offchain/advance-runner/src/runner.rs index bdfeac15d..21004e27c 100644 --- a/offchain/advance-runner/src/runner.rs +++ b/offchain/advance-runner/src/runner.rs @@ -52,6 +52,9 @@ pub enum RunnerError { got ))] ParentIdMismatchError { expected: String, got: String }, + + #[snafu(display("failed to get hash from snapshot "))] + GetSnapshotHashError { source: SnapError }, } type Result = std::result::Result>; @@ -118,6 +121,13 @@ impl Runner { .context(GetLatestSnapshotSnafu)?; tracing::info!(?snapshot, "got latest snapshot"); + let offchain_hash = self + .snapshot_manager + .get_template_hash(&snapshot) + .await + .context(GetSnapshotHashSnafu)?; + tracing::trace!(?offchain_hash, "got snapshot hash"); + let event_id = self .broker .find_previous_finish_epoch(snapshot.epoch) diff --git a/offchain/advance-runner/src/snapshot/disabled.rs b/offchain/advance-runner/src/snapshot/disabled.rs index 340221073..5b22459b8 100644 --- a/offchain/advance-runner/src/snapshot/disabled.rs +++ b/offchain/advance-runner/src/snapshot/disabled.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 (see LICENSE) use super::{Snapshot, SnapshotManager}; +use rollups_events::Hash; #[derive(Debug)] pub struct SnapshotDisabled {} @@ -41,4 +42,12 @@ impl SnapshotManager for SnapshotDisabled { tracing::trace!("snapshots disabled; ignoring"); Ok(()) } + + async fn get_template_hash( + &self, + _: &Snapshot, + ) -> Result { + tracing::trace!("snapshots disabled; returning default"); + Ok(Hash::default()) + } } diff --git a/offchain/advance-runner/src/snapshot/fs_manager.rs b/offchain/advance-runner/src/snapshot/fs_manager.rs index 5c8bedd28..9cf01b496 100644 --- a/offchain/advance-runner/src/snapshot/fs_manager.rs +++ b/offchain/advance-runner/src/snapshot/fs_manager.rs @@ -1,14 +1,18 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +use rollups_events::{Hash, HASH_SIZE}; use snafu::{ensure, OptionExt, ResultExt, Snafu}; use std::collections::HashSet; -use std::fs; +use std::fs::{self, File}; +use std::io::Read; use std::path::{Path, PathBuf}; use super::config::FSManagerConfig; use super::{Snapshot, SnapshotManager}; +const HASH_FILE: &str = "hash"; + #[derive(Debug, Snafu)] #[allow(clippy::enum_variant_names)] pub enum FSSnapshotError { @@ -67,6 +71,18 @@ pub enum FSSnapshotError { path: PathBuf, source: std::io::Error, }, + + #[snafu(display("failed to open hash file for snapshot ({})", path.display()))] + OpenHashError { + path: PathBuf, + source: std::io::Error, + }, + + #[snafu(display("failed to read hash file for snapshot ({})", path.display()))] + ReadHashError { + path: PathBuf, + source: std::io::Error, + }, } #[derive(Debug)] @@ -196,6 +212,28 @@ impl SnapshotManager for FSSnapshotManager { Ok(()) } + + /// Reads the binary contents of the hash file in snapshot's directory + /// and converts them to a `Hash`. It assumes the file was created correctly + /// and makes no checks in this regard. + #[tracing::instrument(level = "trace", skip_all)] + async fn get_template_hash( + &self, + snapshot: &Snapshot, + ) -> Result { + let path = snapshot.path.join(HASH_FILE); + tracing::trace!(?path, "opening hash file at"); + let file = File::open(path.clone()) + .context(OpenHashSnafu { path: path.clone() })?; + + let mut buffer = [0_u8; HASH_SIZE]; + let bytes = file + .take(HASH_SIZE as u64) + .read(&mut buffer) + .context(ReadHashSnafu { path: path.clone() })?; + tracing::trace!("read {bytes} bytes from file"); + Ok(Hash::new(buffer)) + } } fn encode_filename(epoch: u64, processed_input_count: u64) -> String { @@ -525,4 +563,51 @@ mod tests { state.tempdir.path().join("2_2"), ); } + + #[test_log::test(tokio::test)] + async fn test_it_gets_snapshot_hash() { + let state = TestState::setup(); + let path = state.create_snapshot("0_0"); + let hash_path = path.join(HASH_FILE); + let hash = [ + 160, 170, 75, 88, 113, 141, 144, 31, 252, 78, 159, 6, 79, 114, 6, + 16, 196, 49, 44, 208, 62, 83, 66, 97, 4, 151, 159, 105, 124, 85, + 51, 87, + ]; + fs::write(hash_path, hash).expect("should write hash to file"); + + let snap = Snapshot { + epoch: 0, + processed_input_count: 0, + path, + }; + + assert_eq!( + state + .manager + .get_template_hash(&snap) + .await + .expect("get template hash should work"), + Hash::new(hash) + ); + } + + #[test_log::test(tokio::test)] + async fn test_it_fails_to_get_hash_when_hash_file_does_not_exist() { + let state = TestState::setup(); + let path = state.create_snapshot("0_0"); + let snap = Snapshot { + epoch: 0, + processed_input_count: 0, + path, + }; + + let err = state + .manager + .get_template_hash(&snap) + .await + .expect_err("get template hash should fail"); + + assert!(matches!(err, FSSnapshotError::OpenHashError { .. })) + } } diff --git a/offchain/advance-runner/src/snapshot/mod.rs b/offchain/advance-runner/src/snapshot/mod.rs index 3149aa68b..f06402901 100644 --- a/offchain/advance-runner/src/snapshot/mod.rs +++ b/offchain/advance-runner/src/snapshot/mod.rs @@ -1,14 +1,15 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) +use rollups_events::Hash; use std::path::PathBuf; pub mod config; pub mod disabled; pub mod fs_manager; -/// Cartesi Machine snapshot description -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// A path to a Cartesi Machine snapshot and its metadata +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Snapshot { pub path: PathBuf, pub epoch: u64, @@ -31,4 +32,10 @@ pub trait SnapshotManager { /// Set the most recent snapshot async fn set_latest(&self, snapshot: Snapshot) -> Result<(), Self::Error>; + + /// Get the snapshot's template hash + async fn get_template_hash( + &self, + snapshot: &Snapshot, + ) -> Result; }