Skip to content

Commit a8e02c7

Browse files
elizabethengelmanfnandojanewang
authored
Add stellar tx fetch events cmd (#2157)
* Add stellar tx fetch events cmd * Add text output options for tx events --------- Co-authored-by: Nando Vieira <me@fnando.com> Co-authored-by: Jane Wang <jane.wang@stellar.org>
1 parent eb71aed commit a8e02c7

5 files changed

Lines changed: 262 additions & 8 deletions

File tree

FULL_HELP_DOCS.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3241,6 +3241,7 @@ Fetch a transaction from the network by hash If no subcommand is passed in, the
32413241
- `result` — Fetch the transaction result
32423242
- `meta` — Fetch the transaction meta
32433243
- `fee` — Fetch the transaction fee information
3244+
- `events` — Fetch the transaction events
32443245

32453246
###### **Options:**
32463247

@@ -3254,8 +3255,8 @@ Fetch a transaction from the network by hash If no subcommand is passed in, the
32543255
Default value: `json`
32553256

32563257
Possible values:
3257-
- `json`: JSON output of the ledger entry with parsed XDRs (one line, not formatted)
3258-
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
3258+
- `json`: JSON output with parsed XDRs (one line, not formatted)
3259+
- `json-formatted`: Formatted (multiline) JSON output with parsed XDRs
32593260
- `xdr`: Original RPC output (containing XDRs)
32603261

32613262
## `stellar tx fetch result`
@@ -3276,8 +3277,8 @@ Fetch the transaction result
32763277
Default value: `json`
32773278

32783279
Possible values:
3279-
- `json`: JSON output of the ledger entry with parsed XDRs (one line, not formatted)
3280-
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
3280+
- `json`: JSON output with parsed XDRs (one line, not formatted)
3281+
- `json-formatted`: Formatted (multiline) JSON output with parsed XDRs
32813282
- `xdr`: Original RPC output (containing XDRs)
32823283

32833284
## `stellar tx fetch meta`
@@ -3298,8 +3299,8 @@ Fetch the transaction meta
32983299
Default value: `json`
32993300

33003301
Possible values:
3301-
- `json`: JSON output of the ledger entry with parsed XDRs (one line, not formatted)
3302-
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
3302+
- `json`: JSON output with parsed XDRs (one line, not formatted)
3303+
- `json-formatted`: Formatted (multiline) JSON output with parsed XDRs
33033304
- `xdr`: Original RPC output (containing XDRs)
33043305

33053306
## `stellar tx fetch fee`
@@ -3324,6 +3325,28 @@ Fetch the transaction fee information
33243325
- `json-formatted`: Formatted (multiline) JSON output of the ledger entry with parsed XDRs
33253326
- `table`: Formatted in a table comparing fee types
33263327

3328+
## `stellar tx fetch events`
3329+
3330+
Fetch the transaction events
3331+
3332+
**Usage:** `stellar tx fetch events [OPTIONS] --hash <HASH>`
3333+
3334+
###### **Options:**
3335+
3336+
- `--hash <HASH>` — Transaction hash to fetch
3337+
- `--rpc-url <RPC_URL>` — RPC server endpoint
3338+
- `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
3339+
- `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
3340+
- `-n`, `--network <NETWORK>` — Name of network to use from config
3341+
- `--output <OUTPUT>` — Format of the output
3342+
3343+
Default value: `json`
3344+
3345+
Possible values:
3346+
- `json`: JSON output of the events with parsed XDRs (one line, not formatted)
3347+
- `json-formatted`: Formatted (multiline) JSON output of events with parsed XDRs
3348+
- `text`: Human readable event output with parsed XDRs
3349+
33273350
## `stellar tx decode`
33283351

33293352
Decode a transaction envelope from XDR to JSON

cmd/crates/soroban-test/tests/it/integration/tx/fetch.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use soroban_cli::{
22
commands::tx::fetch::fee::FeeTable,
3+
commands::tx::fetch::GetTransactionEvents,
34
utils::transaction_hash,
45
xdr::{
56
Limits, ReadXdr, TransactionEnvelope, TransactionMeta, TransactionResult,
@@ -284,6 +285,92 @@ async fn tx_fetch_fee() {
284285
);
285286
}
286287

288+
#[tokio::test]
289+
async fn tx_fetch_events() {
290+
let sandbox = &TestEnv::new();
291+
let test_account_alias = "test";
292+
let contract_id = deploy_contract(
293+
sandbox,
294+
HELLO_WORLD,
295+
DeployOptions {
296+
deployer: Some(test_account_alias.to_string()),
297+
..Default::default()
298+
},
299+
)
300+
.await;
301+
302+
let tx_xdr = sandbox
303+
.new_assert_cmd("contract")
304+
.arg("invoke")
305+
.arg("--build-only")
306+
.arg("--id")
307+
.arg(contract_id.clone())
308+
.arg("--network")
309+
.arg("local")
310+
.arg("--")
311+
.arg("log")
312+
.arg("--str")
313+
.arg("hi")
314+
.assert()
315+
.success()
316+
.stdout_as_str();
317+
318+
let tx_simulated = sandbox
319+
.new_assert_cmd("tx")
320+
.arg("simulate")
321+
.write_stdin(tx_xdr.as_bytes())
322+
.assert()
323+
.success()
324+
.stdout_as_str();
325+
326+
let signed = sandbox
327+
.new_assert_cmd("tx")
328+
.arg("sign")
329+
.arg("--sign-with-key")
330+
.arg("test")
331+
.write_stdin(tx_simulated.as_bytes())
332+
.assert()
333+
.success()
334+
.stdout_as_str();
335+
336+
sandbox
337+
.new_assert_cmd("tx")
338+
.arg("send")
339+
.write_stdin(signed.as_bytes())
340+
.assert()
341+
.success()
342+
.stdout_as_str();
343+
344+
let tx_env = TransactionEnvelope::from_xdr_base64(signed.clone(), Limits::none()).unwrap();
345+
let tx = if let TransactionEnvelope::Tx(env) = tx_env {
346+
env.tx
347+
} else {
348+
panic!("Expected TransactionEnvelope::Tx, got something else");
349+
};
350+
351+
let tx_hash = hex::encode(transaction_hash(&tx, &sandbox.network.network_passphrase).unwrap());
352+
353+
// fetch the tx events
354+
let output = sandbox
355+
.new_assert_cmd("tx")
356+
.arg("fetch")
357+
.arg("events")
358+
.arg("--hash")
359+
.arg(&tx_hash)
360+
.arg("--network")
361+
.arg("local")
362+
.arg("--output")
363+
.arg("json")
364+
.assert()
365+
.success()
366+
.stdout_as_str();
367+
368+
let parsed: GetTransactionEvents = serde_json::from_str(&output).unwrap();
369+
assert!(parsed.diagnostic_events.is_empty());
370+
assert_eq!(parsed.contract_events.len(), 1);
371+
assert_eq!(parsed.transaction_events.len(), 2);
372+
}
373+
287374
async fn add_account_data(
288375
sandbox: &TestEnv,
289376
account_alias: &str,

cmd/soroban-cli/src/commands/tx/fetch/args.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ pub enum Error {
3333

3434
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
3535
pub enum OutputFormat {
36-
/// JSON output of the ledger entry with parsed XDRs (one line, not formatted)
36+
/// JSON output with parsed XDRs (one line, not formatted)
3737
#[default]
3838
Json,
39-
/// Formatted (multiline) JSON output of the ledger entry with parsed XDRs
39+
/// Formatted (multiline) JSON output with parsed XDRs
4040
JsonFormatted,
4141
/// Original RPC output (containing XDRs)
4242
Xdr,
@@ -70,5 +70,6 @@ impl Args {
7070
if let Some(ledger) = tx.ledger {
7171
println!("Transaction Ledger: {ledger}");
7272
}
73+
println!();
7374
}
7475
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use crate::{commands::global, xdr};
2+
use clap::{command, Parser};
3+
4+
use super::args;
5+
6+
#[derive(Parser, Debug, Clone)]
7+
#[group(skip)]
8+
pub struct Cmd {
9+
#[command(flatten)]
10+
args: args::Args,
11+
12+
/// Format of the output
13+
#[arg(long, default_value = "json")]
14+
output: EventsOutputFormat,
15+
}
16+
17+
#[derive(thiserror::Error, Debug)]
18+
pub enum Error {
19+
#[error(transparent)]
20+
Serde(#[from] serde_json::Error),
21+
#[error(transparent)]
22+
Xdr(#[from] xdr::Error),
23+
#[error(transparent)]
24+
Args(#[from] args::Error),
25+
}
26+
27+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum, Default)]
28+
pub enum EventsOutputFormat {
29+
/// JSON output of the events with parsed XDRs (one line, not formatted)
30+
Json,
31+
/// Formatted (multiline) JSON output of events with parsed XDRs
32+
JsonFormatted,
33+
/// Human readable event output with parsed XDRs
34+
#[default]
35+
Text,
36+
}
37+
38+
impl Cmd {
39+
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
40+
let resp = self.args.fetch_transaction(global_args).await?;
41+
let events = &resp.events;
42+
let contract_events: &Vec<Vec<xdr::ContractEvent>> = &events.contract_events;
43+
let diagnostic_events = &events.diagnostic_events;
44+
let transaction_events = &events.transaction_events;
45+
match self.output {
46+
EventsOutputFormat::Text => {
47+
args::Args::print_tx_summary(&resp);
48+
Self::print_contract_events(contract_events);
49+
Self::print_transaction_events(transaction_events);
50+
Self::print_diagnostic_events(diagnostic_events);
51+
}
52+
EventsOutputFormat::JsonFormatted => {
53+
args::Args::print_tx_summary(&resp);
54+
println!("{}", serde_json::to_string_pretty(&events)?);
55+
}
56+
EventsOutputFormat::Json => {
57+
println!("{}", serde_json::to_string(&events)?);
58+
}
59+
}
60+
Ok(())
61+
}
62+
63+
fn get_sc_val_string(val: &xdr::ScVal) -> String {
64+
match val {
65+
xdr::ScVal::Symbol(sym) => {
66+
format!("Symbol: {:?}", sym.to_string())
67+
}
68+
xdr::ScVal::Address(addr) => {
69+
format!("Address: {:?}", addr.to_string())
70+
}
71+
xdr::ScVal::I128(val) => {
72+
format!("I128: {:?}", val.to_string())
73+
}
74+
other => {
75+
format!("Other: {other:?}")
76+
}
77+
}
78+
}
79+
80+
fn print_contract_event(event: &xdr::ContractEvent) {
81+
if let Some(id) = event.contract_id.as_ref() {
82+
println!(" Contract Id: {id}");
83+
}
84+
85+
match &event.body {
86+
xdr::ContractEventBody::V0(body) => {
87+
for (i, topic) in body.topics.iter().enumerate() {
88+
println!(" Topic[{i}]: {}", Self::get_sc_val_string(topic));
89+
}
90+
println!(" Data: {}", Self::get_sc_val_string(&body.data));
91+
}
92+
}
93+
}
94+
95+
fn print_contract_events(events: &[Vec<xdr::ContractEvent>]) {
96+
if events.is_empty() {
97+
println!("Contract Events: None");
98+
return;
99+
}
100+
println!("Contract Events:");
101+
for event in events.iter().flatten() {
102+
Self::print_contract_event(event);
103+
println!();
104+
}
105+
}
106+
107+
fn print_transaction_events(events: &Vec<xdr::TransactionEvent>) {
108+
if events.is_empty() {
109+
println!("Transaction Events: None");
110+
return;
111+
}
112+
println!("Transaction Events:");
113+
for event in events {
114+
println!(" Transaction State: {:?}", event.stage);
115+
Self::print_contract_event(&event.event);
116+
println!();
117+
}
118+
}
119+
120+
fn print_diagnostic_events(events: &Vec<xdr::DiagnosticEvent>) {
121+
if events.is_empty() {
122+
println!("Diagnostic Events: None");
123+
return;
124+
}
125+
126+
println!("Diagnostic Events:");
127+
for event in events {
128+
println!(
129+
" In Successful Contract Call: {:?}",
130+
event.in_successful_contract_call
131+
);
132+
Self::print_contract_event(&event.event);
133+
println!();
134+
}
135+
}
136+
}

cmd/soroban-cli/src/commands/tx/fetch/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use crate::{commands::global, config::network, xdr::Hash};
22
use clap::{command, Subcommand};
3+
pub use soroban_rpc::GetTransactionEvents;
34
use std::fmt::Debug;
45

56
mod args;
67
mod envelope;
8+
mod events;
79
pub mod fee;
810
mod meta;
911
mod result;
@@ -26,6 +28,8 @@ pub enum FetchCommands {
2628
Meta(meta::Cmd),
2729
/// Fetch the transaction fee information
2830
Fee(fee::Cmd),
31+
/// Fetch the transaction events
32+
Events(events::Cmd),
2933
/// Fetch the transaction envelope
3034
#[command(hide = true)]
3135
Envelope(envelope::Cmd),
@@ -56,6 +60,8 @@ pub enum Error {
5660
#[error(transparent)]
5761
Envelope(#[from] envelope::Error),
5862
#[error(transparent)]
63+
Events(#[from] events::Error),
64+
#[error(transparent)]
5965
NotSupported(#[from] fee::Error),
6066
#[error("the following required argument was not provided: {0}")]
6167
MissingArg(String),
@@ -68,6 +74,7 @@ impl Cmd {
6874
Some(FetchCommands::Meta(cmd)) => cmd.run(global_args).await?,
6975
Some(FetchCommands::Envelope(cmd)) => cmd.run(global_args).await?,
7076
Some(FetchCommands::Fee(cmd)) => cmd.run(global_args).await?,
77+
Some(FetchCommands::Events(cmd)) => cmd.run(global_args).await?,
7178
None => {
7279
envelope::Cmd {
7380
args: args::Args {

0 commit comments

Comments
 (0)