Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(offchain): adds the authority-claimer #45

Merged
merged 1 commit into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions offchain/Cargo.lock

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

2 changes: 2 additions & 0 deletions offchain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"advance-runner",
"authority-claimer",
gligneul marked this conversation as resolved.
Show resolved Hide resolved
"contracts",
"data",
"dispatcher",
Expand Down Expand Up @@ -38,6 +39,7 @@ diesel_migrations = "2.0"
env_logger = "0.10"
eth-tx-manager = "0.10"
ethabi = "18.0"
ethers = "1.0"
ethers-signers = "1.0"
futures = "0.3"
futures-util = "0.3"
Expand Down
26 changes: 26 additions & 0 deletions offchain/authority-claimer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "authority-claimer"
license = "Apache-2.0"
version = "1.0.0"
edition = "2021"

[[bin]]
name = "cartesi-rollups-authority-claimer"
path = "src/main.rs"
test = false

[dependencies]
http-server = { path = "../http-server" }
rollups-events = { path = "../rollups-events" }

async-trait.workspace = true
clap = { workspace = true, features = ["derive"] }
eth-tx-manager.workspace = true
ethers.workspace = true
rusoto_core.workspace = true
serde.workspace = true
serde_json.workspace = true
snafu.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing.workspace = true
49 changes: 49 additions & 0 deletions offchain/authority-claimer/src/checker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use async_trait::async_trait;
gligneul marked this conversation as resolved.
Show resolved Hide resolved
use rollups_events::RollupsClaim;
use snafu::Snafu;
use std::fmt::Debug;

/// The `DuplicateChecker` checks if a given claim was already submitted
/// to the blockchain.
#[async_trait]
pub trait DuplicateChecker: Debug {
type Error: snafu::Error;

async fn is_duplicated_rollups_claim(
&self,
rollups_claim: &RollupsClaim,
) -> Result<bool, Self::Error>;
}

// ------------------------------------------------------------------------------------------------
// DefaultDuplicateChecker
// ------------------------------------------------------------------------------------------------

#[derive(Debug)]
pub struct DefaultDuplicateChecker;

#[derive(Debug, Snafu)]
pub enum DefaultDuplicateCheckerError {
Todo,
}

impl DefaultDuplicateChecker {
pub fn new() -> Result<Self, DefaultDuplicateCheckerError> {
todo!()
}
}

#[async_trait]
impl DuplicateChecker for DefaultDuplicateChecker {
type Error = DefaultDuplicateCheckerError;

async fn is_duplicated_rollups_claim(
&self,
_rollups_claim: &RollupsClaim,
) -> Result<bool, Self::Error> {
todo!()
}
}
89 changes: 89 additions & 0 deletions offchain/authority-claimer/src/claimer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use async_trait::async_trait;
use snafu::ResultExt;
use tracing::{info, trace};

use crate::{
checker::DuplicateChecker, listener::BrokerListener,
sender::TransactionSender,
};

/// The `AuthorityClaimer` starts an event loop that waits for claim messages
/// from the broker, and then sends the claims to the blockchain. It checks to
/// see if the claim is duplicated before sending.
///
/// It uses three injected traits, `BrokerListener`, `DuplicateChecker`, and
/// `TransactionSender`, to, respectivelly, listen for messages, check for
/// duplicated claims, and send claims to the blockchain.
#[async_trait]
pub trait AuthorityClaimer {
async fn start<L, C, S>(
&self,
broker_listener: L,
duplicate_checker: C,
transaction_sender: S,
) -> Result<(), AuthorityClaimerError<L, C, S>>
where
L: BrokerListener + Send + Sync,
C: DuplicateChecker + Send + Sync,
S: TransactionSender + Send,
{
trace!("Starting the authority claimer loop");
let mut transaction_sender = transaction_sender;
loop {
let rollups_claim = broker_listener
.listen()
.await
.context(BrokerListenerSnafu)?;
trace!("Got a claim from the broker: {:?}", rollups_claim);

let is_duplicated_rollups_claim = duplicate_checker
.is_duplicated_rollups_claim(&rollups_claim)
.await
.context(DuplicateCheckerSnafu)?;
gligneul marked this conversation as resolved.
Show resolved Hide resolved
if is_duplicated_rollups_claim {
trace!("It was a duplicated claim");
continue;
}

info!("Sending a new rollups claim");
transaction_sender = transaction_sender
.send_rollups_claim(rollups_claim)
.await
.context(TransactionSenderSnafu)?
}
}
}

#[derive(Debug, snafu::Snafu)]
pub enum AuthorityClaimerError<
L: BrokerListener + 'static,
C: DuplicateChecker + 'static,
S: TransactionSender + 'static,
> {
#[snafu(display("broker listener error"))]
BrokerListenerError { source: L::Error },

#[snafu(display("duplicate checker error"))]
DuplicateCheckerError { source: C::Error },

#[snafu(display("transaction sender error"))]
TransactionSenderError { source: S::Error },
}

// ------------------------------------------------------------------------------------------------
// DefaultAuthorityClaimer
// ------------------------------------------------------------------------------------------------

#[derive(Default)]
pub struct DefaultAuthorityClaimer;

impl DefaultAuthorityClaimer {
pub fn new() -> Self {
Self
}
}

impl AuthorityClaimer for DefaultAuthorityClaimer {}
136 changes: 136 additions & 0 deletions offchain/authority-claimer/src/config/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

use clap::{command, Parser};
use eth_tx_manager::{
config::{TxEnvCLIConfig as TxManagerCLIConfig, TxManagerConfig},
Priority,
};
use rollups_events::{BrokerCLIConfig, BrokerConfig};
use rusoto_core::Region;
use snafu::ResultExt;
use std::{fs, path::PathBuf, str::FromStr};

