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]
|
||||
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" }
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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("e)?;
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue