Skip to content

Commit

Permalink
fix: only submit invoke transaction if required
Browse files Browse the repository at this point in the history
  • Loading branch information
willemneal committed Nov 7, 2023
1 parent 9259e08 commit e0e104e
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![no_std]
#![allow(clippy::ignored_unit_patterns)]
use soroban_sdk::{
contract, contracterror, contractimpl, contracttype, symbol_short, vec, Address, Bytes, BytesN,
Env, Map, String, Symbol, Val, Vec, I256, U256,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![no_std]
#![allow(clippy::ignored_unit_patterns)]
use soroban_sdk::{contract, contractimpl, contracttype, Vec};

#[contracttype]
Expand Down
10 changes: 9 additions & 1 deletion cmd/soroban-cli/src/commands/contract/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,15 @@ impl Cmd {
&key,
)?;
client
.prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None)
.prepare_and_send_transaction(
&tx,
&key,
&[],
&network.network_passphrase,
None,
None,
true,
)
.await?;
Ok(stellar_strkey::Contract(contract_id.0).to_string())
}
Expand Down
20 changes: 16 additions & 4 deletions cmd/soroban-cli/src/commands/contract/extend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,27 @@ impl Cmd {
}),
};

let (result, meta, events) = client
.prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None)
let res = client
.prepare_and_send_transaction(
&tx,
&key,
&[],
&network.network_passphrase,
None,
None,
true,
)
.await?;

tracing::trace!(?result);
tracing::trace!(?meta);
let events = res.events()?;
if !events.is_empty() {
tracing::info!("Events:\n {events:#?}");
}
let meta = res
.as_signed()?
.result_meta
.as_ref()
.ok_or(Error::MissingOperationResult)?;

// The transaction from core will succeed regardless of whether it actually found & extended
// the entry, so we have to inspect the result meta to tell if it worked or not.
Expand Down
15 changes: 7 additions & 8 deletions cmd/soroban-cli/src/commands/contract/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,22 @@ impl Cmd {
build_install_contract_code_tx(contract, sequence + 1, self.fee.fee, &key)?;

// Currently internal errors are not returned if the contract code is expired
if let (
TransactionResult {
result: TransactionResultResult::TxInternalError,
..
},
_,
_,
) = client
if let Some(TransactionResult {
result: TransactionResultResult::TxInternalError,
..
}) = client
.prepare_and_send_transaction(
&tx_without_preflight,
&key,
&[],
&network.network_passphrase,
None,
None,
true,
)
.await?
.as_signed()?
.result
{
// Now just need to restore it and don't have to install again
restore::Cmd {
Expand Down
17 changes: 4 additions & 13 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ impl Cmd {
&key,
)?;

let (result, meta, events) = client
let res = client
.prepare_and_send_transaction(
&tx,
&key,
Expand All @@ -309,20 +309,11 @@ impl Cmd {
Some(log_events),
(global_args.verbose || global_args.very_verbose || self.cost)
.then_some(log_resources),
false,
)
.await?;

tracing::debug!(?result);
crate::log::diagnostic_events(&events, tracing::Level::INFO);
let xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
..
}) = meta
else {
return Err(Error::MissingOperationResult);
};

output_to_string(&spec, &return_value, &function)
crate::log::diagnostic_events(&res.events()?, tracing::Level::INFO);
output_to_string(&spec, &res.return_value()?, &function)
}

pub fn read_wasm(&self) -> Result<Option<Vec<u8>>, Error> {
Expand Down
21 changes: 17 additions & 4 deletions cmd/soroban-cli/src/commands/contract/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,24 @@ impl Cmd {
}),
};

let (result, meta, events) = client
.prepare_and_send_transaction(&tx, &key, &[], &network.network_passphrase, None, None)
let res = client
.prepare_and_send_transaction(
&tx,
&key,
&[],
&network.network_passphrase,
None,
None,
true,
)
.await?;

tracing::trace!(?result);
let meta = res
.as_signed()?
.result_meta
.as_ref()
.ok_or(Error::MissingOperationResult)?;
let events = res.events()?;
tracing::trace!(?meta);
if !events.is_empty() {
tracing::info!("Events:\n {events:#?}");
Expand All @@ -177,7 +190,7 @@ impl Cmd {
operations[0].changes.len()
);
}
parse_operations(&operations).ok_or(Error::MissingOperationResult)
parse_operations(operations).ok_or(Error::MissingOperationResult)
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/lab/token/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ impl Cmd {
)?;

client
.prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None)
.prepare_and_send_transaction(&tx, &key, &[], network_passphrase, None, None, true)
.await?;

Ok(stellar_strkey::Contract(contract_id.0).to_string())
Expand Down
32 changes: 17 additions & 15 deletions cmd/soroban-cli/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ pub enum Error {
LargeFee(u64),
#[error("Cannot authorize raw transactions")]
CannotAuthorizeRawTransaction,

#[error("Missing result for tnx")]
MissingOp,
#[error("A simulation is not a transaction")]
NotSignedTransaction,
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
Expand Down Expand Up @@ -623,10 +628,7 @@ soroban config identity fund {address} --helper-url <url>"#
}
}

pub async fn send_transaction(
&self,
tx: &TransactionEnvelope,
) -> Result<(TransactionResult, TransactionMeta, Vec<DiagnosticEvent>), Error> {
pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result<txn::Finished, Error> {
let client = self.client()?;
tracing::trace!("Sending:\n{tx:#?}");
let SendTransactionResponse {
Expand Down Expand Up @@ -665,14 +667,8 @@ soroban config identity fund {address} --helper-url <url>"#
"SUCCESS" => {
// TODO: the caller should probably be printing this
tracing::trace!("{response:#?}");
let GetTransactionResponse {
result,
result_meta,
..
} = response;
let meta = result_meta.ok_or(Error::MissingResult)?;
let events = extract_events(&meta);
return Ok((result.ok_or(Error::MissingResult)?, meta, events));

return Ok(txn::Finished::signed(response));
}
"FAILED" => {
tracing::error!("{response:#?}");
Expand Down Expand Up @@ -716,6 +712,7 @@ soroban config identity fund {address} --helper-url <url>"#
}
}

#[allow(clippy::too_many_arguments)]
pub async fn prepare_and_send_transaction(
&self,
tx_without_preflight: &Transaction,
Expand All @@ -724,7 +721,8 @@ soroban config identity fund {address} --helper-url <url>"#
network_passphrase: &str,
log_events: Option<LogEvents>,
log_resources: Option<LogResources>,
) -> Result<(TransactionResult, TransactionMeta, Vec<DiagnosticEvent>), Error> {
always_submit: bool,
) -> Result<txn::Finished, Error> {
let txn = txn::Assembled::new(tx_without_preflight, self).await?;
let seq_num = txn.sim_res().latest_ledger + 60; //5 min;
let authorized = txn
Expand All @@ -733,8 +731,12 @@ soroban config identity fund {address} --helper-url <url>"#
.authorize(self, source_key, signers, seq_num, network_passphrase)
.await?;
authorized.log(log_events, log_resources)?;
let tx = authorized.sign(source_key, network_passphrase)?;
self.send_transaction(&tx).await
if always_submit || authorized.requires_auth() {
let tx = authorized.sign(source_key, network_passphrase)?;
self.send_transaction(&tx).await
} else {
Ok(authorized.finish_simulation())
}
}

pub async fn get_transaction(&self, tx_id: &str) -> Result<GetTransactionResponseRaw, Error> {
Expand Down
41 changes: 29 additions & 12 deletions cmd/soroban-cli/src/rpc/txn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use crate::rpc::{Client, Error, RestorePreamble, SimulateTransactionResponse};

use super::{LogEvents, LogResources};

mod finished;
pub use finished::*;

pub struct Assembled {
txn: Transaction,
sim_res: SimulateTransactionResponse,
Expand Down Expand Up @@ -155,6 +158,14 @@ impl Assembled {
}
Ok(())
}

pub fn requires_auth(&self) -> bool {
requires_auth(&self.txn)
}

pub fn finish_simulation(self) -> Finished {
Finished::simulated(self.sim_res)
}
}

// Apply the result of a simulateTransaction onto a transaction envelope, preparing it for
Expand Down Expand Up @@ -219,6 +230,20 @@ pub fn assemble(
Ok(tx)
}

fn requires_auth(txn: &Transaction) -> bool {
let [Operation {
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }),
..
}] = txn.operations.as_slice()
else {
return false;
};
matches!(
auth.get(0).map(|x| &x.root_invocation.function),
Some(&SorobanAuthorizedFunction::ContractFn(_))
)
}

// Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given
// transaction. If unable to sign, return an error.
fn sign_soroban_authorizations(
Expand All @@ -229,18 +254,10 @@ fn sign_soroban_authorizations(
network_passphrase: &str,
) -> Result<Option<Transaction>, Error> {
let mut tx = raw.clone();
let mut op = match tx.operations.as_slice() {
[op @ Operation {
body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }),
..
}] if matches!(
auth.get(0).map(|x| &x.root_invocation.function),
Some(&SorobanAuthorizedFunction::ContractFn(_))
) =>
{
op.clone()
}
_ => return Ok(None),
let mut op = if requires_auth(&tx) {
tx.operations[0].clone()
} else {
return Ok(None);
};

let Operation {
Expand Down
66 changes: 66 additions & 0 deletions cmd/soroban-cli/src/rpc/txn/finished.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use soroban_env_host::xdr::{self, DiagnosticEvent, ScVal};

use super::Error;
use crate::rpc::{extract_events, GetTransactionResponse, SimulateTransactionResponse};

pub enum Kind {
Simulated(SimulateTransactionResponse),
Signed(GetTransactionResponse),
}

pub struct Finished {
txn_res: Kind,
}

impl Finished {
pub fn simulated(txn_res: SimulateTransactionResponse) -> Self {
Self {
txn_res: Kind::Simulated(txn_res),
}
}

pub fn signed(txn_res: GetTransactionResponse) -> Self {
Self {
txn_res: Kind::Signed(txn_res),
}
}

pub fn return_value(&self) -> Result<ScVal, Error> {
match &self.txn_res {
Kind::Simulated(sim_res) => Ok(sim_res
.results()?
.get(0)
.ok_or(Error::MissingOp)?
.xdr
.clone()),
Kind::Signed(GetTransactionResponse {
result_meta:
Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
..
})),
..
}) => Ok(return_value.clone()),
Kind::Signed(_) => Err(Error::MissingOp),
}
}

pub fn events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
match &self.txn_res {
Kind::Simulated(sim_res) => sim_res.events(),
Kind::Signed(GetTransactionResponse {
result_meta: Some(meta),
..
}) => Ok(extract_events(meta)),
Kind::Signed(_) => Err(Error::MissingOp),
}
}

pub fn as_signed(&self) -> Result<&GetTransactionResponse, Error> {
if let Kind::Signed(res) = &self.txn_res {
Ok(res)
} else {
Err(Error::NotSignedTransaction)
}
}
}

0 comments on commit e0e104e

Please sign in to comment.