use crate::config::{
error::{
AuthorityClaimerConfigError, InvalidRegionSnafu, MnemonicFileSnafu,
TxManagerSnafu, TxSigningConfigError, TxSigningSnafu,
},
json::{read_json_file, DappDeployment},
AuthorityClaimerConfig, TxSigningConfig,
};

// ------------------------------------------------------------------------------------------------
// AuthorityClaimerCLI
// ------------------------------------------------------------------------------------------------

#[derive(Clone, Parser)]
#[command(name = "rd_config")]
#[command(about = "Configuration for rollups authority claimer")]
pub(crate) struct AuthorityClaimerCLI {
torives marked this conversation as resolved.
Show resolved Hide resolved
#[command(flatten)]
tx_manager_config: TxManagerCLIConfig,

#[command(flatten)]
tx_signing_config: TxSigningCLIConfig,

#[command(flatten)]
broker_config: BrokerCLIConfig,

/// Path to a file with the deployment json of the dapp
#[arg(long, env, default_value = "./dapp_deployment.json")]
dapp_deployment_file: PathBuf,
}

impl TryFrom<AuthorityClaimerCLI> for AuthorityClaimerConfig {
type Error = AuthorityClaimerConfigError;

fn try_from(cli_config: AuthorityClaimerCLI) -> Result<Self, Self::Error> {
let tx_manager_config =
TxManagerConfig::initialize(cli_config.tx_manager_config)
.context(TxManagerSnafu)?;

let tx_signing_config =
TxSigningConfig::try_from(cli_config.tx_signing_config)
.context(TxSigningSnafu)?;

let broker_config = BrokerConfig::from(cli_config.broker_config);

let dapp_deployment =
read_json_file::<DappDeployment>(cli_config.dapp_deployment_file)?;
let dapp_address = dapp_deployment.dapp_address;
let dapp_deploy_block_hash = dapp_deployment.dapp_deploy_block_hash;

Ok(AuthorityClaimerConfig {
tx_manager_config,
tx_signing_config,
tx_manager_priority: Priority::Normal,
broker_config,
dapp_address,
dapp_deploy_block_hash,
})
}
}

// ------------------------------------------------------------------------------------------------
// TxSigningConfig
// ------------------------------------------------------------------------------------------------

#[derive(Debug, Clone, Parser)]
#[command(name = "tx_signing_config")]
#[command(about = "Configuration for signing transactions")]
pub(crate) struct TxSigningCLIConfig {
/// Signer mnemonic, overrides `tx_signing_mnemonic_file` and `tx_signing_aws_kms_*`
#[arg(long, env)]
tx_signing_mnemonic: Option<String>,

/// Signer mnemonic file path, overrides `tx_signing_aws_kms_*`
#[arg(long, env)]
tx_signing_mnemonic_file: Option<String>,

/// Mnemonic account index
#[arg(long, env)]
tx_signing_mnemonic_account_index: Option<u32>,

/// AWS KMS signer key-id
#[arg(long, env)]
tx_signing_aws_kms_key_id: Option<String>,

/// AWS KMS signer region
#[arg(long, env)]
tx_signing_aws_kms_region: Option<String>,
}

impl TryFrom<TxSigningCLIConfig> for TxSigningConfig {
type Error = TxSigningConfigError;

fn try_from(cli: TxSigningCLIConfig) -> Result<Self, Self::Error> {
let account_index = cli.tx_signing_mnemonic_account_index;
if let Some(mnemonic) = cli.tx_signing_mnemonic {
Ok(TxSigningConfig::Mnemonic {
mnemonic,
account_index,
})
} else if let Some(path) = cli.tx_signing_mnemonic_file {
let mnemonic = fs::read_to_string(path.clone())
.context(MnemonicFileSnafu { path })?
.trim()
.to_string();
Ok(TxSigningConfig::Mnemonic {
mnemonic,
account_index,
})
} else {
match (cli.tx_signing_aws_kms_key_id, cli.tx_signing_aws_kms_region)
{
(None, _) => Err(TxSigningConfigError::MissingConfiguration),
(Some(_), None) => Err(TxSigningConfigError::MissingRegion),
(Some(key_id), Some(region)) => {
let region = Region::from_str(&region)
.context(InvalidRegionSnafu)?;
Ok(TxSigningConfig::Aws { key_id, region })
}
}
torives marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
48 changes: 48 additions & 0 deletions offchain/authority-claimer/src/config/error.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 eth_tx_manager::config::Error as TxManagerConfigError;
use rusoto_core::region::ParseRegionError;
use snafu::Snafu;
use std::path::PathBuf;

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum AuthorityClaimerConfigError {
#[snafu(display("TxManager configuration error"))]
TxManagerError { source: TxManagerConfigError },

#[snafu(display("TxSigning configuration error"))]
TxSigningError { source: TxSigningConfigError },

#[snafu(display("Read file error ({})", path.display()))]
ReadFileError {
path: PathBuf,
source: std::io::Error,
},

#[snafu(display("Json parse error ({})", path.display()))]
JsonParseError {
path: PathBuf,
source: serde_json::Error,
},
}

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum TxSigningConfigError {
#[snafu(display("Missing auth configuration"))]
MissingConfiguration,

#[snafu(display("Could not read mnemonic file at path `{}`", path,))]
MnemonicFileError {
path: String,
source: std::io::Error,
},

#[snafu(display("Missing AWS region"))]
MissingRegion,

#[snafu(display("Invalid AWS region"))]
InvalidRegion { source: ParseRegionError },
}
Loading
Loading