Skip to content

Commit 962430f

Browse files
feat: Add proper logging of simulation/extend/restore events (#1774)
* feat: Add proper logging of simulation/extend/restore events * feat: consolidate diagnostic event logging - Use tracing logs with DEBUG and TRACE for contract events and diagnostic events respectively. - Use Printer to display the events in a more human readable way. - Simulate once to determine if an invoke should send and use that result instead of simulating again * fix: logging test --------- Co-authored-by: Elizabeth Engelman <[email protected]>
1 parent 27c9ad2 commit 962430f

File tree

13 files changed

+177
-125
lines changed

13 files changed

+177
-125
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/crates/soroban-test/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ walkdir = "2.4.0"
4242
ulid.workspace = true
4343
ed25519-dalek = { workspace = true }
4444
hex = { workspace = true }
45+
tracing = "0.1.40"
46+
tracing-subscriber = "0.3.18"
4547
httpmock = { workspace = true }
4648

4749
[features]

cmd/crates/soroban-test/tests/it/integration/hello_world.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,9 @@ fn invoke_log(sandbox: &TestEnv, id: &str) {
402402
.assert()
403403
.success()
404404
.stderr(predicates::str::contains(
405-
"INFO contract_event: soroban_cli::log::event: 1:",
405+
r#"Event: [{"symbol":"hello"},{"symbol":""}] = {"symbol":"world"}"#,
406406
))
407-
.stderr(predicates::str::contains("hello"))
408407
.stderr(predicates::str::contains(
409-
"INFO log_event: soroban_cli::log::event: 2:",
410-
))
411-
.stderr(predicates::str::contains("hello {}"))
412-
.stderr(predicates::str::contains("world"));
408+
r#"Log: {"vec":[{"string":"hello {}"},{"symbol":"world"}]}"#,
409+
));
413410
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use soroban_cli::xdr::{self, ReadXdr};
2+
use std::sync::{Arc, Mutex};
3+
use tracing::{Event, Subscriber};
4+
use tracing_subscriber::layer::{Context, SubscriberExt};
5+
6+
struct TestSubscriber {
7+
logs: Arc<Mutex<Vec<String>>>,
8+
}
9+
10+
impl<S: Subscriber> tracing_subscriber::Layer<S> for TestSubscriber {
11+
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
12+
// Capture the event data
13+
let mut logs = self.logs.lock().unwrap();
14+
logs.push(format!("{event:?}"));
15+
}
16+
}
17+
18+
#[test]
19+
fn test_diagnostic_events_logging() {
20+
let logs = Arc::new(Mutex::new(Vec::new()));
21+
let subscriber = TestSubscriber { logs: logs.clone() };
22+
23+
tracing::subscriber::with_default(tracing_subscriber::registry().with(subscriber), || {
24+
let events = [
25+
"AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgfKvD/pIJPlRnGd3RKaBZSHfoq/nJbJSYxkVTScSbhuYAAAAPAAAABGRlY3IAAAAB",
26+
"AAAAAAAAAAAAAAABfKvD/pIJPlRnGd3RKaBZSHfoq/nJbJSYxkVTScSbhuYAAAACAAAAAAAAAAEAAAAPAAAAA2xvZwAAAAAQAAAAAQAAAAIAAAAOAAAACWNvdW50OiB7fQAAAAAAAAMAAAAA",
27+
"AAAAAAAAAAAAAAABfKvD/pIJPlRnGd3RKaBZSHfoq/nJbJSYxkVTScSbhuYAAAACAAAAAAAAAAIAAAAPAAAABWVycm9yAAAAAAAAAgAAAAEAAAAGAAAAEAAAAAEAAAACAAAADgAAACdWTSBjYWxsIHRyYXBwZWQ6IFVucmVhY2hhYmxlQ29kZVJlYWNoZWQAAAAADwAAAARkZWNy",
28+
].iter().map(|event| xdr::DiagnosticEvent::from_xdr_base64(event,xdr::Limits::none()).unwrap()).collect::<Vec<_>>();
29+
soroban_cli::log::event::all(&events);
30+
});
31+
32+
let captured_logs = logs.lock().unwrap();
33+
assert!(captured_logs.iter().any(|log| log.contains(r#"AAAAAAAAAAAAAAAAAAAAAgAAAAAAAAADAAAADwAAAAdmbl9jYWxsAAAAAA0AAAAgfKvD/pIJPlRnGd3RKaBZSHfoq/nJbJSYxkVTScSbhuYAAAAPAAAABGRlY3IAAAAB {"in_successful_contract_call":false,"event":{"ext":"v0","contract_id":null,"type_":"diagnostic","body":{"v0":{"topics":[{"symbol":"fn_call"},{"bytes":"7cabc3fe92093e546719ddd129a0594877e8abf9c96c9498c6455349c49b86e6"},{"symbol":"decr"}],"data":"void"}}}}"#)));
34+
assert!(captured_logs
35+
.iter()
36+
.any(|log| log.contains("VM call trapped")));
37+
}

cmd/crates/soroban-test/tests/it/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod help;
55
mod init;
66
// #[cfg(feature = "it")]
77
mod integration;
8+
mod log;
89
mod plugin;
910
mod rpc_provider;
1011
mod util;

cmd/soroban-cli/src/assembled.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ pub async fn simulate_and_assemble_transaction(
2323
signatures: VecM::default(),
2424
}))
2525
.await?;
26-
match sim_res.error {
27-
None => Ok(Assembled::new(tx, sim_res)?),
28-
Some(e) => {
29-
diagnostic_events(&sim_res.events, tracing::Level::ERROR);
30-
Err(Error::TransactionSimulationFailed(e))
31-
}
26+
tracing::trace!("{sim_res:#?}");
27+
if let Some(e) = &sim_res.error {
28+
crate::log::event::all(&sim_res.events()?);
29+
Err(Error::TransactionSimulationFailed(e.clone()))
30+
} else {
31+
Ok(Assembled::new(tx, sim_res)?)
3232
}
3333
}
3434

@@ -284,18 +284,6 @@ fn restore(parent: &Transaction, restore: &RestorePreamble) -> Result<Transactio
284284
})
285285
}
286286

