From 5d5158e64e6059442789000bb9f2776c80ebb174 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 2 Aug 2023 11:52:49 -0400 Subject: [PATCH 1/5] Fix ledger nano downcasting and add test --- .../account/operations/address_generation.rs | 100 ++++++++++-------- .../operations/output_consolidation.rs | 81 ++++++++------ .../transaction/sign_transaction.rs | 54 +++++----- sdk/tests/wallet/common/mod.rs | 20 ++++ sdk/tests/wallet/transactions.rs | 51 +++++++++ 5 files changed, 203 insertions(+), 103 deletions(-) diff --git a/sdk/src/wallet/account/operations/address_generation.rs b/sdk/src/wallet/account/operations/address_generation.rs index bc6c8b8842..c3b47f5204 100644 --- a/sdk/src/wallet/account/operations/address_generation.rs +++ b/sdk/src/wallet/account/operations/address_generation.rs @@ -69,28 +69,56 @@ where // needs to have it visible on the computer first, so we need to generate it without the // prompt first #[cfg(feature = "ledger_nano")] - let addresses = if options.ledger_nano_prompt - && self - .wallet - .secret_manager - .read() - .await + let addresses = { + use crate::wallet::account::SecretManager; + let secret_manager = self.wallet.secret_manager.read().await; + if secret_manager .downcast::() + .or_else(|| { + secret_manager.downcast::().and_then(|s| { + if let SecretManager::LedgerNano(n) = s { + Some(n) + } else { + None + } + }) + }) .is_some() - { - #[cfg(feature = "events")] - let changed_options = { - // Change options so ledger will not show the prompt the first time - let mut changed_options = options; - changed_options.ledger_nano_prompt = false; - changed_options - }; - let mut addresses = Vec::new(); - - for address_index in address_range { + { #[cfg(feature = "events")] - { - // Generate without prompt to be able to display it + let changed_options = { + // Change options so ledger will not show the prompt the first time + let mut changed_options = options; + changed_options.ledger_nano_prompt = false; + changed_options + }; + let mut addresses = Vec::new(); + + for address_index in address_range { + #[cfg(feature = "events")] + { + // Generate without prompt to be able to display it + let address = self + .wallet + .secret_manager + .read() + .await + .generate_ed25519_addresses( + account_details.coin_type, + account_details.index, + address_index..address_index + 1, + Some(changed_options), + ) + .await?; + self.emit( + account_details.index, + WalletEvent::LedgerAddressGeneration(AddressData { + address: address[0].to_bech32(bech32_hrp), + }), + ) + .await; + } + // Generate with prompt so the user can verify let address = self .wallet .secret_manager @@ -100,45 +128,25 @@ where account_details.coin_type, account_details.index, address_index..address_index + 1, - Some(changed_options), + Some(options), ) .await?; - self.emit( - account_details.index, - WalletEvent::LedgerAddressGeneration(AddressData { - address: address[0].to_bech32(bech32_hrp), - }), - ) - .await; + addresses.push(address[0]); } - // Generate with prompt so the user can verify - let address = self - .wallet + addresses + } else { + self.wallet .secret_manager .read() .await .generate_ed25519_addresses( account_details.coin_type, account_details.index, - address_index..address_index + 1, + address_range, Some(options), ) - .await?; - addresses.push(address[0]); + .await? } - addresses - } else { - self.wallet - .secret_manager - .read() - .await - .generate_ed25519_addresses( - account_details.coin_type, - account_details.index, - address_range, - Some(options), - ) - .await? }; #[cfg(not(feature = "ledger_nano"))] diff --git a/sdk/src/wallet/account/operations/output_consolidation.rs b/sdk/src/wallet/account/operations/output_consolidation.rs index 35ba99479e..071b2d1c11 100644 --- a/sdk/src/wallet/account/operations/output_consolidation.rs +++ b/sdk/src/wallet/account/operations/output_consolidation.rs @@ -153,17 +153,26 @@ where Some(t) => t, None => { #[cfg(feature = "ledger_nano")] - if self - .wallet - .secret_manager - .read() - .await - .downcast::() - .is_some() { - DEFAULT_LEDGER_OUTPUT_CONSOLIDATION_THRESHOLD - } else { - DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD + use crate::wallet::account::SecretManager; + let secret_manager = self.wallet.secret_manager.read().await; + if secret_manager + .downcast::() + .or_else(|| { + secret_manager.downcast::().and_then(|s| { + if let SecretManager::LedgerNano(n) = s { + Some(n) + } else { + None + } + }) + }) + .is_some() + { + DEFAULT_LEDGER_OUTPUT_CONSOLIDATION_THRESHOLD + } else { + DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD + } } #[cfg(not(feature = "ledger_nano"))] DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD @@ -184,31 +193,37 @@ where } #[cfg(feature = "ledger_nano")] - let max_inputs = if let Some(ledger) = self - .wallet - .secret_manager - .read() - .await - .downcast::() - { - let ledger_nano_status = ledger.get_ledger_nano_status().await; - // With blind signing we are only limited by the protocol - if ledger_nano_status.blind_signing_enabled() { - INPUT_COUNT_MAX + let max_inputs = { + use crate::wallet::account::SecretManager; + let secret_manager = self.wallet.secret_manager.read().await; + if let Some(ledger) = secret_manager.downcast::().or_else(|| { + secret_manager.downcast::().and_then(|s| { + if let SecretManager::LedgerNano(n) = s { + Some(n) + } else { + None + } + }) + }) { + let ledger_nano_status = ledger.get_ledger_nano_status().await; + // With blind signing we are only limited by the protocol + if ledger_nano_status.blind_signing_enabled() { + INPUT_COUNT_MAX + } else { + ledger_nano_status + .buffer_size() + .map(|buffer_size| { + // Calculate how many inputs we can have with this ledger, buffer size is different for + // different ledger types + let available_buffer_size_for_inputs = + buffer_size - ESSENCE_SIZE_WITHOUT_IN_AND_OUTPUTS - MIN_OUTPUT_SIZE_IN_ESSENCE; + (available_buffer_size_for_inputs / INPUT_SIZE) as u16 + }) + .unwrap_or(INPUT_COUNT_MAX) + } } else { - ledger_nano_status - .buffer_size() - .map(|buffer_size| { - // Calculate how many inputs we can have with this ledger, buffer size is different for - // different ledger types - let available_buffer_size_for_inputs = - buffer_size - ESSENCE_SIZE_WITHOUT_IN_AND_OUTPUTS - MIN_OUTPUT_SIZE_IN_ESSENCE; - (available_buffer_size_for_inputs / INPUT_SIZE) as u16 - }) - .unwrap_or(INPUT_COUNT_MAX) + INPUT_COUNT_MAX } - } else { - INPUT_COUNT_MAX }; #[cfg(not(feature = "ledger_nano"))] let max_inputs = INPUT_COUNT_MAX; diff --git a/sdk/src/wallet/account/operations/transaction/sign_transaction.rs b/sdk/src/wallet/account/operations/transaction/sign_transaction.rs index 1f29282b8a..56bce829a9 100644 --- a/sdk/src/wallet/account/operations/transaction/sign_transaction.rs +++ b/sdk/src/wallet/account/operations/transaction/sign_transaction.rs @@ -39,31 +39,37 @@ where .await; #[cfg(all(feature = "events", feature = "ledger_nano"))] - if let Some(ledger) = self - .wallet - .secret_manager - .read() - .await - .downcast::() { - let ledger_nano_status = ledger.get_ledger_nano_status().await; - if let Some(buffer_size) = ledger_nano_status.buffer_size() { - if needs_blind_signing(prepared_transaction_data, buffer_size) { - self.emit( - self.details().await.index, - WalletEvent::TransactionProgress(TransactionProgressEvent::PreparedTransactionEssenceHash( - prefix_hex::encode(prepared_transaction_data.essence.hash()), - )), - ) - .await; - } else { - self.emit( - self.details().await.index, - WalletEvent::TransactionProgress(TransactionProgressEvent::PreparedTransaction(Box::new( - PreparedTransactionDataDto::from(prepared_transaction_data), - ))), - ) - .await; + use crate::wallet::account::SecretManager; + let secret_manager = self.wallet.secret_manager.read().await; + if let Some(ledger) = secret_manager.downcast::().or_else(|| { + secret_manager.downcast::().and_then(|s| { + if let SecretManager::LedgerNano(n) = s { + Some(n) + } else { + None + } + }) + }) { + let ledger_nano_status = ledger.get_ledger_nano_status().await; + if let Some(buffer_size) = ledger_nano_status.buffer_size() { + if needs_blind_signing(prepared_transaction_data, buffer_size) { + self.emit( + self.details().await.index, + WalletEvent::TransactionProgress(TransactionProgressEvent::PreparedTransactionEssenceHash( + prefix_hex::encode(prepared_transaction_data.essence.hash()), + )), + ) + .await; + } else { + self.emit( + self.details().await.index, + WalletEvent::TransactionProgress(TransactionProgressEvent::PreparedTransaction(Box::new( + PreparedTransactionDataDto::from(prepared_transaction_data), + ))), + ) + .await; + } } } } diff --git a/sdk/tests/wallet/common/mod.rs b/sdk/tests/wallet/common/mod.rs index f93d5850ab..394e99b900 100644 --- a/sdk/tests/wallet/common/mod.rs +++ b/sdk/tests/wallet/common/mod.rs @@ -47,6 +47,26 @@ pub(crate) async fn make_wallet(storage_path: &str, mnemonic: Option, wallet_builder.finish().await } +#[allow(dead_code, unused_variables)] +#[cfg(feature = "ledger_nano")] +pub(crate) async fn make_ledger_nano_wallet(storage_path: &str, node: Option<&str>) -> Result { + let client_options = ClientOptions::new().with_node(node.unwrap_or(NODE_LOCAL))?; + let mut secret_manager = iota_sdk::client::secret::ledger_nano::LedgerSecretManager::new(true); + secret_manager.non_interactive = true; + + #[allow(unused_mut)] + let mut wallet_builder = Wallet::builder() + .with_secret_manager(SecretManager::LedgerNano(secret_manager)) + .with_client_options(client_options) + .with_coin_type(SHIMMER_COIN_TYPE); + #[cfg(feature = "storage")] + { + wallet_builder = wallet_builder.with_storage_path(storage_path); + } + + wallet_builder.finish().await +} + /// Create `amount` new accounts, request funds from the faucet and sync the accounts afterwards until the faucet output /// is available. Returns the new accounts. #[allow(dead_code)] diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 9f9b7c81ef..6c2fc755c7 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -255,3 +255,54 @@ async fn conflicting_transaction() -> Result<()> { tear_down(storage_path_0).ok(); tear_down(storage_path_1) } + +#[tokio::test] +#[cfg(all(feature = "ledger_nano", feature = "events"))] +#[ignore = "requires ledger nano instance"] +async fn prepare_transaction_ledger() -> Result<()> { + use std::sync::{Arc, Mutex}; + + use iota_sdk::wallet::events::{types::TransactionProgressEvent, WalletEvent, WalletEventType}; + + let storage_path = "test-storage/wallet_address_generation_ledger"; + setup(storage_path)?; + + let wallet = crate::wallet::common::make_ledger_nano_wallet(storage_path, None).await?; + + let account_0 = &create_accounts_with_funds(&wallet, 1).await?[0]; + let account_1 = wallet.create_account().finish().await?; + + let amount = 1_000_000; + let tx = account_0 + .send_with_params( + [SendParams::new(amount, *account_1.addresses().await?[0].address())?], + None, + ) + .await?; + + let prepared_event = Arc::new(Mutex::new(None)); + let prepared_event_clone = prepared_event.clone(); + + wallet + .listen([WalletEventType::TransactionProgress], move |event| { + if let WalletEvent::TransactionProgress(progress) = &event.event { + *prepared_event_clone.lock().unwrap() = Some(progress.clone()); + } else { + panic!("expected TransactionProgress") + } + }) + .await; + + if let TransactionProgressEvent::PreparedTransaction(data) = prepared_event.lock().unwrap().as_ref().unwrap() { + assert_eq!(data.essence, tx.payload.essence().into()); + for (sign, input) in data.inputs_data.iter().zip(tx.inputs) { + assert_eq!(sign.output, input.output); + assert_eq!(sign.output_metadata, input.metadata); + } + assert!(data.remainder.is_none()); + } else { + panic!("expected PreparedTransaction event"); + } + + tear_down(storage_path) +} From f8e94f5843ed9abe7b01a96e24082e0a21f56482 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 2 Aug 2023 12:04:20 -0400 Subject: [PATCH 2/5] changelogs --- .changes/ledger-nano-events.md | 5 +++++ bindings/nodejs/CHANGELOG.md | 1 + bindings/python/CHANGELOG.md | 6 ++++++ sdk/CHANGELOG.md | 1 + 4 files changed, 13 insertions(+) create mode 100644 .changes/ledger-nano-events.md diff --git a/.changes/ledger-nano-events.md b/.changes/ledger-nano-events.md new file mode 100644 index 0000000000..95dbdf5700 --- /dev/null +++ b/.changes/ledger-nano-events.md @@ -0,0 +1,5 @@ +--- +"wallet-nodejs-binding": patch +--- + +Ledger Nano events properly created when preparing transactions; diff --git a/bindings/nodejs/CHANGELOG.md b/bindings/nodejs/CHANGELOG.md index 323015b754..ab6bc7e6fd 100644 --- a/bindings/nodejs/CHANGELOG.md +++ b/bindings/nodejs/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Missing production profile when no prebuild binary is available; +- Ledger Nano events properly created when preparing transactions; ## 1.0.3 - 2023-07-31 diff --git a/bindings/python/CHANGELOG.md b/bindings/python/CHANGELOG.md index 9937cfed8e..94d0db27e7 100644 --- a/bindings/python/CHANGELOG.md +++ b/bindings/python/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.0.1 - 2023-MM-DD + +### Fixed + +- Ledger Nano events properly created when preparing transactions; + ## 1.0.0 - 2023-07-24 ### Added diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index e757791a37..e2c1c8f462 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `Clients` returning the default protocol parameters when multiple `Client` instances are used; +- Ledger Nano events properly created when preparing transactions using a `SecretManager`; ## 1.0.2 - 2023-07-28 From ead889e44c484edeabddc6d37fd8f47ef4471c3f Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 2 Aug 2023 12:46:23 -0400 Subject: [PATCH 3/5] fix test --- sdk/tests/wallet/transactions.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 6c2fc755c7..668afbed6f 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -273,12 +273,6 @@ async fn prepare_transaction_ledger() -> Result<()> { let account_1 = wallet.create_account().finish().await?; let amount = 1_000_000; - let tx = account_0 - .send_with_params( - [SendParams::new(amount, *account_1.addresses().await?[0].address())?], - None, - ) - .await?; let prepared_event = Arc::new(Mutex::new(None)); let prepared_event_clone = prepared_event.clone(); @@ -293,6 +287,13 @@ async fn prepare_transaction_ledger() -> Result<()> { }) .await; + let tx = account_0 + .send_with_params( + [SendParams::new(amount, *account_1.addresses().await?[0].address())?], + None, + ) + .await?; + if let TransactionProgressEvent::PreparedTransaction(data) = prepared_event.lock().unwrap().as_ref().unwrap() { assert_eq!(data.essence, tx.payload.essence().into()); for (sign, input) in data.inputs_data.iter().zip(tx.inputs) { From 36733c06dff8f995c57a435e07543680447e3198 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 2 Aug 2023 13:05:31 -0400 Subject: [PATCH 4/5] forgot channels exist --- sdk/tests/wallet/transactions.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 668afbed6f..c76a349376 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -260,8 +260,6 @@ async fn conflicting_transaction() -> Result<()> { #[cfg(all(feature = "ledger_nano", feature = "events"))] #[ignore = "requires ledger nano instance"] async fn prepare_transaction_ledger() -> Result<()> { - use std::sync::{Arc, Mutex}; - use iota_sdk::wallet::events::{types::TransactionProgressEvent, WalletEvent, WalletEventType}; let storage_path = "test-storage/wallet_address_generation_ledger"; @@ -274,15 +272,18 @@ async fn prepare_transaction_ledger() -> Result<()> { let amount = 1_000_000; - let prepared_event = Arc::new(Mutex::new(None)); - let prepared_event_clone = prepared_event.clone(); + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); wallet .listen([WalletEventType::TransactionProgress], move |event| { if let WalletEvent::TransactionProgress(progress) = &event.event { - *prepared_event_clone.lock().unwrap() = Some(progress.clone()); + if let TransactionProgressEvent::PreparedTransaction(data) = progress { + sender + .try_send(data.as_ref().clone()) + .expect("too many PreparedTransaction events"); + } } else { - panic!("expected TransactionProgress") + panic!("expected TransactionProgress event") } }) .await; @@ -294,16 +295,13 @@ async fn prepare_transaction_ledger() -> Result<()> { ) .await?; - if let TransactionProgressEvent::PreparedTransaction(data) = prepared_event.lock().unwrap().as_ref().unwrap() { - assert_eq!(data.essence, tx.payload.essence().into()); - for (sign, input) in data.inputs_data.iter().zip(tx.inputs) { - assert_eq!(sign.output, input.output); - assert_eq!(sign.output_metadata, input.metadata); - } - assert!(data.remainder.is_none()); - } else { - panic!("expected PreparedTransaction event"); + let data = receiver.recv().await.expect("never recieved event"); + assert_eq!(data.essence, tx.payload.essence().into()); + for (sign, input) in data.inputs_data.iter().zip(tx.inputs) { + assert_eq!(sign.output, input.output); + assert_eq!(sign.output_metadata, input.metadata); } + assert!(data.remainder.is_none()); tear_down(storage_path) } From db98c29cc68cb2fab63d9a9011624235547815be Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Wed, 2 Aug 2023 13:24:07 -0400 Subject: [PATCH 5/5] Improve event handlers and remove remainder assertion --- sdk/src/wallet/core/mod.rs | 2 +- sdk/src/wallet/events/mod.rs | 13 ++++++------- sdk/tests/wallet/address_generation.rs | 23 ++++++++++------------- sdk/tests/wallet/transactions.rs | 1 - 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index f007ac896a..e48de98f89 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -178,7 +178,7 @@ impl WalletInner { pub async fn listen + Send>(&self, events: I, handler: F) where I::IntoIter: Send, - F: Fn(&Event) + 'static + Clone + Send + Sync, + F: Fn(&Event) + 'static + Send + Sync, { let mut emitter = self.event_emitter.write().await; emitter.on(events, handler); diff --git a/sdk/src/wallet/events/mod.rs b/sdk/src/wallet/events/mod.rs index 010804ed9f..140159d0a6 100644 --- a/sdk/src/wallet/events/mod.rs +++ b/sdk/src/wallet/events/mod.rs @@ -3,6 +3,7 @@ pub mod types; +use alloc::sync::Arc; use std::{ collections::HashMap, fmt::{Debug, Formatter, Result}, @@ -10,7 +11,7 @@ use std::{ pub use self::types::{Event, WalletEvent, WalletEventType}; -type Handler = Box; +type Handler = Arc; pub struct EventEmitter { handlers: HashMap>>, @@ -28,9 +29,10 @@ impl EventEmitter { /// multiple listeners for a single event. pub fn on(&mut self, events: impl IntoIterator, handler: F) where - F: Fn(&Event) + 'static + Clone + Send + Sync, + F: Fn(&Event) + 'static + Send + Sync, { let mut events = events.into_iter().peekable(); + let handler = Arc::new(handler); // if no event is provided the handler is registered for all event types if events.peek().is_none() { // we could use a crate like strum or a macro to iterate over all values, but not sure if it's worth it @@ -43,14 +45,11 @@ impl EventEmitter { #[cfg(feature = "ledger_nano")] WalletEventType::LedgerAddressGeneration, ] { - self.handlers - .entry(event_type) - .or_default() - .push(Box::new(handler.clone())); + self.handlers.entry(event_type).or_default().push(handler.clone()); } } for event in events { - self.handlers.entry(event).or_default().push(Box::new(handler.clone())); + self.handlers.entry(event).or_default().push(handler.clone()); } } diff --git a/sdk/tests/wallet/address_generation.rs b/sdk/tests/wallet/address_generation.rs index 3309a63a60..7677855da0 100644 --- a/sdk/tests/wallet/address_generation.rs +++ b/sdk/tests/wallet/address_generation.rs @@ -1,9 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "ledger_nano")] -use std::sync::{Arc, Mutex}; - #[cfg(feature = "stronghold")] use crypto::keys::bip39::Mnemonic; #[cfg(feature = "stronghold")] @@ -94,7 +91,7 @@ async fn wallet_address_generation_stronghold() -> Result<()> { } #[tokio::test] -#[cfg(feature = "ledger_nano")] +#[cfg(all(feature = "ledger_nano", feature = "events"))] #[ignore = "requires ledger nano instance"] async fn wallet_address_generation_ledger() -> Result<()> { let storage_path = "test-storage/wallet_address_generation_ledger"; @@ -127,16 +124,16 @@ async fn wallet_address_generation_ledger() -> Result<()> { "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" ); - let address_event = Arc::new(Mutex::new(None)); - let address_event_clone = address_event.clone(); + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); - #[cfg(feature = "events")] wallet .listen([WalletEventType::LedgerAddressGeneration], move |event| { if let WalletEvent::LedgerAddressGeneration(address) = &event.event { - *address_event_clone.lock().unwrap() = Some(address.address); + sender + .try_send(address.address) + .expect("too many LedgerAddressGeneration events"); } else { - panic!("expected LedgerAddressGeneration") + panic!("expected LedgerAddressGeneration event") } }) .await; @@ -162,10 +159,10 @@ async fn wallet_address_generation_ledger() -> Result<()> { ); assert_eq!( - address_event - .lock() - .unwrap() - .unwrap() + receiver + .recv() + .await + .expect("never received event") .inner() .to_bech32_unchecked("smr"), // Address generated with bip32 path: [44, 4218, 0, 0, 0]. diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index c76a349376..14952185fa 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -301,7 +301,6 @@ async fn prepare_transaction_ledger() -> Result<()> { assert_eq!(sign.output, input.output); assert_eq!(sign.output_metadata, input.metadata); } - assert!(data.remainder.is_none()); tear_down(storage_path) }