diff --git a/.github/workflows/bindings-wallet-nodejs.yml b/.github/workflows/bindings-wallet-nodejs.yml deleted file mode 100644 index e86578a441..0000000000 --- a/.github/workflows/bindings-wallet-nodejs.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Nodejs bindings checks (wallet) - -on: - push: - branches: [develop, production, 2.0] - paths: - - ".github/actions/**" - - "**.rs" # Include all rust files - - "**Cargo.toml" # Include all Cargo.toml files - - "**Cargo.lock" # Include all Cargo.lock files - - "!**/examples/**" # Exclude all examples - - "!**/tests/**" # Exclude all tests - - "!cli/**" # Exclude CLI - - "!**/bindings/**" # Exclude all bindings - - "bindings/nodejs-old/**" - - ".github/workflows/bindings-wallet-nodejs.yml" - - ".patches/*" - pull_request: - branches: [develop, production, 2.0] - paths: - - ".github/actions/**" - - "**.rs" # Include all rust files - - "**Cargo.toml" # Include all Cargo.toml files - - "**Cargo.lock" # Include all Cargo.lock files - - "!**/examples/**" # Exclude all examples - - "!**/tests/**" # Exclude all tests - - "!cli/**" # Exclude CLI - - "!**/bindings/**" # Exclude all bindings - - "bindings/nodejs-old/**" - - ".github/workflows/bindings-wallet-nodejs.yml" - - ".patches/*" - schedule: - - cron: "0 1 * * *" - workflow_dispatch: - -env: - CARGO_INCREMENTAL: 0 - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [windows-latest, macos-13, ubuntu-latest] - node: ["18"] - - steps: - - name: Checkout the Source Code - uses: actions/checkout@v3 - - - name: Select Xcode (macOS) - uses: maxim-lobanov/setup-xcode@v1 - if: matrix.os == 'macos-13' - with: - xcode-version: '14.3' - - - name: Set deployment target (macOS) - run: echo "MACOSX_DEPLOYMENT_TARGET=10.13" >> $GITHUB_ENV - if: matrix.os == 'macos-13' - - - name: Set up Rust - uses: ./.github/actions/setup-rust - with: - cache-root: bindings/nodejs-old - - - name: Set Up Node.js ${{ matrix.node }} and Yarn Cache - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - cache: npm - cache-dependency-path: bindings/nodejs-old/package-lock.json - - # This step is required for bindgen to work on Windows. - - name: Set Up Clang/LLVM (Windows) - if: ${{ startsWith(matrix.os, 'windows') }} - uses: ./.github/actions/setup-clang - - - name: Install Required Dependencies (Ubuntu) - if: ${{ startsWith(matrix.os, 'ubuntu') }} - run: | - sudo apt-get update - sudo apt-get install libudev-dev libusb-1.0-0-dev - - # This step is required to support macOS 10.13 - - name: Patch librocksdb-sys (macOS) - if: ${{ startsWith(matrix.os, 'macos') }} - run: | - cargo install cargo-patch - cp ${{ github.workspace }}/.patches/rocksdb_faligned_allocation.patch . - git apply --ignore-space-change --ignore-whitespace ${{ github.workspace }}/.patches/macos_cargo_toml.patch - cat Cargo.toml - cargo patch - - - name: Build nodejs binding - run: npm ci --build-from-source - working-directory: bindings/nodejs-old - - - name: Run npm test - run: npm test - working-directory: bindings/nodejs-old diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index 5342bbd85d..bb8cb0114f 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -299,7 +299,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM ClientMethod::GetBlockRaw { block_id } => Response::Raw(client.get_block_raw(&block_id).await?), ClientMethod::GetOutput { output_id } => Response::OutputWithMetadataResponse( client - .get_output(&output_id) + .get_output_with_metadata(&output_id) .await .map(OutputWithMetadataResponse::from)?, ), @@ -329,7 +329,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM ClientMethod::FoundryOutputId { foundry_id } => Response::OutputId(client.foundry_output_id(foundry_id).await?), ClientMethod::GetOutputs { output_ids } => { let outputs_response = client - .get_outputs(&output_ids) + .get_outputs_with_metadata(&output_ids) .await? .iter() .map(OutputWithMetadataResponse::from) @@ -338,7 +338,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM } ClientMethod::GetOutputsIgnoreErrors { output_ids } => { let outputs_response = client - .get_outputs_ignore_errors(&output_ids) + .get_outputs_with_metadata_ignore_errors(&output_ids) .await? .iter() .map(OutputWithMetadataResponse::from) diff --git a/sdk/examples/client/output/nft.rs b/sdk/examples/client/output/nft.rs index 9278ed28ec..3f01558e02 100644 --- a/sdk/examples/client/output/nft.rs +++ b/sdk/examples/client/output/nft.rs @@ -94,18 +94,16 @@ async fn main() -> Result<()> { let output_ids_response = client .basic_output_ids([QueryParameter::Address(bech32_nft_address)]) .await?; - let output_with_meta = client.get_output(&output_ids_response.items[0]).await?; + let output = client.get_output(&output_ids_response.items[0]).await?; let block = client .build_block() .with_secret_manager(&secret_manager) .with_input(nft_output_id.into())? .with_input(output_ids_response.items[0].into())? - .with_outputs([ - NftOutputBuilder::new_with_amount(1_000_000 + output_with_meta.output().amount(), nft_id) - .add_unlock_condition(AddressUnlockCondition::new(bech32_nft_address)) - .finish_output(token_supply)?, - ])? + .with_outputs([NftOutputBuilder::new_with_amount(1_000_000 + output.amount(), nft_id) + .add_unlock_condition(AddressUnlockCondition::new(bech32_nft_address)) + .finish_output(token_supply)?])? .finish() .await?; @@ -122,8 +120,8 @@ async fn main() -> Result<()> { ////////////////////////////////// let nft_output_id = get_nft_output_id(block.payload().unwrap())?; - let output_with_meta = client.get_output(&nft_output_id).await?; - let outputs = [BasicOutputBuilder::new_with_amount(output_with_meta.output().amount()) + let output = client.get_output(&nft_output_id).await?; + let outputs = [BasicOutputBuilder::new_with_amount(output.amount()) .add_unlock_condition(AddressUnlockCondition::new(bech32_nft_address)) .finish_output(token_supply)?]; diff --git a/sdk/src/client/api/block_builder/input_selection/automatic.rs b/sdk/src/client/api/block_builder/input_selection/automatic.rs index e31675f3be..46cd8a36a6 100644 --- a/sdk/src/client/api/block_builder/input_selection/automatic.rs +++ b/sdk/src/client/api/block_builder/input_selection/automatic.rs @@ -57,7 +57,7 @@ impl<'a> ClientBlockBuilder<'a> { .items, ); - self.client.get_outputs(&output_ids).await + self.client.get_outputs_with_metadata(&output_ids).await } /// Searches inputs for provided outputs, by requesting the outputs from the account addresses or for diff --git a/sdk/src/client/api/block_builder/input_selection/manual.rs b/sdk/src/client/api/block_builder/input_selection/manual.rs index 5f40b40f0f..9c5b8e1e4d 100644 --- a/sdk/src/client/api/block_builder/input_selection/manual.rs +++ b/sdk/src/client/api/block_builder/input_selection/manual.rs @@ -38,7 +38,7 @@ impl<'a> ClientBlockBuilder<'a> { if let Some(inputs) = &self.inputs { for input in inputs { - let output_with_meta = self.client.get_output(input.output_id()).await?; + let output_with_meta = self.client.get_output_with_metadata(input.output_id()).await?; if !output_with_meta.metadata().is_spent() { let alias_transition = is_alias_transition( diff --git a/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs b/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs index e76234d984..46ca0b8b06 100644 --- a/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs +++ b/sdk/src/client/api/block_builder/input_selection/sender_issuer.rs @@ -100,7 +100,7 @@ impl<'a> ClientBlockBuilder<'a> { } }) { let output_id = self.client.alias_output_id(*alias_id).await?; - let output_with_meta = self.client.get_output(&output_id).await?; + let output_with_meta = self.client.get_output_with_metadata(&output_id).await?; if let Output::Alias(alias_output) = output_with_meta.output() { // State transition if we add them to inputs let unlock_address = alias_output.state_controller_address(); @@ -153,7 +153,7 @@ impl<'a> ClientBlockBuilder<'a> { } }) { let output_id = self.client.nft_output_id(*nft_id).await?; - let output_with_meta = self.client.get_output(&output_id).await?; + let output_with_meta = self.client.get_output_with_metadata(&output_id).await?; if let Output::Nft(nft_output) = output_with_meta.output() { let unlock_address = nft_output .unlock_conditions() diff --git a/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs b/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs index 94c99f70d2..978537b332 100644 --- a/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs +++ b/sdk/src/client/api/block_builder/input_selection/utxo_chains.rs @@ -57,7 +57,7 @@ pub(crate) async fn get_alias_and_nft_outputs_recursively( match unlock_address { Address::Alias(address) => { let input_id = client.alias_output_id(*address.alias_id()).await?; - let input = client.get_output(&input_id).await?; + let input = client.get_output_with_metadata(&input_id).await?; if let Output::Alias(alias_input) = input.output() { // State transition if we add them to inputs let alias_unlock_address = alias_input.state_controller_address(); @@ -71,7 +71,7 @@ pub(crate) async fn get_alias_and_nft_outputs_recursively( } Address::Nft(address) => { let input_id = client.nft_output_id(*address.nft_id()).await?; - let input = client.get_output(&input_id).await?; + let input = client.get_output_with_metadata(&input_id).await?; if let Output::Nft(nft_input) = input.output() { let unlock_address = nft_input .unlock_conditions() @@ -119,7 +119,7 @@ impl<'a> ClientBlockBuilder<'a> { // Check if the transaction is a governance_transition, by checking if the new index is the same // as the previous index let output_id = client.alias_output_id(*alias_output.alias_id()).await?; - let input = client.get_output(&output_id).await?; + let input = client.get_output_with_metadata(&output_id).await?; if let Output::Alias(alias_input) = input.output() { // A governance transition is identified by an unchanged State Index in next // state. @@ -135,7 +135,7 @@ impl<'a> ClientBlockBuilder<'a> { // If the id is null then this output creates it and we can't have a previous output if !nft_output.nft_id().is_null() { let output_id = client.nft_output_id(*nft_output.nft_id()).await?; - let input = client.get_output(&output_id).await?; + let input = client.get_output_with_metadata(&output_id).await?; if let Output::Nft(nft_input) = input.output() { let unlock_address = nft_input .unlock_conditions() @@ -148,7 +148,7 @@ impl<'a> ClientBlockBuilder<'a> { Output::Foundry(foundry_output) => { // if it's the first foundry output, then we can't have it as input if let Ok(output_id) = client.foundry_output_id(foundry_output.id()).await { - let input = client.get_output(&output_id).await?; + let input = client.get_output_with_metadata(&output_id).await?; if let Output::Foundry(foundry_input_output) = input.output() { utxo_chains.push((Address::Alias(*foundry_input_output.alias_address()), input)); } diff --git a/sdk/src/client/api/consolidation.rs b/sdk/src/client/api/consolidation.rs index 684f2a031a..5f9790a862 100644 --- a/sdk/src/client/api/consolidation.rs +++ b/sdk/src/client/api/consolidation.rs @@ -46,7 +46,7 @@ impl Client { ]) .await?; - let basic_outputs_responses = self.get_outputs(&output_ids_response.items).await?; + let basic_outputs_responses = self.get_outputs_with_metadata(&output_ids_response.items).await?; if !basic_outputs_responses.is_empty() { // If we reach the same index again diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index 4db73632f1..9f7fee2ba2 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -53,7 +53,7 @@ impl Client { }) .collect::>(); - self.get_outputs(&input_ids).await + self.get_outputs_with_metadata(&input_ids).await } /// Get a builder that can be used to construct a block in parts. @@ -179,7 +179,7 @@ impl Client { }) .and_then(|res| async { let items = res.items; - self.get_outputs(&items).await + self.get_outputs_with_metadata(&items).await }) .try_collect::>() .await?; @@ -232,7 +232,7 @@ impl Client { output_ids: &[OutputId], addresses: &[Bech32Address], ) -> Result> { - let mut output_responses = self.get_outputs(output_ids).await?; + let mut output_responses = self.get_outputs_with_metadata(output_ids).await?; // Use `get_address()` API to get the address outputs first, // then collect the `UtxoInput` in the HashSet. @@ -247,7 +247,7 @@ impl Client { ]) .await?; - output_responses.extend(self.get_outputs(&output_ids_response.items).await?); + output_responses.extend(self.get_outputs_with_metadata(&output_ids_response.items).await?); } Ok(output_responses.clone()) diff --git a/sdk/src/client/node_api/core/mod.rs b/sdk/src/client/node_api/core/mod.rs index 9ae39e13b5..0dfce551c6 100644 --- a/sdk/src/client/node_api/core/mod.rs +++ b/sdk/src/client/node_api/core/mod.rs @@ -5,16 +5,31 @@ pub mod routes; +use packable::PackableExt; + #[cfg(not(target_family = "wasm"))] use crate::client::constants::MAX_PARALLEL_API_REQUESTS; use crate::{ client::{Client, Result}, - types::block::output::{OutputId, OutputMetadata, OutputWithMetadata}, + types::block::output::{Output, OutputId, OutputMetadata, OutputWithMetadata}, }; impl Client { + // Finds output and its metadata by output ID. + /// GET /api/core/v3/outputs/{outputId} + /// + GET /api/core/v3/outputs/{outputId}/metadata + pub async fn get_output_with_metadata(&self, output_id: &OutputId) -> Result { + let output = Output::unpack_verified( + self.get_output_raw(output_id).await?, + &self.get_protocol_parameters().await?, + )?; + let metadata = self.get_output_metadata(output_id).await?; + + Ok(OutputWithMetadata::new(output, metadata)) + } + /// Request outputs by their output ID in parallel - pub async fn get_outputs(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs(&self, output_ids: &[OutputId]) -> Result> { #[cfg(target_family = "wasm")] let outputs = futures::future::try_join_all(output_ids.iter().map(|id| self.get_output(id))).await?; @@ -38,9 +53,38 @@ impl Client { Ok(outputs) } + /// Request outputs and their metadata by their output ID in parallel + pub async fn get_outputs_with_metadata(&self, output_ids: &[OutputId]) -> Result> { + #[cfg(target_family = "wasm")] + let outputs = + futures::future::try_join_all(output_ids.iter().map(|id| self.get_output_with_metadata(id))).await?; + + #[cfg(not(target_family = "wasm"))] + let outputs = + futures::future::try_join_all(output_ids.chunks(MAX_PARALLEL_API_REQUESTS).map(|output_ids_chunk| { + let client = self.clone(); + let output_ids_chunk = output_ids_chunk.to_vec(); + async move { + tokio::spawn(async move { + futures::future::try_join_all( + output_ids_chunk.iter().map(|id| client.get_output_with_metadata(id)), + ) + .await + }) + .await? + } + })) + .await? + .into_iter() + .flatten() + .collect(); + + Ok(outputs) + } + /// Request outputs by their output ID in parallel, ignoring failed requests /// Useful to get data about spent outputs, that might not be pruned yet - pub async fn get_outputs_ignore_errors(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs_ignore_errors(&self, output_ids: &[OutputId]) -> Result> { #[cfg(target_family = "wasm")] let outputs = futures::future::join_all(output_ids.iter().map(|id| self.get_output(id))) .await @@ -69,6 +113,40 @@ impl Client { Ok(outputs) } + /// Request outputs and their metadata by their output ID in parallel, ignoring failed requests + /// Useful to get data about spent outputs, that might not be pruned yet + pub async fn get_outputs_with_metadata_ignore_errors( + &self, + output_ids: &[OutputId], + ) -> Result> { + #[cfg(target_family = "wasm")] + let outputs = futures::future::join_all(output_ids.iter().map(|id| self.get_output_with_metadata(id))) + .await + .into_iter() + .filter_map(Result::ok) + .collect(); + + #[cfg(not(target_family = "wasm"))] + let outputs = + futures::future::try_join_all(output_ids.chunks(MAX_PARALLEL_API_REQUESTS).map(|output_ids_chunk| { + let client = self.clone(); + let output_ids_chunk = output_ids_chunk.to_vec(); + tokio::spawn(async move { + futures::future::join_all(output_ids_chunk.iter().map(|id| client.get_output_with_metadata(id))) + .await + .into_iter() + .filter_map(Result::ok) + .collect::>() + }) + })) + .await? + .into_iter() + .flatten() + .collect(); + + Ok(outputs) + } + /// Requests metadata for outputs by their output ID in parallel, ignoring failed requests pub async fn get_outputs_metadata_ignore_errors(&self, output_ids: &[OutputId]) -> Result> { #[cfg(target_family = "wasm")] diff --git a/sdk/src/client/node_api/core/routes.rs b/sdk/src/client/node_api/core/routes.rs index 04351d165e..f1b255db24 100644 --- a/sdk/src/client/node_api/core/routes.rs +++ b/sdk/src/client/node_api/core/routes.rs @@ -15,11 +15,13 @@ use crate::{ }, types::{ api::core::response::{ - BlockMetadataResponse, InfoResponse, OutputWithMetadataResponse, PeerResponse, RoutesResponse, - SubmitBlockResponse, TipsResponse, + BlockMetadataResponse, InfoResponse, PeerResponse, RoutesResponse, SubmitBlockResponse, TipsResponse, }, block::{ - output::{dto::OutputMetadataDto, Output, OutputId, OutputMetadata, OutputWithMetadata}, + output::{ + dto::{OutputDto, OutputMetadataDto}, + Output, OutputId, OutputMetadata, + }, payload::transaction::TransactionId, slot::{SlotCommitment, SlotCommitmentId, SlotIndex}, Block, BlockDto, BlockId, @@ -257,21 +259,18 @@ impl ClientInner { /// Finds an output by its ID and returns it as object. /// GET /api/core/v3/outputs/{outputId} - pub async fn get_output(&self, output_id: &OutputId) -> Result { + pub async fn get_output(&self, output_id: &OutputId) -> Result { let path = &format!("api/core/v3/outputs/{output_id}"); - let response: OutputWithMetadataResponse = self + let output = self .node_manager .read() .await - .get_request(path, None, self.get_timeout().await, false, true) + .get_request::(path, None, self.get_timeout().await, false, true) .await?; - let token_supply = self.get_token_supply().await?; - let output = Output::try_from_dto(response.output, token_supply)?; - let metadata = OutputMetadata::try_from(response.metadata)?; - Ok(OutputWithMetadata::new(output, metadata)) + Ok(Output::try_from_dto(output, token_supply)?) } /// Finds an output by its ID and returns it as raw bytes. @@ -439,8 +438,7 @@ impl Client { .map_err(|_| crate::client::Error::UrlAuth("password"))?; } } - let path = "api/core/v3/info"; - url.set_path(path); + url.set_path(INFO_PATH); let resp: InfoResponse = crate::client::node_manager::http_client::HttpClient::new(DEFAULT_USER_AGENT.to_string()) diff --git a/sdk/src/wallet/account/mod.rs b/sdk/src/wallet/account/mod.rs index 7a67509d54..7174c8102a 100644 --- a/sdk/src/wallet/account/mod.rs +++ b/sdk/src/wallet/account/mod.rs @@ -228,9 +228,9 @@ where // Foundry was not found in the account, try to get it from the node let foundry_output_id = self.client().foundry_output_id(foundry_id).await?; - let output_response = self.client().get_output(&foundry_output_id).await?; + let output = self.client().get_output(&foundry_output_id).await?; - Ok(output_response.output().to_owned()) + Ok(output) } /// Save the account to the database, accepts the updated_account as option so we don't need to drop it before diff --git a/sdk/src/wallet/account/operations/syncing/foundries.rs b/sdk/src/wallet/account/operations/syncing/foundries.rs index 1d4b97bb3b..96c5998d37 100644 --- a/sdk/src/wallet/account/operations/syncing/foundries.rs +++ b/sdk/src/wallet/account/operations/syncing/foundries.rs @@ -39,9 +39,9 @@ where .await?; // Update account with new foundries. - for foundry_output_with_metadata in results.into_iter().flatten() { - if let Output::Foundry(foundry) = foundry_output_with_metadata.output() { - foundries.insert(foundry.id(), foundry.to_owned()); + for foundry in results.into_iter().flatten() { + if let Output::Foundry(foundry) = foundry { + foundries.insert(foundry.id(), foundry); } } diff --git a/sdk/src/wallet/account/operations/syncing/outputs.rs b/sdk/src/wallet/account/operations/syncing/outputs.rs index 5710d9156a..2beaa541b1 100644 --- a/sdk/src/wallet/account/operations/syncing/outputs.rs +++ b/sdk/src/wallet/account/operations/syncing/outputs.rs @@ -105,7 +105,7 @@ where drop(account_details); if !unknown_outputs.is_empty() { - outputs.extend(self.client().get_outputs(&unknown_outputs).await?); + outputs.extend(self.client().get_outputs_with_metadata(&unknown_outputs).await?); } log::debug!( @@ -215,7 +215,7 @@ pub(crate) async fn get_inputs_for_transaction_payload( .collect::>(); client - .get_outputs_ignore_errors(&output_ids) + .get_outputs_with_metadata_ignore_errors(&output_ids) .await .map_err(|e| e.into()) } diff --git a/sdk/tests/client/consolidation.rs b/sdk/tests/client/consolidation.rs index 84224d0f04..644fd44271 100644 --- a/sdk/tests/client/consolidation.rs +++ b/sdk/tests/client/consolidation.rs @@ -34,7 +34,7 @@ async fn consolidate_outputs() -> Result<()> { assert_eq!(output_ids_response.items.len(), 1); let initial_output = client.get_output(&output_ids_response.items[0]).await?; - let initial_base_coin_amount = initial_output.output().amount(); + let initial_base_coin_amount = initial_output.amount(); // First split funds to multiple addresses let token_supply = client.get_token_supply().await?; @@ -77,7 +77,7 @@ async fn consolidate_outputs() -> Result<()> { assert_eq!(output_ids_response.items.len(), 1); let final_output = client.get_output(&output_ids_response.items[0]).await?; - let final_base_coin_amount = final_output.output().amount(); + let final_base_coin_amount = final_output.amount(); // The output has the full amount again assert_eq!(final_base_coin_amount, initial_base_coin_amount);