287-
fn diagnostic_events(events: &[impl std::fmt::Debug], level: tracing::Level) {
288-
for (i, event) in events.iter().enumerate() {
289-
if level == tracing::Level::TRACE {
290-
tracing::trace!("{i}: {event:#?}");
291-
} else if level == tracing::Level::INFO {
292-
tracing::info!("{i}: {event:#?}");
293-
} else if level == tracing::Level::ERROR {
294-
tracing::error!("{i}: {event:#?}");
295-
}
296-
}
297-
}
298-
299287
#[cfg(test)]
300288
mod tests {
301289
use super::*;

cmd/soroban-cli/src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub async fn main() {
5252
let mut e_filter = EnvFilter::from_default_env()
5353
.add_directive("hyper=off".parse().unwrap())
5454
.add_directive(format!("stellar_cli={level}").parse().unwrap())
55+
.add_directive("stellar_rpc_client=off".parse().unwrap())
5556
.add_directive(format!("soroban_cli={level}").parse().unwrap());
5657

5758
for filter in &root.global_args.filter_logs {

cmd/soroban-cli/src/commands/contract/extend.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use std::{fmt::Debug, path::Path, str::FromStr};
22

3-
use crate::xdr::{
4-
Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange,
5-
LedgerEntryData, LedgerFootprint, Limits, Memo, Operation, OperationBody, Preconditions,
6-
SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt,
7-
TransactionMeta, TransactionMetaV3, TtlEntry, WriteXdr,
3+
use crate::{
4+
print::Print,
5+
xdr::{
6+
Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange,
7+
LedgerEntryData, LedgerFootprint, Limits, Memo, Operation, OperationBody, Preconditions,
8+
SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt,
9+
TransactionMeta, TransactionMetaV3, TtlEntry, WriteXdr,
10+
},
811
};
912
use clap::{command, Parser};
1013

@@ -127,6 +130,7 @@ impl NetworkRunnable for Cmd {
127130
config: Option<&config::Args>,
128131
) -> Result<TxnResult<u32>, Self::Error> {
129132
let config = config.unwrap_or(&self.config);
133+
let print = Print::new(args.map_or(false, |a| a.quiet));
130134
let network = config.get_network()?;
131135
tracing::trace!(?network);
132136
let keys = self.key.parse_keys(&config.locator, &network)?;
@@ -184,7 +188,8 @@ impl NetworkRunnable for Cmd {
184188

185189
let events = res.events()?;
186190
if !events.is_empty() {
187-
tracing::info!("Events:\n {events:#?}");
191+
crate::log::event::all(&events);
192+
crate::log::event::contract(&events, &print);
188193
}
189194
let meta = res.result_meta.ok_or(Error::MissingOperationResult)?;
190195

cmd/soroban-cli/src/commands/contract/invoke.rs

Lines changed: 45 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use soroban_spec::read::FromWasmError;
1212

1313
use super::super::events;
1414
use super::arg_parsing;
15+
use crate::assembled::Assembled;
1516
use crate::{
1617
assembled::simulate_and_assemble_transaction,
1718
commands::{
@@ -179,25 +180,23 @@ impl Cmd {
179180
}
180181

181182
// uses a default account to check if the tx should be sent after the simulation
182-
async fn should_send_after_sim(
183+
async fn simulate(
183184
&self,
184-
host_function_params: InvokeContractArgs,
185-
rpc_client: Client,
186-
) -> Result<ShouldSend, Error> {
187-
let account_details = default_account_entry();
188-
let sequence: i64 = account_details.seq_num.into();
189-
let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id;
185+
host_function_params: &InvokeContractArgs,
186+
account_details: &AccountEntry,
187+
rpc_client: &Client,
188+
) -> Result<Assembled, Error> {
189+
let sequence: i64 = account_details.seq_num.0;
190+
let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) =
191+
account_details.account_id.clone();
190192

191193
let tx = build_invoke_contract_tx(
192194
host_function_params.clone(),
193195
sequence + 1,
194196
self.fee.fee,
195197
account_id,
196198
)?;
197-
let txn = simulate_and_assemble_transaction(&rpc_client, &tx).await?;
198-
let txn = self.fee.apply_to_assembled_txn(txn); // do we need this part?
199-
let sim_res = txn.sim_response();
200-
self.should_send_tx(sim_res)
199+
Ok(simulate_and_assemble_transaction(rpc_client, &tx).await?)
201200
}
202201
}
203202

@@ -212,6 +211,7 @@ impl NetworkRunnable for Cmd {
212211
config: Option<&config::Args>,
213212
) -> Result<TxnResult<String>, Error> {
214213
let config = config.unwrap_or(&self.config);
214+
let print = print::Print::new(global_args.map_or(false, |g| g.quiet));
215215
let network = config.get_network()?;
216216
tracing::trace!(?network);
217217
let contract_id = self
@@ -238,11 +238,12 @@ impl NetworkRunnable for Cmd {
238238
let (function, spec, host_function_params, signers) =
239239
build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?;
240240

241-
let should_send_tx = self
242-
.should_send_after_sim(host_function_params.clone(), client.clone())
241+
let assembled = self
242+
.simulate(&host_function_params, &default_account_entry(), &client)
243243
.await?;
244+
let should_send = self.should_send_tx(&assembled.sim_res)?;
244245

245-
let account_details = if should_send_tx == ShouldSend::Yes {
246+
let account_details = if should_send == ShouldSend::Yes {
246247
client
247248
.verify_network_passphrase(Some(&network.network_passphrase))
248249
.await?;
@@ -251,7 +252,16 @@ impl NetworkRunnable for Cmd {
251252
.get_account(&config.source_account()?.to_string())
252253
.await?
253254
} else {
254-
default_account_entry()
255+
if should_send == ShouldSend::DefaultNo {
256+
print.infoln(
257+
"Simulation identified as read-only. Send by rerunning with `--send=yes`.",
258+
);
259+
}
260+
let sim_res = assembled.sim_response();
261+
let (return_value, events) = (sim_res.results()?, sim_res.events()?);
262+
crate::log::event::all(&events);
263+
crate::log::event::contract(&events, &print);
264+
return Ok(output_to_string(&spec, &return_value[0].xdr, &function)?);
255265
};
256266
let sequence: i64 = account_details.seq_num.into();
257267
let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id;
@@ -275,69 +285,32 @@ impl NetworkRunnable for Cmd {
275285
if global_args.map_or(true, |a| !a.no_cache) {
276286
data::write(sim_res.clone().into(), &network.rpc_uri()?)?;
277287
}
278-
let should_send = self.should_send_tx(sim_res)?;
279-
let (return_value, events) = match should_send {
280-
ShouldSend::Yes => {
281-
let global::Args { no_cache, .. } = global_args.cloned().unwrap_or_default();
282-
// Need to sign all auth entries
283-
if let Some(tx) = config.sign_soroban_authorizations(&txn, &signers).await? {
284-
txn = Box::new(tx);
285-
}
286-
let res = client
287-
.send_transaction_polling(&config.sign_with_local_key(*txn).await?)
288-
.await?;
289-
if !no_cache {
290-
data::write(res.clone().try_into()?, &network.rpc_uri()?)?;
291-
}
292-
let events = res
293-
.result_meta
294-
.as_ref()
295-
.map(crate::log::extract_events)
296-
.unwrap_or_default();
297-
(res.return_value()?, events)
298-
}
299-
ShouldSend::No => (sim_res.results()?[0].xdr.clone(), sim_res.events()?),
300-
ShouldSend::DefaultNo => {
301-
let print = print::Print::new(global_args.map_or(false, |g| g.quiet));
302-
print.infoln("Send skipped because simulation identified as read-only. Send by rerunning with `--send=yes`.");
303-
(sim_res.results()?[0].xdr.clone(), sim_res.events()?)
304-
}
305-
};
306-
crate::log::events(&events);
288+
let global::Args { no_cache, .. } = global_args.cloned().unwrap_or_default();
289+
// Need to sign all auth entries
290+
if let Some(tx) = config.sign_soroban_authorizations(&txn, &signers).await? {
291+
txn = Box::new(tx);
292+
}
293+
let res = client
294+
.send_transaction_polling(&config.sign_with_local_key(*txn).await?)
295+
.await?;
296+
if !no_cache {
297+
data::write(res.clone().try_into()?, &network.rpc_uri()?)?;
298+
}
299+
let events = res
300+
.result_meta
301+
.as_ref()
302+
.map(crate::log::extract_events)
303+
.unwrap_or_default();
304+
let return_value = res.return_value()?;
305+
306+
crate::log::event::all(&events);
307+
crate::log::event::contract(&events, &print);
307308
Ok(output_to_string(&spec, &return_value, &function)?)
308309
}
309310
}
310311

311312
const DEFAULT_ACCOUNT_ID: AccountId = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32])));
312313

313-
// fn log_auth_cost_and_footprint(resources: Option<&SorobanResources>) {
314-
// if let Some(resources) = resources {
315-
// crate::log::footprint(&resources.footprint);
316-
// crate::log::cost(resources);
317-
// }
318-
// }
319-
320-
// fn resources(tx: &Transaction) -> Option<&SorobanResources> {
321-
// let TransactionExt::V1(SorobanTransactionData { resources, .. }) = &tx.ext else {
322-
// return None;
323-
// };
324-
// Some(resources)
325-
// }
326-
327-
// fn auth_entries(tx: &Transaction) -> VecM<SorobanAuthorizationEntry> {
328-
// tx.operations
329-
// .first()
330-
// .and_then(|op| match op.body {
331-
// OperationBody::InvokeHostFunction(ref body) => (matches!(
332-
// body.auth.first().map(|x| &x.root_invocation.function),
333-
// Some(&SorobanAuthorizedFunction::ContractFn(_))
334-
// ))
335-
// .then_some(body.auth.clone()),
336-
// _ => None,
337-
// })
338-
// .unwrap_or_default()
339-
// }
340-
341314
fn default_account_entry() -> AccountEntry {
342315
AccountEntry {
343316
account_id: DEFAULT_ACCOUNT_ID,

cmd/soroban-cli/src/commands/contract/restore.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ impl NetworkRunnable for Cmd {
129129
config: Option<&config::Args>,
130130
) -> Result<TxnResult<u32>, Error> {
131131
let config = config.unwrap_or(&self.config);
132+
let print = crate::print::Print::new(args.map_or(true, |a| a.quiet));
132133
let network = config.get_network()?;
133134
tracing::trace!(?network);
134135
let entry_keys = self.key.parse_keys(&config.locator, &network)?;
@@ -184,7 +185,8 @@ impl NetworkRunnable for Cmd {
184185
let events = res.events()?;
185186
tracing::trace!(?meta);
186187
if !events.is_empty() {
187-
tracing::info!("Events:\n {events:#?}");
188+
crate::log::event::all(&events);
189+
crate::log::event::contract(&events, &print);
188190
}
189191

190192
// The transaction from core will succeed regardless of whether it actually found &

0 commit comments

Comments
 (0)