Skip to content

Commit

Permalink
feat(advance-runner)!: get on-chain machine hash
Browse files Browse the repository at this point in the history
BREAKING CHANGE: adds the PROVIDER_HTTP_ENDPOINT parameter, which is
required to validate snapshots. Validation is enabled by default, but
can be disabled by setting SNAPSHOT_VALIDATION_ENABLED to false
  • Loading branch information
torives committed Aug 23, 2023
1 parent 2c552ae commit ef3ceb0
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 70 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added snapshot validation. The node will now check if the snapshot's template hash matches the one stored in the blockchain

## [0.9.1] 2023-06-14

### Changed
Expand Down
3 changes: 3 additions & 0 deletions offchain/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions offchain/advance-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ rollups-events = { path = "../rollups-events" }
async-trait.workspace = true
backoff = { workspace = true, features = ["tokio"] }
clap = { workspace = true, features = ["derive", "env"] }
contracts = { path = "../contracts" }
hex.workspace = true
sha3 = { workspace = true, features = ["std"] }
snafu.workspace = true
state-fold-types = { workspace = true, features = ["ethers"] }
tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread"] }
tonic.workspace = true
tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
uuid = { workspace = true, features = ["v4"] }
url.workspace = true

[dev-dependencies]
test-fixtures = { path = "../test-fixtures" }
Expand Down
11 changes: 7 additions & 4 deletions offchain/advance-runner/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ impl AdvanceRunnerConfig {
pub fn parse() -> Result<Self, ConfigError> {
let cli_config = CLIConfig::parse();
let broker_config = cli_config.broker_cli_config.into();
let dapp_metadata = cli_config.dapp_metadata_cli_config.into();
let dapp_metadata: DAppMetadata =
cli_config.dapp_metadata_cli_config.into();
let server_manager_config =
ServerManagerConfig::parse_from_cli(cli_config.sm_cli_config);
let snapshot_config =
SnapshotConfig::parse_from_cli(cli_config.snapshot_cli_config)
.context(SnapshotConfigSnafu)?;
let snapshot_config = SnapshotConfig::new(
cli_config.snapshot_cli_config,
dapp_metadata.dapp_address.clone(),
)
.context(SnapshotConfigSnafu)?;
let backoff_max_elapsed_duration =
Duration::from_millis(cli_config.backoff_max_elapsed_duration);
let healthcheck_port = cli_config.healthcheck_port;
Expand Down
48 changes: 48 additions & 0 deletions offchain/advance-runner/src/dapp_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use contracts::cartesi_dapp::CartesiDApp;
use rollups_events::{Address, Hash};
use snafu::{ResultExt, Snafu};
use state_fold_types::ethers::{
prelude::ContractError,
providers::{Http, Provider, RetryClient},
};
use std::sync::Arc;

const MAX_RETRIES: u32 = 10;
const INITIAL_BACKOFF: u64 = 1000;

#[derive(Debug, Snafu)]
pub enum DappContractError {
#[snafu(display("failed to create a json rpc client"))]
CreateRpcClientError { source: url::ParseError },

#[snafu(display("failed to obtain hash from dapp contract"))]
ContractError {
source: ContractError<Provider<RetryClient<Http>>>,
},
}

pub async fn get_template_hash(
dapp_address: Address,
provider_http_endpoint: String,
) -> Result<Hash, DappContractError> {
let provider = Provider::new_client(
&provider_http_endpoint,
MAX_RETRIES,
INITIAL_BACKOFF,
)
.context(CreateRpcClientSnafu)?;

let cartesi_dapp =
CartesiDApp::new(dapp_address.inner(), Arc::new(provider));

let template_hash = cartesi_dapp
.get_template_hash()
.call()
.await
.context(ContractSnafu)?;

Ok(Hash::new(template_hash))
}
8 changes: 5 additions & 3 deletions offchain/advance-runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub use error::AdvanceRunnerError;

mod broker;
pub mod config;
mod dapp_contract;
mod error;
pub mod runner;
mod server_manager;
Expand Down Expand Up @@ -53,9 +54,10 @@ async fn start_advance_runner(
.context(error::ServerManagerSnafu)?;
tracing::trace!("connected to the server-manager");

let broker = BrokerFacade::new(config.broker_config, config.dapp_metadata)
.await
.context(error::BrokerSnafu)?;
let broker =
BrokerFacade::new(config.broker_config, config.dapp_metadata.clone())
.await
.context(error::BrokerSnafu)?;
tracing::trace!("connected the broker");

match config.snapshot_config {
Expand Down
18 changes: 8 additions & 10 deletions offchain/advance-runner/src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use rollups_events::{Event, InputMetadata, RollupsData, RollupsInput};
use snafu::{ResultExt, Snafu};

use crate::broker::{BrokerFacade, BrokerFacadeError};
use crate::server_manager::{ServerManagerError, ServerManagerFacade};
use crate::snapshot::SnapshotManager;
use rollups_events::{Event, InputMetadata, RollupsData, RollupsInput};
use snafu::{ResultExt, Snafu};

#[derive(Debug, Snafu)]
pub enum RunnerError<SnapError: snafu::Error + 'static> {
Expand Down Expand Up @@ -53,8 +52,8 @@ pub enum RunnerError<SnapError: snafu::Error + 'static> {
))]
ParentIdMismatchError { expected: String, got: String },

#[snafu(display("failed to get hash from snapshot "))]
GetSnapshotHashError { source: SnapError },
#[snafu(display("failed to validate snapshot"))]
ValidateSnapshotError { source: SnapError },
}

