From f77e3393f7e6d769f1f13f4b451f4e226cdab227 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 3 Jan 2024 16:26:52 +0100 Subject: [PATCH] Fixed wrapper signing and added the writing module Author: satan Date: Wed Jan 3 16:26:52 2024 +0100 --- Cargo.lock | 1 + light_sdk/Cargo.toml | 3 +- light_sdk/src/reading/mod.rs | 26 +- light_sdk/src/transaction/account.rs | 115 ++++++++- light_sdk/src/transaction/bridge.rs | 44 +++- light_sdk/src/transaction/governance.rs | 73 +++++- light_sdk/src/transaction/ibc.rs | 44 +++- light_sdk/src/transaction/mod.rs | 52 +++- light_sdk/src/transaction/pgf.rs | 79 +++++- light_sdk/src/transaction/pos.rs | 310 +++++++++++++++++++++++- light_sdk/src/transaction/transfer.rs | 42 +++- light_sdk/src/transaction/wrapper.rs | 64 ----- light_sdk/src/writing/mod.rs | 57 ++++- 13 files changed, 826 insertions(+), 84 deletions(-) delete mode 100644 light_sdk/src/transaction/wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index 2d3065f4872..5413a2510bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4413,6 +4413,7 @@ dependencies = [ "namada_core", "namada_sdk", "prost 0.12.3", + "serde_json", "tendermint-config", "tendermint-rpc", "tokio", diff --git a/light_sdk/Cargo.toml b/light_sdk/Cargo.toml index 3e1a925f461..38659c92596 100644 --- a/light_sdk/Cargo.toml +++ b/light_sdk/Cargo.toml @@ -21,5 +21,6 @@ namada_core = {path = "../core"} namada_sdk = {path = "../sdk"} prost.workspace = true tendermint-config.workspace = true -tendermint-rpc = {worskpace = true, features = ["http-client"]} +tendermint-rpc = { workspace = true, features = ["http-client"] } tokio = {workspace = true, features = ["rt"]} +serde_json = "1.0.108" diff --git a/light_sdk/src/reading/mod.rs b/light_sdk/src/reading/mod.rs index 690ea074d45..ed727ab2ce4 100644 --- a/light_sdk/src/reading/mod.rs +++ b/light_sdk/src/reading/mod.rs @@ -4,7 +4,9 @@ use namada_core::ledger::storage::LastBlock; use namada_core::types::address::Address; use namada_core::types::storage::BlockResults; use namada_core::types::token; -use namada_sdk::error::Error; +use namada_core::types::token::DenominatedAmount; +use namada_sdk::error::{EncodingError, Error}; +use namada_sdk::io::StdIo; use namada_sdk::queries::RPC; use namada_sdk::rpc; use tendermint_config::net::Address as TendermintAddress; @@ -51,3 +53,25 @@ pub fn query_results( let rt = Runtime::new().unwrap(); rt.block_on(rpc::query_results(&client)) } + +/// Get a properly denominated amount of a token +pub fn denominate_amount( + tendermint_addr: &str, + amount: u64, + token: &str, +) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let token = Address::decode(token) + .map_err(|e| Error::Encode(EncodingError::Decoding(e.to_string())))?; + let rt = Runtime::new().unwrap(); + Ok(rt.block_on(rpc::denominate_amount( + &client, + &StdIo {}, + &token, + token::Amount::from(amount), + ))) +} diff --git a/light_sdk/src/transaction/account.rs b/light_sdk/src/transaction/account.rs index 146915881a7..343906d3c89 100644 --- a/light_sdk/src/transaction/account.rs +++ b/light_sdk/src/transaction/account.rs @@ -1,9 +1,12 @@ -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::key::common; +use namada_core::types::storage::Epoch; +use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -51,10 +54,46 @@ impl InitAccount { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to reveal a public key to the ledger to validate signatures of @@ -87,10 +126,46 @@ impl RevealPk { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to update the parameters of an established account @@ -136,8 +211,44 @@ impl UpdateAccount { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/bridge.rs b/light_sdk/src/transaction/bridge.rs index c41407f31e5..603ec366479 100644 --- a/light_sdk/src/transaction/bridge.rs +++ b/light_sdk/src/transaction/bridge.rs @@ -1,9 +1,13 @@ -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; +use namada_core::types::address::Address; pub use namada_core::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use namada_core::types::hash::Hash; use namada_core::types::key::common; +use namada_core::types::storage::Epoch; +use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; @@ -47,8 +51,44 @@ impl BridgeTransfer { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/governance.rs b/light_sdk/src/transaction/governance.rs index a3a37a6fd21..ce0db803974 100644 --- a/light_sdk/src/transaction/governance.rs +++ b/light_sdk/src/transaction/governance.rs @@ -1,12 +1,14 @@ use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::governance::storage::vote::ProposalVote; -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::key::common; use namada_core::types::storage::Epoch; +use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_INIT_PROPOSAL_WASM: &str = "tx_init_proposal.wasm"; @@ -62,10 +64,41 @@ impl InitProposal { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } } /// Transaction to vote on a governance proposal @@ -111,8 +144,44 @@ impl VoteProposal { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/ibc.rs b/light_sdk/src/transaction/ibc.rs index 98a28aa2262..eed7b2f90ff 100644 --- a/light_sdk/src/transaction/ibc.rs +++ b/light_sdk/src/transaction/ibc.rs @@ -2,12 +2,16 @@ use std::str::FromStr; pub use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer; use namada_core::ibc::primitives::Msg; -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; +use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::key::common; +use namada_core::types::storage::Epoch; use namada_core::types::time::DateTimeUtc; +use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_IBC_WASM: &str = "tx_ibc.wasm"; @@ -53,8 +57,44 @@ impl IbcTransfer { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/mod.rs b/light_sdk/src/transaction/mod.rs index 2c755da39f4..51f2cc77a45 100644 --- a/light_sdk/src/transaction/mod.rs +++ b/light_sdk/src/transaction/mod.rs @@ -1,11 +1,16 @@ +use std::collections::BTreeMap; use std::str::FromStr; use borsh::BorshSerialize; use namada_core::proto::{Section, Signature, Signer, Tx}; +use namada_core::types::address::Address; use namada_core::types::chain::ChainId; use namada_core::types::hash::Hash; use namada_core::types::key::common; +use namada_core::types::storage::Epoch; use namada_core::types::time::DateTimeUtc; +use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::{Fee, GasLimit}; pub mod account; pub mod bridge; @@ -14,7 +19,6 @@ pub mod ibc; pub mod pgf; pub mod pos; pub mod transfer; -pub mod wrapper; /// Generic arguments required to construct a transaction pub struct GlobalArgs { @@ -46,6 +50,17 @@ pub(in crate::transaction) fn get_sign_bytes(tx: &Tx) -> Vec { vec![tx.raw_header_hash()] } +pub(in crate::transaction) fn get_wrapper_sign_bytes(tx: &Tx) -> Hash { + let targets = tx.sechashes(); + // Commit to the given targets + let partial = Signature { + targets, + signer: Signer::PubKeys(vec![]), + signatures: BTreeMap::new(), + }; + partial.get_raw_hash() +} + pub(in crate::transaction) fn attach_raw_signatures( mut tx: Tx, signer: common::PublicKey, @@ -59,3 +74,38 @@ pub(in crate::transaction) fn attach_raw_signatures( })); tx } + +pub(in crate::transaction) fn attach_fee( + mut tx: Tx, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, +) -> Tx { + tx.add_wrapper( + Fee { + amount_per_gas_unit: fee, + token, + }, + fee_payer, + epoch, + gas_limit, + None, + ); + tx +} + +pub(in crate::transaction) fn attach_fee_signature( + mut tx: Tx, + signer: common::PublicKey, + signature: common::Signature, +) -> Tx { + tx.protocol_filter(); + tx.add_section(Section::Signature(Signature { + targets: tx.sechashes(), + signer: Signer::PubKeys(vec![signer]), + signatures: [(0, signature)].into_iter().collect(), + })); + tx +} diff --git a/light_sdk/src/transaction/pgf.rs b/light_sdk/src/transaction/pgf.rs index 7a53a7e4caf..93947084318 100644 --- a/light_sdk/src/transaction/pgf.rs +++ b/light_sdk/src/transaction/pgf.rs @@ -1,12 +1,15 @@ use std::collections::HashMap; -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; use namada_core::types::key::common; +use namada_core::types::storage::Epoch; +use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_RESIGN_STEWARD: &str = "tx_resign_steward.wasm"; @@ -41,10 +44,46 @@ impl ResignSteward { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to update a pgf steward's commission rate @@ -87,8 +126,44 @@ impl UpdateStewardCommission { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/pos.rs b/light_sdk/src/transaction/pos.rs index e042d80c5a7..547af0d019a 100644 --- a/light_sdk/src/transaction/pos.rs +++ b/light_sdk/src/transaction/pos.rs @@ -1,13 +1,15 @@ -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; use namada_core::types::key::{common, secp256k1}; +use namada_core::types::storage::Epoch; use namada_core::types::token; -use namada_core::types::token::Amount; +use namada_core::types::token::{Amount, DenominatedAmount}; use namada_core::types::transaction::pos::Redelegation; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_BOND_WASM: &str = "tx_bond.wasm"; @@ -67,6 +69,16 @@ impl Bond { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// An unbond transaction @@ -113,6 +125,16 @@ impl Unbond { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to initialize a new PoS validator @@ -179,6 +201,16 @@ impl BecomeValidator { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to unjail a PoS validator @@ -214,6 +246,16 @@ impl UnjailValidator { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to deactivate a validator @@ -249,6 +291,16 @@ impl DeactivateValidator { pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to reactivate a previously deactivated validator @@ -280,10 +332,46 @@ impl ReactivateValidator { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to claim PoS rewards @@ -324,10 +412,46 @@ impl ClaimRewards { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to change the validator's metadata @@ -380,10 +504,46 @@ impl ChangeMetaData { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to modify the validator's consensus key @@ -425,10 +585,46 @@ impl ChangeConsensusKey { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to modify the validator's commission rate @@ -466,10 +662,46 @@ impl ChangeCommission { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to withdraw previously unstaked funds @@ -510,10 +742,46 @@ impl Withdraw { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } /// Transaction to redelegate @@ -558,8 +826,44 @@ impl Redelegate { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/transfer.rs b/light_sdk/src/transaction/transfer.rs index cce45f1e919..c24f8043a87 100644 --- a/light_sdk/src/transaction/transfer.rs +++ b/light_sdk/src/transaction/transfer.rs @@ -1,11 +1,13 @@ use borsh_ext::BorshSerializeExt; -use namada_core::proto::Tx; +use namada_core::proto::{Signature, Tx, TxError}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::key::common; +use namada_core::types::storage::Epoch; use namada_core::types::token::DenominatedAmount; +use namada_core::types::transaction::GasLimit; -use super::GlobalArgs; +use super::{attach_fee, attach_fee_signature, GlobalArgs}; use crate::transaction; const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; @@ -57,8 +59,44 @@ impl Transfer { )) } + /// Attach the fee data to the tx + pub fn attach_fee( + self, + fee: DenominatedAmount, + token: Address, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + ) -> Self { + Self(attach_fee(self.0, fee, token, fee_payer, epoch, gas_limit)) + } + + /// Get the bytes of the fee data to sign + pub fn get_fee_sig_bytes(&self) -> Hash { + transaction::get_wrapper_sign_bytes(&self.0) + } + + /// Attach a signature of the fee to the tx + pub fn attach_fee_signature( + self, + signer: common::PublicKey, + signature: common::Signature, + ) -> Self { + Self(attach_fee_signature(self.0, signer, signature)) + } + /// Generates the protobuf encoding of this transaction pub fn to_bytes(&self) -> Vec { self.0.to_bytes() } + + /// Gets the inner transaction without the domain wrapper + pub fn payload(self) -> Tx { + self.0 + } + + /// Validate this wrapper transaction + pub fn validate_tx(&self) -> Result, TxError> { + self.0.validate_tx() + } } diff --git a/light_sdk/src/transaction/wrapper.rs b/light_sdk/src/transaction/wrapper.rs deleted file mode 100644 index eb4373a0c2d..00000000000 --- a/light_sdk/src/transaction/wrapper.rs +++ /dev/null @@ -1,64 +0,0 @@ -use namada_core::proto::{Section, Signature, Signer, Tx, TxError}; -use namada_core::types::hash::Hash; -use namada_core::types::key::common; -use namada_core::types::storage::Epoch; -use namada_core::types::transaction::{Fee, GasLimit}; - -#[allow(missing_docs)] -pub struct Wrapper(Tx); - -impl Wrapper { - /// Takes a transaction and a signature and wraps them in a wrapper - /// transaction ready for submission - pub fn new( - mut tx: Tx, - fee: Fee, - fee_payer: common::PublicKey, - gas_limit: GasLimit, - // FIXME: fix masp unshielding - unshield_hash: Option, - ) -> Self { - tx.add_wrapper( - fee, - fee_payer, - Epoch::default(), - gas_limit, - unshield_hash, - ); - - Self(tx) - } - - /// Returns the message to be signed for this transaction - pub fn get_sign_bytes(mut self) -> (Self, Vec) { - self.0.protocol_filter(); - let msg = self.0.sechashes(); - - (self, msg) - } - - /// Attach the given outer signature to the transaction - pub fn attach_signature( - mut self, - signer: common::PublicKey, - signature: common::Signature, - ) -> Self { - self.0.add_section(Section::Signature(Signature { - targets: self.0.sechashes(), - signer: Signer::PubKeys(vec![signer]), - signatures: [(0, signature)].into_iter().collect(), - })); - - self - } - - /// Validate this wrapper transaction - pub fn validate_tx(&self) -> Result, TxError> { - self.0.validate_tx() - } - - /// Generates the protobuf encoding of this transaction - pub fn to_bytes(&self) -> Vec { - self.0.to_bytes() - } -} diff --git a/light_sdk/src/writing/mod.rs b/light_sdk/src/writing/mod.rs index ac809ebf48f..5da31ea62a9 100644 --- a/light_sdk/src/writing/mod.rs +++ b/light_sdk/src/writing/mod.rs @@ -1,3 +1,56 @@ -pub mod blocking {} +use std::str::FromStr; -pub mod unblocking {} +use namada_core::proto::Tx; +use namada_sdk::error::{EncodingError, Error, TxError}; +use namada_sdk::queries::Client; +use tendermint_config::net::Address as TendermintAddress; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::error::Error as RpcError; +use tendermint_rpc::HttpClient; +use tokio::runtime::Runtime; + +/// Broadcast a transaction to be included in the blockchain. This +/// +/// Checks that +/// 1. The tx has been successfully included into the mempool of a validator +/// 2. The tx with encrypted payload has been included on the blockchain +/// 3. The decrypted payload of the tx has been included on the blockchain. +/// +/// In the case of errors in any of those stages, an error message is returned +pub fn broadcast_tx(tendermint_addr: &str, tx: Tx) -> Result { + let client = HttpClient::new( + TendermintAddress::from_str(tendermint_addr) + .map_err(|e| Error::Other(e.to_string()))?, + ) + .map_err(|e| Error::Other(e.to_string()))?; + let rt = Runtime::new().unwrap(); + + let wrapper_tx_hash = tx.header_hash().to_string(); + // We use this to determine when the decrypted inner tx makes it + // on-chain + let decrypted_tx_hash = tx.raw_header_hash().to_string(); + + // TODO: configure an explicit timeout value? we need to hack away at + // `tendermint-rs` for this, which is currently using a hard-coded 30s + // timeout. + let response = rt + .block_on(client.broadcast_tx_sync(tx.to_bytes())) + .map_err(|e| Error::from(TxError::TxBroadcast(e)))?; + + if response.code == 0.into() { + println!("Transaction added to mempool: {:?}", response); + // Print the transaction identifiers to enable the extraction of + // acceptance/application results later + { + println!("Wrapper transaction hash: {:?}", wrapper_tx_hash); + println!("Inner transaction hash: {:?}", decrypted_tx_hash); + } + Ok(response) + } else { + Err(Error::from(TxError::TxBroadcast(RpcError::server( + serde_json::to_string(&response).map_err(|err| { + Error::from(EncodingError::Serde(err.to_string())) + })?, + )))) + } +}