Add tx broadcast machinery

This commit is contained in:
hu55a1n1 2024-02-26 16:15:42 -08:00
parent c3e9af7ce9
commit e0ce71de3f
5 changed files with 816 additions and 98 deletions

719
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,12 +5,20 @@ edition = "2021"
[dependencies]
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"
displaydoc = { version = "0.2.3", default-features = false }
ecies = "0.2.6"
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.94"
subtle-encoding = { version = "0.5.1", features = ["bech32-preview"] }
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"] }
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" }

View file

@ -1,10 +1,53 @@
use std::path::PathBuf;
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;
#[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)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// RPC server address
#[clap(long, default_value = "http://localhost:11090")]
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())
}

View file

@ -3,14 +3,43 @@ mod cli;
use std::{
error::Error,
fs::{read_to_string, File},
io::Write,
io::{Read, Write},
process::Command,
};
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_relayer::types::InstantiateResponse;
use quartz_tee_ra::IASReport;
use serde_json::{json, Value};
use subtle_encoding::base64;
use tendermint::public_key::Secp256k1 as TmPublicKey;
use crate::cli::Cli;
@ -18,16 +47,111 @@ use crate::cli::Cli;
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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: 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(&quote)?;
println!(
"{}",
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(())
}

View file

@ -18,6 +18,10 @@ impl InstantiateResponse {
pub fn quote(&self) -> &[u8] {
&self.message.quote
}
pub fn into_message(self) -> InstantiateResponseMsg {
self.message
}
}
impl TryFrom<RawInstantiateResponse> for InstantiateResponse {
@ -47,6 +51,12 @@ pub struct InstantiateResponseMsg {
quote: Vec<u8>,
}
impl InstantiateResponseMsg {
pub fn into_tuple(self) -> (Config, Vec<u8>) {
(self.config, self.quote)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RawInstantiateResponseMsg {
config: RawConfig,