type Result<T, SnapError> = std::result::Result<T, RunnerError<SnapError>>;
Expand Down Expand Up @@ -121,12 +120,11 @@ impl<Snap: SnapshotManager + std::fmt::Debug + 'static> Runner<Snap> {
.context(GetLatestSnapshotSnafu)?;
tracing::info!(?snapshot, "got latest snapshot");

let offchain_hash = self
.snapshot_manager
.get_template_hash(&snapshot)
self.snapshot_manager
.validate(&snapshot)
.await
.context(GetSnapshotHashSnafu)?;
tracing::trace!(?offchain_hash, "got snapshot hash");
.context(ValidateSnapshotSnafu)?;
tracing::trace!("snapshot is valid");

let event_id = self
.broker
Expand Down
44 changes: 42 additions & 2 deletions offchain/advance-runner/src/snapshot/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use clap::Parser;
use rollups_events::Address;
use snafu::{ensure, Snafu};
use std::path::PathBuf;
use url::Url;

#[derive(Debug, Clone)]
pub struct FSManagerConfig {
pub snapshot_dir: PathBuf,
pub snapshot_latest: PathBuf,
pub validation_enabled: bool,
pub provider_http_endpoint: Option<String>,
pub dapp_address: Address,
}

#[derive(Debug, Clone)]
Expand All @@ -18,8 +23,9 @@ pub enum SnapshotConfig {
}

impl SnapshotConfig {
pub fn parse_from_cli(
pub fn new(
cli_config: SnapshotCLIConfig,
dapp_address: Address,
) -> Result<Self, SnapshotConfigError> {
if cli_config.snapshot_enabled {
let snapshot_dir = PathBuf::from(cli_config.snapshot_dir);
Expand All @@ -28,9 +34,27 @@ impl SnapshotConfig {
let snapshot_latest = PathBuf::from(cli_config.snapshot_latest);
ensure!(snapshot_latest.is_symlink(), SymlinkSnafu);

let validation_enabled = cli_config.snapshot_validation_enabled;
let provider_http_endpoint = cli_config.provider_http_endpoint;
if validation_enabled {
if let Some(endpoint) = &provider_http_endpoint {
ensure!(
Url::parse(endpoint.as_str()).is_ok(),
EndpointParseSnafu { endpoint }
);
} else {
return Err(
SnapshotConfigError::NoProviderEndpointError {},
);
}
}

Ok(SnapshotConfig::FileSystem(FSManagerConfig {
snapshot_dir,
snapshot_latest,
validation_enabled,
provider_http_endpoint,
dapp_address,
}))
} else {
Ok(SnapshotConfig::Disabled)
Expand All @@ -39,18 +63,25 @@ impl SnapshotConfig {
}

#[derive(Debug, Snafu)]
#[allow(clippy::enum_variant_names)]
pub enum SnapshotConfigError {
#[snafu(display("Snapshot dir isn't a directory"))]
DirError {},

#[snafu(display("Snapshot latest isn't a symlink"))]
SymlinkError {},

#[snafu(display("A provider http endpoint is required"))]
NoProviderEndpointError {},

#[snafu(display("provider_http_endpoint isn't a valid URL"))]
EndpointParseError { endpoint: String },
}

#[derive(Parser, Debug)]
#[command(name = "snapshot")]
pub struct SnapshotCLIConfig {
/// If set to false, disable snapshots
/// If set to false, disables snapshots. Enabled by default
#[arg(long, env, default_value_t = true)]
snapshot_enabled: bool,

Expand All @@ -61,4 +92,13 @@ pub struct SnapshotCLIConfig {
/// Path to the symlink of the latest snapshot
#[arg(long, env)]
snapshot_latest: String,

/// If set to false, disables snapshot validation. Enabled by default
#[arg(long, env, default_value_t = true)]
snapshot_validation_enabled: bool,

/// The endpoint for a JSON-RPC provider.
/// Required if SNAPSHOT_VALIDATION_ENABLED is `true`
#[arg(long, env)]
provider_http_endpoint: Option<String>,
}
20 changes: 7 additions & 13 deletions offchain/advance-runner/src/snapshot/disabled.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use super::{Snapshot, SnapshotManager};
use rollups_events::Hash;

#[derive(Debug)]
pub struct SnapshotDisabled {}
Expand All @@ -17,7 +16,7 @@ impl SnapshotManager for SnapshotDisabled {

/// Get the most recent snapshot
#[tracing::instrument(level = "trace", skip_all)]
async fn get_latest(&self) -> Result<Snapshot, SnapshotDisabledError> {
async fn get_latest(&self) -> Result<Snapshot, Self::Error> {
tracing::trace!("snapshots disabled; returning default");
Ok(Default::default())
}
Expand All @@ -28,26 +27,21 @@ impl SnapshotManager for SnapshotDisabled {
&self,
_: u64,
_: u64,
) -> Result<Snapshot, SnapshotDisabledError> {
) -> Result<Snapshot, Self::Error> {
tracing::trace!("snapshots disabled; returning default");
Ok(Default::default())
}

/// Set the most recent snapshot
#[tracing::instrument(level = "trace", skip_all)]
async fn set_latest(
&self,
_: Snapshot,
) -> Result<(), SnapshotDisabledError> {
async fn set_latest(&self, _: Snapshot) -> Result<(), Self::Error> {
tracing::trace!("snapshots disabled; ignoring");
Ok(())
}

async fn get_template_hash(
&self,
_: &Snapshot,
) -> Result<Hash, SnapshotDisabledError> {
tracing::trace!("snapshots disabled; returning default");
Ok(Hash::default())
#[tracing::instrument(level = "trace", skip_all)]
async fn validate(&self, _: &Snapshot) -> Result<(), Self::Error> {
tracing::trace!("snapshots disabled; ignoring");
Ok(())
}
}
Loading

0 comments on commit ef3ceb0

Please sign in to comment.