Add tx broadcast machinery
This commit is contained in:
parent
c3e9af7ce9
commit
e0ce71de3f
5 changed files with 816 additions and 98 deletions
719
Cargo.lock
generated
719
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -5,12 +5,20 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.1.8", features = ["derive"] }
|
clap = { version = "4.1.8", features = ["derive"] }
|
||||||
|
cosmos-sdk-proto = "0.16.0"
|
||||||
|
cosmrs = { version = "=0.11.0", features = ["cosmwasm"] }
|
||||||
cosmwasm-std = "1.4.0"
|
cosmwasm-std = "1.4.0"
|
||||||
|
displaydoc = { version = "0.2.3", default-features = false }
|
||||||
|
ecies = "0.2.6"
|
||||||
serde = { version = "1.0.189", features = ["derive"] }
|
serde = { version = "1.0.189", features = ["derive"] }
|
||||||
serde_json = "1.0.94"
|
serde_json = "1.0.94"
|
||||||
|
subtle-encoding = { version = "0.5.1", features = ["bech32-preview"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
tonic = "0.11"
|
tendermint = { version = "0.29.0", features = ["secp256k1"] }
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
tonic = "=0.8.3"
|
||||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
||||||
quartz-cw = { git = "ssh://git@github.com/informalsystems/bisenzone-cw-mvp.git", branch = "hu55a1n1/11-use-quartz" }
|
quartz-cw = { path = "../../../bisenzone-cw-mvp/packages/quartz-cw" }
|
||||||
|
quartz-tee-ra = { path = "../../../bisenzone-cw-mvp/packages/quartz-tee-ra" }
|
||||||
quartz-proto = { path = "../../utils/quartz-proto" }
|
quartz-proto = { path = "../../utils/quartz-proto" }
|
||||||
|
|
|
@ -1,10 +1,53 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use cosmrs::{tendermint::chain::Id, AccountId};
|
||||||
|
use displaydoc::Display;
|
||||||
|
use subtle_encoding::{bech32::decode as bech32_decode, Error as Bech32DecodeError};
|
||||||
|
use thiserror::Error;
|
||||||
use tonic::transport::Endpoint;
|
use tonic::transport::Endpoint;
|
||||||
|
|
||||||
|
#[derive(Display, Error, Debug)]
|
||||||
|
pub enum AddressError {
|
||||||
|
/// Address is not bech32 encoded
|
||||||
|
NotBech32Encoded(#[source] Bech32DecodeError),
|
||||||
|
/// Human readable part mismatch (expected `wasm`, found {0})
|
||||||
|
HumanReadableMismatch(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// RPC server address
|
/// RPC server address
|
||||||
#[clap(long, default_value = "http://localhost:11090")]
|
#[clap(long, default_value = "http://localhost:11090")]
|
||||||
pub enclave_addr: Endpoint,
|
pub enclave_addr: Endpoint,
|
||||||
|
|
||||||
|
/// Blockchain node gRPC URL
|
||||||
|
#[arg(short, long, default_value = "tcp://127.0.0.1:9090")]
|
||||||
|
pub node_addr: Endpoint,
|
||||||
|
|
||||||
|
/// Chain-id of MTCS chain
|
||||||
|
#[arg(long, default_value = "testing")]
|
||||||
|
pub chain_id: Id,
|
||||||
|
|
||||||
|
/// Smart contract address
|
||||||
|
#[arg(short, long, value_parser = wasm_address)]
|
||||||
|
pub contract: AccountId,
|
||||||
|
|
||||||
|
/// Path to TSP secret key file
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub secret: PathBuf,
|
||||||
|
|
||||||
|
/// Gas limit for the set-offs submission transaction
|
||||||
|
#[arg(long, default_value = "900000000")]
|
||||||
|
pub gas_limit: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wasm_address(address_str: &str) -> Result<AccountId, AddressError> {
|
||||||
|
let (hr, _) = bech32_decode(address_str).map_err(AddressError::NotBech32Encoded)?;
|
||||||
|
if hr != "wasm" {
|
||||||
|
return Err(AddressError::HumanReadableMismatch(hr));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(address_str.parse().unwrap())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,43 @@ mod cli;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fs::{read_to_string, File},
|
fs::{read_to_string, File},
|
||||||
io::Write,
|
io::{Read, Write},
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use cosmos_sdk_proto::{
|
||||||
|
cosmos::{
|
||||||
|
auth::v1beta1::{
|
||||||
|
query_client::QueryClient as AuthQueryClient, BaseAccount as RawBaseAccount,
|
||||||
|
QueryAccountRequest,
|
||||||
|
},
|
||||||
|
tx::v1beta1::{service_client::ServiceClient, BroadcastMode, BroadcastTxRequest},
|
||||||
|
},
|
||||||
|
traits::Message,
|
||||||
|
Any,
|
||||||
|
};
|
||||||
|
use cosmrs::{
|
||||||
|
auth::BaseAccount,
|
||||||
|
cosmwasm::MsgExecuteContract,
|
||||||
|
crypto::secp256k1::{SigningKey, VerifyingKey},
|
||||||
|
tendermint::{account::Id as TmAccountId, chain::Id as TmChainId},
|
||||||
|
tx,
|
||||||
|
tx::{Fee, Msg, SignDoc, SignerInfo},
|
||||||
|
AccountId, Coin,
|
||||||
|
};
|
||||||
|
use ecies::{PublicKey, SecretKey};
|
||||||
|
use quartz_cw::msg::{
|
||||||
|
execute::attested::{Attested, EpidAttestation},
|
||||||
|
instantiate::{CoreInstantiate, RawInstantiate},
|
||||||
|
InstantiateMsg,
|
||||||
|
};
|
||||||
use quartz_proto::quartz::{core_client::CoreClient, InstantiateRequest};
|
use quartz_proto::quartz::{core_client::CoreClient, InstantiateRequest};
|
||||||
use quartz_relayer::types::InstantiateResponse;
|
use quartz_relayer::types::InstantiateResponse;
|
||||||
|
use quartz_tee_ra::IASReport;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
use subtle_encoding::base64;
|
||||||
|
use tendermint::public_key::Secp256k1 as TmPublicKey;
|
||||||
|
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
|
|
||||||
|
@ -18,16 +47,111 @@ use crate::cli::Cli;
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args = Cli::parse();
|
let args = Cli::parse();
|
||||||
|
|
||||||
let mut client = CoreClient::connect(args.enclave_addr).await?;
|
let mut client = CoreClient::connect(args.enclave_addr.uri().to_string()).await?;
|
||||||
let response = client.instantiate(InstantiateRequest {}).await?;
|
let response = client.instantiate(InstantiateRequest {}).await?;
|
||||||
let response: InstantiateResponse = response.into_inner().try_into()?;
|
let response: InstantiateResponse = response.into_inner().try_into()?;
|
||||||
|
let (config, quote) = response.into_message().into_tuple();
|
||||||
|
|
||||||
let ias_report = gramine_sgx_ias_report(response.quote())?;
|
let ias_report = gramine_sgx_ias_report("e)?;
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
serde_json::to_string(&ias_report).expect("infallible serializer")
|
serde_json::to_string(&ias_report).expect("infallible serializer")
|
||||||
);
|
);
|
||||||
|
let ias_report: IASReport = serde_json::from_str(&ias_report.to_string())?;
|
||||||
|
let mr_enclave = ias_report.report.isv_enclave_quote_body.mrenclave();
|
||||||
|
let user_data = ias_report.report.isv_enclave_quote_body.user_data();
|
||||||
|
let attestation = EpidAttestation::new(ias_report, mr_enclave, user_data);
|
||||||
|
|
||||||
|
let cw_instantiate_msg = Attested::new(CoreInstantiate::new(config), attestation);
|
||||||
|
|
||||||
|
// Read the TSP secret
|
||||||
|
let secret = {
|
||||||
|
let mut secret = Vec::new();
|
||||||
|
let mut tsp_sk_file = File::open(args.secret)?;
|
||||||
|
tsp_sk_file.read_to_end(secret.as_mut())?;
|
||||||
|
let secret = base64::decode(secret).unwrap();
|
||||||
|
SecretKey::parse_slice(&secret).unwrap()
|
||||||
|
};
|
||||||
|
let tm_pubkey = {
|
||||||
|
let pubkey = PublicKey::from_secret_key(&secret);
|
||||||
|
TmPublicKey::from_sec1_bytes(&pubkey.serialize()).unwrap()
|
||||||
|
};
|
||||||
|
let sender = {
|
||||||
|
let tm_key = TmAccountId::from(tm_pubkey);
|
||||||
|
AccountId::new("wasm", tm_key.as_bytes()).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let msgs = vec![MsgExecuteContract {
|
||||||
|
sender: sender.clone(),
|
||||||
|
contract: args.contract.clone(),
|
||||||
|
msg: serde_json::to_string::<RawInstantiate>(&InstantiateMsg(cw_instantiate_msg).into())?
|
||||||
|
.into_bytes(),
|
||||||
|
funds: vec![],
|
||||||
|
}
|
||||||
|
.to_any()
|
||||||
|
.unwrap()];
|
||||||
|
|
||||||
|
let account = account_info(args.node_addr.uri().clone(), sender.clone()).await?;
|
||||||
|
let amount = Coin {
|
||||||
|
amount: 0u128,
|
||||||
|
denom: "cosm".parse()?,
|
||||||
|
};
|
||||||
|
let tx_bytes = tx_bytes(
|
||||||
|
&secret,
|
||||||
|
amount,
|
||||||
|
args.gas_limit,
|
||||||
|
tm_pubkey,
|
||||||
|
msgs,
|
||||||
|
account.sequence,
|
||||||
|
account.account_number,
|
||||||
|
&args.chain_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
send_tx(args.node_addr.uri().clone(), tx_bytes).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn account_info(
|
||||||
|
node: impl ToString,
|
||||||
|
address: impl ToString,
|
||||||
|
) -> Result<BaseAccount, Box<dyn Error>> {
|
||||||
|
let mut client = AuthQueryClient::connect(node.to_string()).await?;
|
||||||
|
let request = tonic::Request::new(QueryAccountRequest {
|
||||||
|
address: address.to_string(),
|
||||||
|
});
|
||||||
|
let response = client.account(request).await?;
|
||||||
|
let response = RawBaseAccount::decode(response.into_inner().account.unwrap().value.as_slice())?;
|
||||||
|
let account = BaseAccount::try_from(response)?;
|
||||||
|
Ok(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn tx_bytes(
|
||||||
|
secret: &SecretKey,
|
||||||
|
amount: Coin,
|
||||||
|
gas: u64,
|
||||||
|
tm_pubkey: VerifyingKey,
|
||||||
|
msgs: Vec<Any>,
|
||||||
|
sequence_number: u64,
|
||||||
|
account_number: u64,
|
||||||
|
chain_id: &TmChainId,
|
||||||
|
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
|
let tx_body = tx::Body::new(msgs, "", 0u16);
|
||||||
|
let signer_info = SignerInfo::single_direct(Some(tm_pubkey.into()), sequence_number);
|
||||||
|
let auth_info = signer_info.auth_info(Fee::from_amount_and_gas(amount, gas));
|
||||||
|
let sign_doc = SignDoc::new(&tx_body, &auth_info, chain_id, account_number)?;
|
||||||
|
let tx_signed = sign_doc.sign(&SigningKey::from_bytes(&secret.serialize()).unwrap())?;
|
||||||
|
Ok(tx_signed.to_bytes()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_tx(node: impl ToString, tx_bytes: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut client = ServiceClient::connect(node.to_string()).await?;
|
||||||
|
let request = tonic::Request::new(BroadcastTxRequest {
|
||||||
|
tx_bytes,
|
||||||
|
mode: BroadcastMode::Block.into(),
|
||||||
|
});
|
||||||
|
let _response = client.broadcast_tx(request).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ impl InstantiateResponse {
|
||||||
pub fn quote(&self) -> &[u8] {
|
pub fn quote(&self) -> &[u8] {
|
||||||
&self.message.quote
|
&self.message.quote
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_message(self) -> InstantiateResponseMsg {
|
||||||
|
self.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<RawInstantiateResponse> for InstantiateResponse {
|
impl TryFrom<RawInstantiateResponse> for InstantiateResponse {
|
||||||
|
@ -47,6 +51,12 @@ pub struct InstantiateResponseMsg {
|
||||||
quote: Vec<u8>,
|
quote: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InstantiateResponseMsg {
|
||||||
|
pub fn into_tuple(self) -> (Config, Vec<u8>) {
|
||||||
|
(self.config, self.quote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct RawInstantiateResponseMsg {
|
pub struct RawInstantiateResponseMsg {
|
||||||
config: RawConfig,
|
config: RawConfig,
|
||||||
|
|
Loading…
Reference in a new issue