From 950b3322dca1a37e39d2709eecba3e731579fc35 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:07:48 +0200 Subject: [PATCH] Add issuer_id to Prepared and Signed TransactionData (#2219) * Add issuer_id to Prepared and Signed TransactionData * Bump h2 dependency * Add missing field * Prefer signed_transaction_data issuer_id over options issuer_id * Add alpha tag --- .github/workflows/bindings-nodejs-publish.yml | 2 +- Cargo.lock | 4 +- bindings/nodejs/CHANGELOG.md | 10 +++ .../types/client/prepared-transaction-data.ts | 7 +- .../types/wallet/signed-transaction-data.ts | 5 +- bindings/nodejs/package.json | 2 +- .../python/iota_sdk/types/transaction_data.py | 6 +- .../offline_signing/2_sign_transaction.rs | 1 + .../block_builder/transaction_builder/mod.rs | 10 +++ sdk/src/client/api/types.rs | 14 +++- sdk/src/client/secret/mod.rs | 2 + sdk/src/wallet/operations/transaction/mod.rs | 4 +- .../operations/transaction/send_outputs.rs | 1 + .../transaction/sign_transaction.rs | 1 + sdk/tests/client/signing/account.rs | 2 + sdk/tests/client/signing/basic.rs | 3 + sdk/tests/client/signing/delegation.rs | 12 +++ sdk/tests/client/signing/mod.rs | 1 + sdk/tests/client/signing/nft.rs | 1 + sdk/tests/wallet/events.rs | 1 + sdk/tests/wallet/transactions.rs | 76 +++++++++++++++++++ 21 files changed, 156 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bindings-nodejs-publish.yml b/.github/workflows/bindings-nodejs-publish.yml index 624b6919e7..9abd347682 100644 --- a/.github/workflows/bindings-nodejs-publish.yml +++ b/.github/workflows/bindings-nodejs-publish.yml @@ -50,7 +50,7 @@ jobs: shell: sh env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: yarn publish --access public + run: yarn publish --access public --tag alpha nodejs-binding-prebuild: runs-on: ${{ matrix.os }} diff --git a/Cargo.lock b/Cargo.lock index b2fcc42678..0191e7fa8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1323,9 +1323,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", diff --git a/bindings/nodejs/CHANGELOG.md b/bindings/nodejs/CHANGELOG.md index 4985373c2d..4caa5bb962 100644 --- a/bindings/nodejs/CHANGELOG.md +++ b/bindings/nodejs/CHANGELOG.md @@ -19,6 +19,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 2.0.0-alpha.5 - 2024-04-15 + +### Added + +- `{PreparedTransactionData, SignedTransactionData}::issuerId`; + +### Fixed + +- Implicit account transition with existing account; + ## 2.0.0-alpha.4 - 2024-04-04 ### Changed diff --git a/bindings/nodejs/lib/types/client/prepared-transaction-data.ts b/bindings/nodejs/lib/types/client/prepared-transaction-data.ts index cfdc7ad9e1..6275c42513 100644 --- a/bindings/nodejs/lib/types/client/prepared-transaction-data.ts +++ b/bindings/nodejs/lib/types/client/prepared-transaction-data.ts @@ -1,7 +1,8 @@ -// Copyright 2021-2023 IOTA Stiftung +// Copyright 2021-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 import { Type } from 'class-transformer'; +import { AccountId } from '../block'; import { Address, AddressDiscriminator } from '../block/address'; import { Output, OutputDiscriminator } from '../block/output/output'; import { Transaction } from '../block/payload/signed_transaction'; @@ -29,6 +30,10 @@ export class PreparedTransactionData { * Mana rewards by input. */ manaRewards?: { [outputId: HexEncodedString]: NumericString }; + /** + * The block issuer id from which the BIC for the block containing this transaction should be burned. + */ + issuerId?: AccountId; } /** diff --git a/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts b/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts index def0268ba5..9fa109afd8 100644 --- a/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts +++ b/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Type } from 'class-transformer'; +import { AccountId } from '../block'; import { SignedTransactionPayload } from '../block/payload/signed_transaction'; import { InputSigningData } from '../client'; import { HexEncodedString, NumericString } from '../utils'; @@ -14,6 +15,8 @@ export class SignedTransactionData { /** Signed inputs data. */ @Type(() => InputSigningData) inputsData!: InputSigningData; - /** Mana rewards by input */ + /** Mana rewards by input. */ manaRewards?: { [outputId: HexEncodedString]: NumericString }; + /** The block issuer id from which the BIC for the block containing this transaction should be burned. */ + issuerId?: AccountId; } diff --git a/bindings/nodejs/package.json b/bindings/nodejs/package.json index 0fc0f2aa24..a1d772639f 100644 --- a/bindings/nodejs/package.json +++ b/bindings/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@iota/sdk", - "version": "2.0.0-alpha.4", + "version": "2.0.0-alpha.5", "description": "Node.js binding to the IOTA SDK library", "main": "out/index.js", "types": "out/index.d.ts", diff --git a/bindings/python/iota_sdk/types/transaction_data.py b/bindings/python/iota_sdk/types/transaction_data.py index 572ef11665..55415b5245 100644 --- a/bindings/python/iota_sdk/types/transaction_data.py +++ b/bindings/python/iota_sdk/types/transaction_data.py @@ -10,7 +10,7 @@ from iota_sdk.types.output_metadata import OutputMetadata from iota_sdk.types.payload import Transaction, SignedTransactionPayload from iota_sdk.types.signature import Bip44 -from iota_sdk.types.common import json +from iota_sdk.types.common import json, HexStr @json @@ -53,11 +53,13 @@ class PreparedTransactionData: inputs_data: Data about the inputs which is required for signing. remainders: Data about remainder outputs. mana_rewards: Mana rewards by input. + issuer_id: The block issuer id from which the BIC for the block containing this transaction should be burned. """ transaction: Transaction inputs_data: List[InputSigningData] remainders: Optional[List[RemainderData]] = None mana_rewards: Optional[dict[OutputId, int]] = None + issuer_id: Optional[HexStr] = None @json @@ -69,7 +71,9 @@ class SignedTransactionData: payload: The transaction payload. inputs_data: Data about the inputs consumed in the transaction. mana_rewards: Mana rewards by input. + issuer_id: The block issuer id from which the BIC for the block containing this transaction should be burned. """ payload: SignedTransactionPayload inputs_data: List[InputSigningData] mana_rewards: Optional[dict[OutputId, int]] = None + issuer_id: Optional[HexStr] = None diff --git a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs index db05836e11..75a5631ed5 100644 --- a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs +++ b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs @@ -52,6 +52,7 @@ async fn main() -> Result<(), Box> { payload: signed_transaction, inputs_data: prepared_transaction_data.inputs_data, mana_rewards: prepared_transaction_data.mana_rewards, + issuer_id: prepared_transaction_data.issuer_id, }; println!("Signed transaction."); diff --git a/sdk/src/client/api/block_builder/transaction_builder/mod.rs b/sdk/src/client/api/block_builder/transaction_builder/mod.rs index 1d5f68bb38..3e538a0960 100644 --- a/sdk/src/client/api/block_builder/transaction_builder/mod.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/mod.rs @@ -160,6 +160,7 @@ impl Client { .with_mana_allotments(options.mana_allotments) .with_remainder_address(remainder_address) .with_transitions(options.transitions) + .with_issuer_id(options.issuer_id) .with_burn(options.burn); if let (Some(account_id), Some(reference_mana_cost)) = (options.issuer_id, reference_mana_cost) { @@ -193,6 +194,7 @@ pub struct TransactionBuilder { latest_slot_commitment_id: SlotCommitmentId, requirements: Vec, min_mana_allotment: Option, + issuer_id: Option, mana_allotments: BTreeMap, mana_rewards: HashMap, payload: Option, @@ -268,6 +270,7 @@ impl TransactionBuilder { latest_slot_commitment_id, requirements: Vec::new(), min_mana_allotment: None, + issuer_id: None, mana_allotments: Default::default(), mana_rewards: Default::default(), allow_additional_input_selection: true, @@ -498,6 +501,7 @@ impl TransactionBuilder { inputs_data, remainders: self.remainders.data, mana_rewards: self.mana_rewards.into_iter().collect(), + issuer_id: self.issuer_id, }; data.verify_semantic(&self.protocol_parameters)?; @@ -607,6 +611,12 @@ impl TransactionBuilder { self } + /// Specifies the block issuer id that should be used when sending a block. + pub fn with_issuer_id(mut self, issuer_id: impl Into>) -> Self { + self.issuer_id = issuer_id.into(); + self + } + /// Disables selecting additional inputs. pub fn disable_additional_input_selection(mut self) -> Self { self.allow_additional_input_selection = false; diff --git a/sdk/src/client/api/types.rs b/sdk/src/client/api/types.rs index 09db52fa87..2c1a3b9dd4 100644 --- a/sdk/src/client/api/types.rs +++ b/sdk/src/client/api/types.rs @@ -11,7 +11,7 @@ use crate::{ types::{ block::{ address::Address, - output::{Output, OutputId}, + output::{AccountId, Output, OutputId}, payload::{ signed_transaction::{ dto::{SignedTransactionPayloadDto, TransactionDto}, @@ -37,6 +37,8 @@ pub struct PreparedTransactionData { pub remainders: Vec, /// Mana rewards pub mana_rewards: BTreeMap, + /// The block issuer id from which the BIC for the block containing this transaction should be burned. + pub issuer_id: Option, } /// PreparedTransactionData Dto @@ -53,6 +55,8 @@ pub struct PreparedTransactionDataDto { /// Mana rewards #[serde(default, skip_serializing_if = "BTreeMap::is_empty", with = "mana_rewards")] pub mana_rewards: BTreeMap, + /// The block issuer id from which the BIC for the block containing this transaction should be burned. + pub issuer_id: Option, } impl From<&PreparedTransactionData> for PreparedTransactionDataDto { @@ -62,6 +66,7 @@ impl From<&PreparedTransactionData> for PreparedTransactionDataDto { inputs_data: value.inputs_data.clone(), remainders: value.remainders.clone(), mana_rewards: value.mana_rewards.clone(), + issuer_id: value.issuer_id, } } } @@ -78,6 +83,7 @@ impl TryFromDto for PreparedTransactionData { inputs_data: dto.inputs_data, remainders: dto.remainders, mana_rewards: dto.mana_rewards, + issuer_id: dto.issuer_id, }) } } @@ -100,6 +106,8 @@ pub struct SignedTransactionData { pub inputs_data: Vec, /// Mana rewards pub mana_rewards: BTreeMap, + /// The block issuer id from which the BIC for the block containing this transaction should be burned. + pub issuer_id: Option, } /// SignedTransactionData Dto @@ -113,6 +121,8 @@ pub struct SignedTransactionDataDto { /// Mana rewards #[serde(default, skip_serializing_if = "BTreeMap::is_empty", with = "mana_rewards")] pub mana_rewards: BTreeMap, + /// The block issuer id from which the BIC for the block containing this transaction should be burned. + pub issuer_id: Option, } impl From<&SignedTransactionData> for SignedTransactionDataDto { @@ -121,6 +131,7 @@ impl From<&SignedTransactionData> for SignedTransactionDataDto { payload: SignedTransactionPayloadDto::from(&value.payload), inputs_data: value.inputs_data.clone(), mana_rewards: value.mana_rewards.clone(), + issuer_id: value.issuer_id, } } } @@ -136,6 +147,7 @@ impl TryFromDto for SignedTransactionData { payload: SignedTransactionPayload::try_from_dto_with_params_inner(dto.payload, params)?, inputs_data: dto.inputs_data, mana_rewards: dto.mana_rewards, + issuer_id: dto.issuer_id, }) } } diff --git a/sdk/src/client/secret/mod.rs b/sdk/src/client/secret/mod.rs index dccb4e6839..b3a70f0f7b 100644 --- a/sdk/src/client/secret/mod.rs +++ b/sdk/src/client/secret/mod.rs @@ -662,6 +662,7 @@ where transaction, inputs_data, mana_rewards, + issuer_id, .. } = prepared_transaction_data; let tx_payload = SignedTransactionPayload::new(transaction, unlocks)?; @@ -670,6 +671,7 @@ where payload: tx_payload, inputs_data, mana_rewards, + issuer_id, }; data.verify_semantic(protocol_parameters).inspect_err(|e| { diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index c4e86cc220..699bd5b821 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -90,7 +90,9 @@ where let block_id = match self .submit_basic_block( Some(Payload::from(signed_transaction_data.payload.clone())), - options.as_ref().and_then(|options| options.issuer_id), + signed_transaction_data + .issuer_id + .or_else(|| options.as_ref().and_then(|options| options.issuer_id)), true, ) .await diff --git a/sdk/src/wallet/operations/transaction/send_outputs.rs b/sdk/src/wallet/operations/transaction/send_outputs.rs index c678a8dbcf..7205cb2bb9 100644 --- a/sdk/src/wallet/operations/transaction/send_outputs.rs +++ b/sdk/src/wallet/operations/transaction/send_outputs.rs @@ -71,6 +71,7 @@ where ) -> Result { log::debug!("[TRANSACTION] prepare_send_outputs"); let options = options.into().unwrap_or_default(); + log::debug!("prepare_send_outputs {options:#?}"); let outputs = outputs.into_iter().collect::>(); let prepare_send_outputs_start_time = Instant::now(); let storage_score_params = self.client().get_storage_score_parameters().await?; diff --git a/sdk/src/wallet/operations/transaction/sign_transaction.rs b/sdk/src/wallet/operations/transaction/sign_transaction.rs index a9c4cd6193..7cf6f53531 100644 --- a/sdk/src/wallet/operations/transaction/sign_transaction.rs +++ b/sdk/src/wallet/operations/transaction/sign_transaction.rs @@ -88,6 +88,7 @@ where payload, inputs_data: prepared_transaction_data.inputs_data.clone(), mana_rewards: prepared_transaction_data.mana_rewards.clone(), + issuer_id: prepared_transaction_data.issuer_id, }) } } diff --git a/sdk/tests/client/signing/account.rs b/sdk/tests/client/signing/account.rs index 22e789792c..afbe9baccc 100644 --- a/sdk/tests/client/signing/account.rs +++ b/sdk/tests/client/signing/account.rs @@ -91,6 +91,7 @@ async fn sign_account_state_transition() -> Result<(), Box Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager diff --git a/sdk/tests/client/signing/basic.rs b/sdk/tests/client/signing/basic.rs index 5fb18bf3d8..53aed73a92 100644 --- a/sdk/tests/client/signing/basic.rs +++ b/sdk/tests/client/signing/basic.rs @@ -86,6 +86,7 @@ async fn single_ed25519_unlock() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -192,6 +193,7 @@ async fn ed25519_reference_unlocks() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -306,6 +308,7 @@ async fn two_signature_unlocks() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager diff --git a/sdk/tests/client/signing/delegation.rs b/sdk/tests/client/signing/delegation.rs index c96bd98fb1..fd0bf7b4f6 100644 --- a/sdk/tests/client/signing/delegation.rs +++ b/sdk/tests/client/signing/delegation.rs @@ -96,6 +96,7 @@ async fn valid_creation() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -240,6 +241,7 @@ async fn non_null_id_creation() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -323,6 +325,7 @@ async fn mismatch_amount_creation() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -406,6 +409,7 @@ async fn non_zero_end_epoch_creation() -> Result<(), Box> inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -487,6 +491,7 @@ async fn invalid_start_epoch_creation() -> Result<(), Box inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -578,6 +583,7 @@ async fn delay_not_null_id() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -672,6 +678,7 @@ async fn delay_modified_amount() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -763,6 +770,7 @@ async fn delay_modified_validator() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -854,6 +862,7 @@ async fn delay_modified_start_epoch() -> Result<(), Box> inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager @@ -945,6 +954,7 @@ async fn delay_pre_registration_slot_end_epoch() -> Result<(), Box Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards, + issuer_id: None, }; let unlocks = secret_manager @@ -1123,6 +1134,7 @@ async fn destroy_reward_missing() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager diff --git a/sdk/tests/client/signing/mod.rs b/sdk/tests/client/signing/mod.rs index 8122e5e0d5..a8bc129f49 100644 --- a/sdk/tests/client/signing/mod.rs +++ b/sdk/tests/client/signing/mod.rs @@ -420,6 +420,7 @@ async fn all_combined() -> Result<(), Box> { inputs_data: selected.inputs_data, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager diff --git a/sdk/tests/client/signing/nft.rs b/sdk/tests/client/signing/nft.rs index 0fd011497d..a1cc26c729 100644 --- a/sdk/tests/client/signing/nft.rs +++ b/sdk/tests/client/signing/nft.rs @@ -136,6 +136,7 @@ async fn nft_reference_unlocks() -> Result<(), Box> { inputs_data: inputs, remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, }; let unlocks = secret_manager diff --git a/sdk/tests/wallet/events.rs b/sdk/tests/wallet/events.rs index 34219b52d0..5b1da22de2 100644 --- a/sdk/tests/wallet/events.rs +++ b/sdk/tests/wallet/events.rs @@ -122,6 +122,7 @@ fn wallet_events_serde() { inputs_data: Vec::new(), remainders: Vec::new(), mana_rewards: Default::default(), + issuer_id: None, })), )); diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 81bd494678..09066a8c56 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -35,6 +35,82 @@ async fn send_amount() -> Result<(), Box> { tear_down(storage_path_1) } +#[ignore] +#[tokio::test] +async fn two_implicit_account_transitions() -> Result<(), Box> { + let storage_path = "test-storage/two_implicit_account_transitions"; + setup(storage_path)?; + + let wallet = make_wallet(storage_path, None, None).await?; + request_funds(&wallet).await?; + + iota_sdk::client::request_funds_from_faucet( + crate::wallet::common::FAUCET_URL, + &wallet.implicit_account_creation_address().await?, + ) + .await?; + + // Continue only after funds are received + let mut attempts = 0; + let implicit_account = loop { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + wallet + .sync(Some(iota_sdk::wallet::SyncOptions { + sync_implicit_accounts: true, + ..Default::default() + })) + .await?; + if let Some(account) = wallet.ledger().await.implicit_accounts().next() { + break account.clone(); + } + attempts += 1; + if attempts == 30 { + panic!("Faucet no longer wants to hand over coins"); + } + }; + + let mut tries = 0; + while let Err(iota_sdk::client::ClientError::Node(iota_sdk::client::node_api::error::Error::NotFound(_))) = wallet + .client() + .get_account_congestion( + &iota_sdk::types::block::output::AccountId::from(&implicit_account.output_id), + None, + ) + .await + { + tries += 1; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + if tries > 100 { + panic!("Can't get account for implicit account"); + } + } + + // Using the prepare method so we test that the issuer id is correctly forwarded in the PreparedTransactionData to + // sign_and_submit_transaction() + let prepared_transaction = wallet + .prepare_implicit_account_transition( + &implicit_account.output_id, + iota_sdk::types::block::output::feature::BlockIssuerKeySource::ImplicitAccountAddress, + ) + .await?; + + let transaction = wallet.sign_and_submit_transaction(prepared_transaction, None).await?; + + wallet + .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) + .await?; + + let balance = wallet.sync(None).await.unwrap(); + assert_eq!(balance.accounts().len(), 2); + + for account_id in balance.accounts() { + let congestion_response = wallet.client().get_account_congestion(&account_id, None).await?; + assert_eq!(congestion_response.block_issuance_credits, 0); + } + + tear_down(storage_path) +} + // #[ignore] // #[tokio::test] // async fn send_amount_127_outputs() -> Result<(), Box> {