-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2147 from oasisprotocol/kostko/feature/rofl-appd-tx
rofl-appd: Optionally add the transaction endpoints
- Loading branch information
Showing
8 changed files
with
253 additions
and
16 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
pub mod app; | ||
pub mod keys; | ||
#[cfg(feature = "tx")] | ||
pub mod tx; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
use std::{collections::BTreeSet, sync::Arc}; | ||
|
||
use rocket::{http::Status, serde::json::Json, State}; | ||
use serde_with::serde_as; | ||
|
||
use oasis_runtime_sdk::{modules::rofl::app::client::SubmitTxOpts, types::transaction}; | ||
use oasis_runtime_sdk_evm as evm; | ||
|
||
use crate::state::Env; | ||
|
||
/// Transaction endpoint configuration. | ||
#[derive(Debug, Clone)] | ||
pub struct Config { | ||
/// Allowed method names. | ||
pub allowed_methods: BTreeSet<String>, | ||
} | ||
|
||
impl Default for Config { | ||
fn default() -> Self { | ||
Self { | ||
// A default set of safe methods to be used from ROFL apps. Specifically this disallows | ||
// key derivation to avoid bypassing the built-in KMS. | ||
allowed_methods: BTreeSet::from_iter( | ||
[ | ||
"accounts.Transfer", | ||
"consensus.Deposit", | ||
"consensus.Withdraw", | ||
"consensus.Delegate", | ||
"consensus.Undelegate", | ||
"evm.Call", | ||
"evm.Create", | ||
"rofl.Create", | ||
"rofl.Update", | ||
"rofl.Remove", | ||
] | ||
.iter() | ||
.map(|m| m.to_string()), | ||
), | ||
} | ||
} | ||
} | ||
|
||
/// A type that can represent both standard and Ethereum transactions. | ||
#[serde_as] | ||
#[derive(Clone, Debug, serde::Deserialize)] | ||
#[serde(tag = "kind", content = "data")] | ||
pub enum Transaction { | ||
/// Standard Oasis SDK transaction. | ||
#[serde(rename = "std")] | ||
Std(#[serde_as(as = "serde_with::hex::Hex")] Vec<u8>), | ||
|
||
/// Ethereum transaction. | ||
#[serde(rename = "eth")] | ||
Eth { | ||
gas_limit: u64, | ||
#[serde_as(as = "serde_with::hex::Hex")] | ||
to: Vec<u8>, | ||
value: u128, | ||
#[serde_as(as = "serde_with::hex::Hex")] | ||
data: Vec<u8>, | ||
}, | ||
} | ||
|
||
/// Transaction signing and submission request. | ||
#[serde_as] | ||
#[derive(Clone, Debug, serde::Deserialize)] | ||
pub struct SignAndSubmitRequest { | ||
/// Transaction. | ||
pub tx: Transaction, | ||
|
||
/// Whether the transaction calldata should be encrypted. | ||
#[serde(default = "default_encrypt_flag")] | ||
pub encrypt: bool, | ||
} | ||
|
||
/// Default value for the `encrypt` field in `SignAndSubmitRequest`. | ||
fn default_encrypt_flag() -> bool { | ||
true | ||
} | ||
|
||
/// Transaction signing and submission response. | ||
#[serde_as] | ||
#[derive(Clone, Default, serde::Serialize)] | ||
pub struct SignAndSubmitResponse { | ||
/// Raw response data. | ||
#[serde_as(as = "serde_with::hex::Hex")] | ||
pub data: Vec<u8>, | ||
} | ||
|
||
/// Sign and submit a transaction to the registration paratime. The signer of the transaction | ||
/// will be a key that is authenticated to represent this ROFL app instance. | ||
#[rocket::post("/sign-submit", data = "<body>")] | ||
pub async fn sign_and_submit( | ||
body: Json<SignAndSubmitRequest>, | ||
env: &State<Arc<dyn Env>>, | ||
cfg: &State<Config>, | ||
) -> Result<Json<SignAndSubmitResponse>, (Status, String)> { | ||
// Grab the default transaction signer. | ||
let signer = env.signer(); | ||
|
||
let opts = SubmitTxOpts { | ||
encrypt: body.encrypt, | ||
..Default::default() | ||
}; | ||
|
||
// Deserialize the passed transaction, depending on its kind. | ||
let tx = match body.into_inner().tx { | ||
Transaction::Std(data) => { | ||
cbor::from_slice(&data).map_err(|err| (Status::BadRequest, err.to_string()))? | ||
} | ||
Transaction::Eth { | ||
gas_limit, | ||
to, | ||
value, | ||
data, | ||
} => { | ||
let (method, body) = if to.is_empty() { | ||
// Create. | ||
( | ||
"evm.Create", | ||
cbor::to_value(evm::types::Create { | ||
value: value.into(), | ||
init_code: data, | ||
}), | ||
) | ||
} else { | ||
// Call. | ||
let address = to | ||
.as_slice() | ||
.try_into() | ||
.map_err(|_| (Status::BadRequest, "malformed address".to_string()))?; | ||
|
||
( | ||
"evm.Call", | ||
cbor::to_value(evm::types::Call { | ||
address, | ||
value: value.into(), | ||
data, | ||
}), | ||
) | ||
}; | ||
|
||
transaction::Transaction { | ||
version: transaction::LATEST_TRANSACTION_VERSION, | ||
call: transaction::Call { | ||
format: transaction::CallFormat::Plain, | ||
method: method.to_owned(), | ||
body, | ||
..Default::default() | ||
}, | ||
auth_info: transaction::AuthInfo { | ||
fee: transaction::Fee { | ||
gas: gas_limit, | ||
..Default::default() | ||
}, | ||
..Default::default() | ||
}, | ||
} | ||
} | ||
}; | ||
|
||
// Check if the method is authorised before signing. | ||
if tx.call.format != transaction::CallFormat::Plain { | ||
// Prevent bypassing the authorization check by encrypting the method name. | ||
return Err(( | ||
Status::BadRequest, | ||
"use the encrypt flag for encryption".to_string(), | ||
)); | ||
} | ||
if !cfg.allowed_methods.contains(&tx.call.method) { | ||
return Err(( | ||
Status::BadRequest, | ||
"transaction method not allowed".to_string(), | ||
)); | ||
} | ||
|
||
// Sign and submit transaction. | ||
let result = env | ||
.sign_and_submit_tx(signer, tx, opts) | ||
.await | ||
.map_err(|err| (Status::BadRequest, err.to_string()))?; | ||
|
||
// Encode the response. | ||
let response = SignAndSubmitResponse { | ||
data: cbor::to_vec(result), | ||
}; | ||
|
||
Ok(Json(response)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,56 @@ | ||
use oasis_runtime_sdk::modules::rofl::app::prelude::*; | ||
use oasis_runtime_sdk::{ | ||
crypto::signature::Signer, | ||
modules::rofl::app::{client::SubmitTxOpts, prelude::*}, | ||
types::transaction, | ||
}; | ||
|
||
/// ROFL app environment. | ||
#[async_trait] | ||
pub trait Env: Send + Sync { | ||
/// ROFL app identifier of the running application. | ||
fn app_id(&self) -> AppId; | ||
|
||
/// Transaction signer. | ||
fn signer(&self) -> Arc<dyn Signer>; | ||
|
||
/// Sign a given transaction, submit it and wait for block inclusion. | ||
async fn sign_and_submit_tx( | ||
&self, | ||
signer: Arc<dyn Signer>, | ||
tx: transaction::Transaction, | ||
opts: SubmitTxOpts, | ||
) -> Result<transaction::CallResult>; | ||
} | ||
|
||
pub(crate) struct EnvImpl<A: App> { | ||
_env: Environment<A>, | ||
env: Environment<A>, | ||
} | ||
|
||
impl<A: App> EnvImpl<A> { | ||
pub fn new(env: Environment<A>) -> Self { | ||
Self { _env: env } | ||
Self { env } | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl<A: App> Env for EnvImpl<A> { | ||
fn app_id(&self) -> AppId { | ||
A::id() | ||
} | ||
|
||
fn signer(&self) -> Arc<dyn Signer> { | ||
self.env.signer() | ||
} | ||
|
||
async fn sign_and_submit_tx( | ||
&self, | ||
signer: Arc<dyn Signer>, | ||
tx: transaction::Transaction, | ||
opts: SubmitTxOpts, | ||
) -> Result<transaction::CallResult> { | ||
self.env | ||
.client() | ||
.multi_sign_and_submit_tx_opts(&[signer], tx, opts) | ||
.await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
[package] | ||
name = "rofl-containers" | ||
version = "0.3.5" | ||
version = "0.4.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters