Skip to content

Commit

Permalink
feat: log transactions to a data file
Browse files Browse the repository at this point in the history
  • Loading branch information
willemneal committed Jan 18, 2024
1 parent 2c7da5b commit f721edd
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 15 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ tracing-appender = "0.2.2"
which = "4.4.0"
wasmparser = "0.90.0"
directories = "5.0.1"
ulid = { version = "1.1" }


# [patch."https://github.com/stellar/rs-soroban-env"]
Expand Down
1 change: 1 addition & 0 deletions cmd/crates/soroban-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl TestEnv {
.arg("--no-fund")
.assert();
std::env::set_var("SOROBAN_ACCOUNT", "test");
std::env::set_var("SOROBAN_INVOKE", "./.soroban");
Ok(this)
}

Expand Down
5 changes: 5 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ async fn invoke() {
handles_kebab_case(sandbox, id).await;
fetch(sandbox, id).await;
invoke_prng_u64_in_range_test(sandbox, id).await;

// Check in ./soroban/txn to see if there is at least one file
// It use use dir_read
let contents = std::fs::read_dir(sandbox.dir().join("soroban").join("txn")).unwrap();
assert!(contents.count() > 0);
}

fn invoke_hello_world(sandbox: &TestEnv, id: &str) {
Expand Down
4 changes: 4 additions & 0 deletions cmd/soroban-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ cargo_metadata = "0.15.4"
pathdiff = "0.2.1"
dotenvy = "0.15.7"
directories = { workspace = true }
# For unique identifiers
ulid.workspace = true
ulid.features = ["serde"]

# For hyper-tls
[target.'cfg(unix)'.dependencies]
openssl = { version = "0.10.55", features = ["vendored"] }
Expand Down
20 changes: 16 additions & 4 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use super::super::{
config::{self, locator},
events,
};
use crate::log;
use crate::{
commands::global,
rpc::{self, Client},
Expand Down Expand Up @@ -299,10 +300,11 @@ impl Cmd {
self.fee.fee,
&key,
)?;

let (result, meta, events) = client
.prepare_and_send_transaction(
&tx,
let txn = client.create_assembled_transaction(&tx).await?;
let sim_res = txn.sim_res().clone();
let ((result, meta, events), txn) = client
.send_assembled_transaction(
txn,
&key,
&signers,
&network.network_passphrase,
Expand All @@ -312,6 +314,16 @@ impl Cmd {
)
.await?;

let invoke_data = log::Invoke::new(
Some(sim_res.clone()),
&host_function_params,
txn,
None,
network.rpc_url.clone(),
)?;

log::to_file(invoke_data, "txn");

tracing::debug!(?result);
crate::log::diagnostic_events(&events, tracing::Level::INFO);
let xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
Expand Down
76 changes: 76 additions & 0 deletions cmd/soroban-cli/src/log.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
use directories::BaseDirs;
use heck::ToShoutySnakeCase;
use serde::Serialize;
use serde_derive::Deserialize;
use soroban_env_host::xdr;

pub mod auth;
pub mod budget;
pub mod cost;
Expand All @@ -11,3 +17,73 @@ pub use cost::*;
pub use diagnostic_event::*;
pub use footprint::*;
pub use host_event::*;

use crate::rpc::{self, SimulateTransactionResponse};

/// This represents the data of a single invoke.
/// The ulid is the unique identifier for each invoke and contains the timestamp.
/// The ulid crate has serde support
#[derive(Serialize, Debug, Deserialize)]
pub struct Invoke {
function_name: String,
contract_address: String,
args: Vec<xdr::ScVal>,
sim_res: Option<SimulateTransactionResponse>,
txn: xdr::TransactionEnvelope,
result: xdr::ScVal,
rpc_url: String,
ulid: ulid::Ulid,
}

impl Invoke {
pub fn new(
sim_res: Option<SimulateTransactionResponse>,
xdr::InvokeContractArgs {
function_name,
args,
contract_address,
}: &xdr::InvokeContractArgs,
txn: xdr::TransactionEnvelope,
response: Option<&crate::rpc::GetTransactionResponse>,
rpc_url: String,
) -> Result<Self, rpc::Error> {
let result = if let Some(res) = &sim_res {
res.results().unwrap().first().unwrap().xdr.clone()
} else if let Some(resp) = response {
resp.return_value()?
} else {
xdr::ScVal::Void
};

Ok(Self {
sim_res,
function_name: function_name.to_string(),
contract_address: contract_address.to_string(),
args: args.to_vec(),
txn,
result,
rpc_url,
ulid: ulid::Ulid::new(),
})
}
}

pub fn to_file(t: impl Serialize, name: &str) {
let env = std::env::var(format!("SOROBAN_${}", name.to_shouty_snake_case()))
.ok()
.map(std::path::PathBuf::from);

let bd = BaseDirs::new();
if let Some(parent) = env
.as_deref()
.or_else(|| bd.as_ref().map(BaseDirs::data_dir))
{
let parent = parent.join("soroban-cli");
let dir = parent.join(name);
std::fs::create_dir_all(&dir).unwrap();
let id = ulid::Ulid::new();
let mut file = dir.join(id.to_string());
file.set_extension("json");
serde_json::to_writer_pretty(std::fs::File::create(file).unwrap(), &t).unwrap();
}
}
106 changes: 95 additions & 11 deletions cmd/soroban-cli/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ use serde_aux::prelude::{
deserialize_option_number_from_string,
};
use soroban_env_host::xdr::{
self, AccountEntry, AccountId, ContractDataEntry, DiagnosticEvent, Error as XdrError,
LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount, Limited, PublicKey, ReadXdr,
SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData, Transaction,
TransactionEnvelope, TransactionMeta, TransactionMetaV3, TransactionResult, Uint256, VecM,
WriteXdr,
self, AccountEntry, AccountId, ContractDataEntry, ContractEventType, DiagnosticEvent,
Error as XdrError, LedgerEntryData, LedgerFootprint, LedgerKey, LedgerKeyAccount, Limited,
PublicKey, ReadXdr, SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData,
Transaction, TransactionEnvelope, TransactionMeta, TransactionMetaV3, TransactionResult,
Uint256, VecM, WriteXdr,
};
use soroban_sdk::token;
use soroban_sdk::xdr::Limits;
Expand All @@ -27,7 +27,9 @@ use tokio::time::sleep;

use crate::utils::contract_spec;

mod txn;
use self::txn::Assembled;

pub mod txn;

const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");

Expand Down Expand Up @@ -99,6 +101,8 @@ pub enum Error {
LargeFee(u64),
#[error("Cannot authorize raw transactions")]
CannotAuthorizeRawTransaction,
#[error("Missing result for tnx")]
MissingOp,
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
Expand Down Expand Up @@ -170,6 +174,51 @@ impl TryInto<GetTransactionResponse> for GetTransactionResponseRaw {
}
}

impl GetTransactionResponse {
pub fn return_value(&self) -> Result<xdr::ScVal, Error> {
if let Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
..
})) = self.result_meta.as_ref()
{
Ok(return_value.clone())
} else {
Err(Error::MissingOp)
}
}

pub fn events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
if let Some(meta) = self.result_meta.as_ref() {
Ok(extract_events(meta))
} else {
Err(Error::MissingOp)
}
}

pub fn filter_events(
&self,
f: impl Fn(&DiagnosticEvent) -> bool,
) -> Result<Vec<DiagnosticEvent>, Error> {
Ok(self
.events()?
.into_iter()
.filter(|e| f(e))
.collect::<Vec<_>>())
}

pub fn contract_events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
self.filter_events(|e| matches!(e.event.type_, ContractEventType::Contract))
}

pub fn system_events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
self.filter_events(|e| matches!(e.event.type_, ContractEventType::System))
}

pub fn diagnostic_event(&self) -> Result<Vec<DiagnosticEvent>, Error> {
self.filter_events(|e| matches!(e.event.type_, ContractEventType::Diagnostic))
}
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct LedgerEntryResult {
pub key: String,
Expand Down Expand Up @@ -213,7 +262,7 @@ pub struct GetLatestLedgerResponse {
pub sequence: u32,
}

#[derive(serde::Deserialize, serde::Serialize, Debug, Default)]
#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
pub struct Cost {
#[serde(
rename = "cpuInsns",
Expand All @@ -227,7 +276,7 @@ pub struct Cost {
pub mem_bytes: u64,
}

#[derive(serde::Deserialize, serde::Serialize, Debug)]
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
pub struct SimulateHostFunctionResultRaw {
#[serde(deserialize_with = "deserialize_default_from_null")]
pub auth: Vec<String>,
Expand All @@ -240,7 +289,7 @@ pub struct SimulateHostFunctionResult {
pub xdr: xdr::ScVal,
}

#[derive(serde::Deserialize, serde::Serialize, Debug, Default)]
#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
pub struct SimulateTransactionResponse {
#[serde(
rename = "minResourceFee",
Expand Down Expand Up @@ -309,7 +358,7 @@ impl SimulateTransactionResponse {
}
}

#[derive(serde::Deserialize, serde::Serialize, Debug, Default)]
#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
pub struct RestorePreamble {
#[serde(rename = "transactionData")]
pub transaction_data: String,
Expand Down Expand Up @@ -719,6 +768,34 @@ soroban config identity fund {address} --helper-url <url>"#
log_resources: Option<LogResources>,
) -> Result<(TransactionResult, TransactionMeta, Vec<DiagnosticEvent>), Error> {
let txn = txn::Assembled::new(tx_without_preflight, self).await?;
Ok(self
.send_assembled_transaction(
txn,
source_key,
signers,
network_passphrase,
log_events,
log_resources,
)
.await?
.0)
}

pub async fn send_assembled_transaction(
&self,
txn: Assembled,
source_key: &ed25519_dalek::SigningKey,
signers: &[ed25519_dalek::SigningKey],
network_passphrase: &str,
log_events: Option<LogEvents>,
log_resources: Option<LogResources>,
) -> Result<
(
(TransactionResult, TransactionMeta, Vec<DiagnosticEvent>),
TransactionEnvelope,
),
Error,
> {
let seq_num = txn.sim_res().latest_ledger + 60; //5 min;
let authorized = txn
.handle_restore(self, source_key, network_passphrase)
Expand All @@ -727,7 +804,7 @@ soroban config identity fund {address} --helper-url <url>"#
.await?;
authorized.log(log_events, log_resources)?;
let tx = authorized.sign(source_key, network_passphrase)?;
self.send_transaction(&tx).await
Ok((self.send_transaction(&tx).await?, tx))
}

pub async fn get_transaction(&self, tx_id: &str) -> Result<GetTransactionResponseRaw, Error> {
Expand Down Expand Up @@ -755,6 +832,13 @@ soroban config identity fund {address} --helper-url <url>"#
.await?)
}

pub async fn create_assembled_transaction(
&self,
txn: &Transaction,
) -> Result<txn::Assembled, Error> {
txn::Assembled::new(txn, self).await
}

pub async fn get_full_ledger_entries(
&self,
ledger_keys: &[LedgerKey],
Expand Down

0 comments on commit f721edd

Please sign in to comment.