From d68c18e6917b500a4672a71775576b1f096f03ed Mon Sep 17 00:00:00 2001 From: aspect Date: Wed, 17 Jul 2024 10:39:19 +0300 Subject: [PATCH 01/10] Kip9 updates to WASM/wallet framework mass calc (#66) * WIP * update kip9 processing in WASM mass calculator --- consensus/core/src/mass/mod.rs | 11 ++++++ consensus/src/processes/mass.rs | 12 +------ wallet/core/src/imports.rs | 1 - wallet/core/src/tx/generator/generator.rs | 6 +++- wallet/core/src/tx/mass.rs | 44 +++++++++++++++-------- wallet/core/src/utxo/settings.rs | 17 ++++----- 6 files changed, 55 insertions(+), 36 deletions(-) diff --git a/consensus/core/src/mass/mod.rs b/consensus/core/src/mass/mod.rs index 6e348299c..f58d10466 100644 --- a/consensus/core/src/mass/mod.rs +++ b/consensus/core/src/mass/mod.rs @@ -4,6 +4,17 @@ use crate::{ }; use kaspa_hashes::HASH_SIZE; +/// Temp enum for the transition phases of KIP9 +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Kip9Version { + /// Initial KIP9 mass calculation, w/o the relaxed formula and summing storage mass and compute mass + Alpha, + + /// Currently proposed KIP9 mass calculation, with the relaxed formula (for the cases `|O| = 1 OR |O| <= |I| <= 2`), + /// and using a maximum operator over storage and compute mass + Beta, +} + // transaction_estimated_serialized_size is the estimated size of a transaction in some // serialization. This has to be deterministic, but not necessarily accurate, since // it's only used as the size component in the transaction and block mass limit diff --git a/consensus/src/processes/mass.rs b/consensus/src/processes/mass.rs index 8bb5f3339..e6198d346 100644 --- a/consensus/src/processes/mass.rs +++ b/consensus/src/processes/mass.rs @@ -1,19 +1,9 @@ +pub use kaspa_consensus_core::mass::Kip9Version; use kaspa_consensus_core::{ mass::transaction_estimated_serialized_size, tx::{Transaction, VerifiableTransaction}, }; -/// Temp enum for the transition phases of KIP9 -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum Kip9Version { - /// Initial KIP9 mass calculation, w/o the relaxed formula and summing storage mass and compute mass - Alpha, - - /// Currently proposed KIP9 mass calculation, with the relaxed formula (for the cases `|O| = 1 OR |O| <= |I| <= 2`), - /// and using a maximum operator over storage and compute mass - Beta, -} - // TODO (aspect) - review and potentially merge this with the new MassCalculator currently located in the wallet core // (i.e. migrate mass calculator from wallet core here or to consensus core) #[derive(Clone)] diff --git a/wallet/core/src/imports.rs b/wallet/core/src/imports.rs index 2d2ce79fd..ede68cb40 100644 --- a/wallet/core/src/imports.rs +++ b/wallet/core/src/imports.rs @@ -17,7 +17,6 @@ pub use crate::rpc::Rpc; pub use crate::rpc::{DynRpcApi, RpcCtl}; pub use crate::serializer::*; pub use crate::storage::*; -pub use crate::tx::MassCombinationStrategy; pub use crate::utxo::balance::Balance; pub use crate::utxo::scan::{Scan, ScanExtent}; pub use crate::utxo::{Maturity, NetworkParams, OutgoingTransaction, UtxoContext, UtxoEntryReference, UtxoProcessor}; diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index eae634651..87dacfe11 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -65,6 +65,7 @@ use crate::tx::{ use crate::utxo::{NetworkParams, UtxoContext, UtxoEntryReference}; use kaspa_consensus_client::UtxoEntry; use kaspa_consensus_core::constants::UNACCEPTED_DAA_SCORE; +use kaspa_consensus_core::mass::Kip9Version; use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE; use kaspa_consensus_core::tx::{Transaction, TransactionInput, TransactionOutpoint, TransactionOutput}; use kaspa_txscript::pay_to_address_script; @@ -862,8 +863,11 @@ impl Generator { calc.calc_storage_mass_output_harmonic_single(change_value) + self.inner.final_transaction_outputs_harmonic; let storage_mass_with_change = self.calc_storage_mass(data, output_harmonic_with_change); + // TODO - review and potentially simplify: + // this profiles the storage mass with change and without change + // and decides which one to use based on the fees if storage_mass_with_change == 0 - || (self.inner.network_params.mass_combination_strategy() == MassCombinationStrategy::Max + || (self.inner.network_params.kip9_version() == Kip9Version::Beta // max(compute vs storage) && storage_mass_with_change < compute_mass_with_change) { 0 diff --git a/wallet/core/src/tx/mass.rs b/wallet/core/src/tx/mass.rs index c76d10905..dd494b168 100644 --- a/wallet/core/src/tx/mass.rs +++ b/wallet/core/src/tx/mass.rs @@ -4,18 +4,11 @@ use crate::utxo::NetworkParams; use kaspa_consensus_client::UtxoEntryReference; +use kaspa_consensus_core::mass::Kip9Version; use kaspa_consensus_core::tx::{Transaction, TransactionInput, TransactionOutput, SCRIPT_VECTOR_SIZE}; use kaspa_consensus_core::{config::params::Params, constants::*, subnets::SUBNETWORK_ID_SIZE}; use kaspa_hashes::HASH_SIZE; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MassCombinationStrategy { - /// `MassCombinator::Add` adds the storage and compute mass. - Add, - /// `MassCombinator::Max` returns the maximum of the storage and compute mass. - Max, -} - // pub const ECDSA_SIGNATURE_SIZE: u64 = 64; // pub const SCHNORR_SIGNATURE_SIZE: u64 = 64; pub const SIGNATURE_SIZE: u64 = 1 + 64 + 1; //1 byte for OP_DATA_65 + 64 (length of signature) + 1 byte for sig hash type @@ -222,7 +215,7 @@ pub struct MassCalculator { mass_per_script_pub_key_byte: u64, mass_per_sig_op: u64, storage_mass_parameter: u64, - mass_combination_strategy: MassCombinationStrategy, + kip9_version: Kip9Version, } impl MassCalculator { @@ -232,7 +225,7 @@ impl MassCalculator { mass_per_script_pub_key_byte: consensus_params.mass_per_script_pub_key_byte, mass_per_sig_op: consensus_params.mass_per_sig_op, storage_mass_parameter: consensus_params.storage_mass_parameter, - mass_combination_strategy: network_params.mass_combination_strategy(), + kip9_version: network_params.kip9_version(), } } @@ -307,9 +300,9 @@ impl MassCalculator { } pub fn combine_mass(&self, compute_mass: u64, storage_mass: u64) -> u64 { - match self.mass_combination_strategy { - MassCombinationStrategy::Add => compute_mass + storage_mass, - MassCombinationStrategy::Max => std::cmp::max(compute_mass, storage_mass), + match self.kip9_version { + Kip9Version::Alpha => compute_mass + storage_mass, + Kip9Version::Beta => std::cmp::max(compute_mass, storage_mass), } } @@ -330,7 +323,7 @@ impl MassCalculator { input values, H(S) := |S|/sum_{s in S} 1 / s is the harmonic mean over the set S and A(S) := sum_{s in S} / |S| is the arithmetic mean. - See the (to date unpublished) KIP-0009 for more details + See KIP-0009 for more details */ // Since we are doing integer division, we perform the multiplication with C over the inner @@ -338,15 +331,36 @@ impl MassCalculator { // // If sum of fractions overflowed (nearly impossible, requires 10^7 outputs for C = 10^12), // we return `None` indicating mass is incomputable + // + // Note: in theory this can be tighten by subtracting input mass in the process (possibly avoiding the overflow), + // however the overflow case is so unpractical with current mass limits so we avoid the hassle let harmonic_outs = outputs .iter() .map(|out| self.storage_mass_parameter / out.value) .try_fold(0u64, |total, current| total.checked_add(current))?; // C·|O|/H(O) + let outs_len = outputs.len() as u64; + let ins_len = inputs.len() as u64; + + /* + KIP-0009 relaxed formula for the cases |O| = 1 OR |O| <= |I| <= 2: + max( 0 , C·( |O|/H(O) - |I|/H(I) ) ) + + Note: in the case |I| = 1 both formulas are equal, yet the following code (harmonic_ins) is a bit more efficient. + Hence, we transform the condition to |O| = 1 OR |I| = 1 OR |O| = |I| = 2 which is equivalent (and faster). + */ + + if self.kip9_version == Kip9Version::Beta && (outs_len == 1 || ins_len == 1 || (outs_len == 2 && ins_len == 2)) { + let harmonic_ins = inputs + .iter() + .map(|entry| self.storage_mass_parameter / entry.amount()) + .fold(0u64, |total, current| total.saturating_add(current)); // C·|I|/H(I) + return Some(harmonic_outs.saturating_sub(harmonic_ins)); // max( 0 , C·( |O|/H(O) - |I|/H(I) ) ); + } + // Total supply is bounded, so a sum of existing UTXO entries cannot overflow (nor can it be zero) let sum_ins = inputs.iter().map(|entry| entry.amount()).sum::(); // |I|·A(I) - let ins_len = inputs.len() as u64; let mean_ins = sum_ins / ins_len; // Inner fraction must be with C and over the mean value, in order to maximize precision. diff --git a/wallet/core/src/utxo/settings.rs b/wallet/core/src/utxo/settings.rs index caa8032eb..e78abf8b7 100644 --- a/wallet/core/src/utxo/settings.rs +++ b/wallet/core/src/utxo/settings.rs @@ -4,13 +4,14 @@ //! use crate::imports::*; +use kaspa_consensus_core::mass::Kip9Version; #[derive(Debug)] pub struct Inner { pub coinbase_transaction_maturity_period_daa: AtomicU64, pub coinbase_transaction_stasis_period_daa: u64, pub user_transaction_maturity_period_daa: AtomicU64, - pub mass_combination_strategy: MassCombinationStrategy, + pub kip9_version: Kip9Version, pub additional_compound_transaction_mass: u64, } @@ -36,8 +37,8 @@ impl NetworkParams { } #[inline] - pub fn mass_combination_strategy(&self) -> MassCombinationStrategy { - self.inner.mass_combination_strategy + pub fn kip9_version(&self) -> Kip9Version { + self.inner.kip9_version } #[inline] @@ -60,7 +61,7 @@ lazy_static::lazy_static! { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, + kip9_version: Kip9Version::Beta, additional_compound_transaction_mass: 0, }), }; @@ -72,7 +73,7 @@ lazy_static::lazy_static! { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, + kip9_version: Kip9Version::Beta, additional_compound_transaction_mass: 0, }), }; @@ -84,7 +85,7 @@ lazy_static::lazy_static! { coinbase_transaction_maturity_period_daa: AtomicU64::new(1_000), coinbase_transaction_stasis_period_daa: 500, user_transaction_maturity_period_daa: AtomicU64::new(100), - mass_combination_strategy: MassCombinationStrategy::Max, + kip9_version: Kip9Version::Alpha, additional_compound_transaction_mass: 100, }), }; @@ -96,7 +97,7 @@ lazy_static::lazy_static! { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, + kip9_version: Kip9Version::Alpha, additional_compound_transaction_mass: 0, }), }; @@ -108,7 +109,7 @@ lazy_static::lazy_static! { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, + kip9_version: Kip9Version::Beta, additional_compound_transaction_mass: 0, }), }; From 1c9299b3cbc0cb403a0d8402c2f899c9e3f5c153 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 18 Jul 2024 23:37:08 +0530 Subject: [PATCH 02/10] XPrv.toPrivateKey support --- wallet/keys/src/xprv.rs | 6 ++++++ .../nodejs/javascript/general/derivation.js | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/wallet/keys/src/xprv.rs b/wallet/keys/src/xprv.rs index bc4b681cf..7284898cc 100644 --- a/wallet/keys/src/xprv.rs +++ b/wallet/keys/src/xprv.rs @@ -70,6 +70,12 @@ impl XPrv { let public_key = self.inner.public_key(); Ok(public_key.into()) } + + #[wasm_bindgen(js_name = toPrivateKey)] + pub fn to_private_key(&self) -> Result { + let private_key = self.inner.private_key(); + Ok(private_key.into()) + } } impl<'a> From<&'a XPrv> for &'a ExtendedPrivateKey { diff --git a/wasm/examples/nodejs/javascript/general/derivation.js b/wasm/examples/nodejs/javascript/general/derivation.js index f92508c88..942f665f9 100644 --- a/wasm/examples/nodejs/javascript/general/derivation.js +++ b/wasm/examples/nodejs/javascript/general/derivation.js @@ -21,6 +21,7 @@ kaspa.initConsolePanicHook(); let xPrv = new XPrv(seed); // derive full path upto second address of receive wallet let pubkey1 = xPrv.derivePath("m/44'/111111'/0'/0/1").toXPub().toPublicKey(); + console.log("publickey", pubkey1.toString()) console.log("address", pubkey1.toAddress(NetworkType.Mainnet)); // create receive wallet @@ -28,17 +29,25 @@ kaspa.initConsolePanicHook(); // derive receive wallet for second address let pubkey2 = receiveWalletXPub.deriveChild(1, false).toPublicKey(); console.log("address", pubkey2.toAddress(NetworkType.Mainnet)); + if (pubkey1.toString() != pubkey2.toString()){ + throw new Error("pubkey2 dont match") + } // create change wallet let changeWalletXPub = xPrv.derivePath("m/44'/111111'/0'/1").toXPub(); // derive change wallet for first address let pubkey3 = changeWalletXPub.deriveChild(0, false).toPublicKey(); - console.log("address", pubkey2.toAddress(NetworkType.Mainnet)); + console.log("change address", pubkey3.toAddress(NetworkType.Mainnet)); + // --- - if (pubkey1.toString() != pubkey2.toString()){ - throw new Error("pubkeyes dont match") + //drive address via private key + let privateKey = xPrv.derivePath("m/44'/111111'/0'/0/1").toPrivateKey(); + console.log("address via private key", privateKey.toAddress(NetworkType.Mainnet)) + console.log("privatekey", privateKey.toString()); + let pubkey4 = privateKey.toPublicKey(); + if (pubkey1.toString() != pubkey4.toString()){ + throw new Error("pubkey4 dont match") } - // --- // xprv with ktrv prefix const ktrv = xPrv.intoString("ktrv"); From 3f6b398ab66ed73a555b4eeb07754fbf35cc63d8 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Tue, 23 Jul 2024 21:20:13 +0300 Subject: [PATCH 03/10] replace lazy_static with OnceLock --- Cargo.lock | 1 - Cargo.toml | 1 - wallet/core/Cargo.toml | 1 - wallet/core/src/imports.rs | 2 +- wallet/core/src/utxo/settings.rs | 52 +++++++++++++++++++------------- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index baaa434f0..2d48f62e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3493,7 +3493,6 @@ dependencies = [ "kaspa-wasm-core", "kaspa-wrpc-client", "kaspa-wrpc-wasm", - "lazy_static", "md-5", "pad", "pbkdf2", diff --git a/Cargo.toml b/Cargo.toml index 885cf9edd..607c5c2c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -192,7 +192,6 @@ ipnet = "2.9.0" itertools = "0.11.0" js-sys = "0.3.67" keccak = "0.1.4" -lazy_static = "1.4.0" local-ip-address = "0.5.6" log = "0.4.20" log4rs = "1.2.0" diff --git a/wallet/core/Cargo.toml b/wallet/core/Cargo.toml index 1e528e369..9a6c4cfa6 100644 --- a/wallet/core/Cargo.toml +++ b/wallet/core/Cargo.toml @@ -74,7 +74,6 @@ kaspa-wallet-macros.workspace = true kaspa-wasm-core.workspace = true kaspa-wrpc-client.workspace = true kaspa-wrpc-wasm.workspace = true -lazy_static.workspace = true md-5.workspace = true pad.workspace = true pbkdf2.workspace = true diff --git a/wallet/core/src/imports.rs b/wallet/core/src/imports.rs index 2d2ce79fd..697e7902d 100644 --- a/wallet/core/src/imports.rs +++ b/wallet/core/src/imports.rs @@ -49,7 +49,7 @@ pub use std::collections::{HashMap, HashSet}; pub use std::pin::Pin; pub use std::str::FromStr; pub use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; -pub use std::sync::{Arc, Mutex, MutexGuard, RwLock}; +pub use std::sync::{Arc, Mutex, MutexGuard, OnceLock, RwLock}; pub use std::task::{Context, Poll}; pub use wasm_bindgen::prelude::*; pub use workflow_core::prelude::*; diff --git a/wallet/core/src/utxo/settings.rs b/wallet/core/src/utxo/settings.rs index caa8032eb..61767483e 100644 --- a/wallet/core/src/utxo/settings.rs +++ b/wallet/core/src/utxo/settings.rs @@ -54,8 +54,10 @@ impl NetworkParams { } } -lazy_static::lazy_static! { - pub static ref MAINNET_NETWORK_PARAMS: NetworkParams = NetworkParams { +static MAINNET_NETWORK_PARAMS: OnceLock = OnceLock::new(); + +pub fn mainnet_network_params() -> &'static NetworkParams { + MAINNET_NETWORK_PARAMS.get_or_init(|| NetworkParams { inner: Arc::new(Inner { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, @@ -63,11 +65,13 @@ lazy_static::lazy_static! { mass_combination_strategy: MassCombinationStrategy::Max, additional_compound_transaction_mass: 0, }), - }; + }) } -lazy_static::lazy_static! { - pub static ref TESTNET10_NETWORK_PARAMS: NetworkParams = NetworkParams { +static TESTNET10_NETWORK_PARAMS: OnceLock = OnceLock::new(); + +pub fn testnet10_network_params() -> &'static NetworkParams { + TESTNET10_NETWORK_PARAMS.get_or_init(|| NetworkParams { inner: Arc::new(Inner { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, @@ -75,23 +79,27 @@ lazy_static::lazy_static! { mass_combination_strategy: MassCombinationStrategy::Max, additional_compound_transaction_mass: 0, }), - }; + }) } -lazy_static::lazy_static! { - pub static ref TESTNET11_NETWORK_PARAMS: NetworkParams = NetworkParams { +static TESTNET11_NETWORK_PARAMS: OnceLock = OnceLock::new(); + +pub fn testnet11_network_params() -> &'static NetworkParams { + TESTNET11_NETWORK_PARAMS.get_or_init(|| NetworkParams { inner: Arc::new(Inner { coinbase_transaction_maturity_period_daa: AtomicU64::new(1_000), coinbase_transaction_stasis_period_daa: 500, user_transaction_maturity_period_daa: AtomicU64::new(100), mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 100, + additional_compound_transaction_mass: 0, }), - }; + }) } -lazy_static::lazy_static! { - pub static ref SIMNET_NETWORK_PARAMS: NetworkParams = NetworkParams { +static SIMNET_NETWORK_PARAMS: OnceLock = OnceLock::new(); + +pub fn simnet_network_params() -> &'static NetworkParams { + SIMNET_NETWORK_PARAMS.get_or_init(|| NetworkParams { inner: Arc::new(Inner { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, @@ -99,11 +107,13 @@ lazy_static::lazy_static! { mass_combination_strategy: MassCombinationStrategy::Max, additional_compound_transaction_mass: 0, }), - }; + }) } -lazy_static::lazy_static! { - pub static ref DEVNET_NETWORK_PARAMS: NetworkParams = NetworkParams { +static DEVNET_NETWORK_PARAMS: OnceLock = OnceLock::new(); + +pub fn devnet_network_params() -> &'static NetworkParams { + DEVNET_NETWORK_PARAMS.get_or_init(|| NetworkParams { inner: Arc::new(Inner { coinbase_transaction_maturity_period_daa: AtomicU64::new(100), coinbase_transaction_stasis_period_daa: 50, @@ -111,21 +121,21 @@ lazy_static::lazy_static! { mass_combination_strategy: MassCombinationStrategy::Max, additional_compound_transaction_mass: 0, }), - }; + }) } impl From for NetworkParams { fn from(value: NetworkId) -> Self { match value.network_type { - NetworkType::Mainnet => MAINNET_NETWORK_PARAMS.clone(), + NetworkType::Mainnet => mainnet_network_params().clone(), NetworkType::Testnet => match value.suffix { - Some(10) => TESTNET10_NETWORK_PARAMS.clone(), - Some(11) => TESTNET11_NETWORK_PARAMS.clone(), + Some(10) => testnet10_network_params().clone(), + Some(11) => testnet11_network_params().clone(), Some(x) => panic!("Testnet suffix {} is not supported", x), None => panic!("Testnet suffix not provided"), }, - NetworkType::Devnet => DEVNET_NETWORK_PARAMS.clone(), - NetworkType::Simnet => SIMNET_NETWORK_PARAMS.clone(), + NetworkType::Devnet => devnet_network_params().clone(), + NetworkType::Simnet => simnet_network_params().clone(), } } } From c6c54ed641a53f1aebb2a2c97d01b23402877834 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Tue, 23 Jul 2024 21:38:59 +0300 Subject: [PATCH 04/10] remove NetworkParams Inner --- wallet/core/src/tx/generator/generator.rs | 6 +- wallet/core/src/utxo/context.rs | 6 +- wallet/core/src/utxo/processor.rs | 11 +-- wallet/core/src/utxo/scan.rs | 4 +- wallet/core/src/utxo/settings.rs | 95 ++++++++++------------- wallet/core/src/wasm/tx/mass.rs | 2 +- 6 files changed, 55 insertions(+), 69 deletions(-) diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index eae634651..0bfd032e3 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -265,7 +265,7 @@ struct Inner { // Current network id network_id: NetworkId, // Current network params - network_params: NetworkParams, + network_params: &'static NetworkParams, // Source Utxo Context (Used for source UtxoEntry aggregation) source_utxo_context: Option, @@ -356,7 +356,7 @@ impl Generator { let network_type = NetworkType::from(network_id); let network_params = NetworkParams::from(network_id); - let mass_calculator = MassCalculator::new(&network_id.into(), &network_params); + let mass_calculator = MassCalculator::new(&network_id.into(), network_params); let (final_transaction_outputs, final_transaction_amount) = match final_transaction_destination { PaymentDestination::Change => { @@ -476,7 +476,7 @@ impl Generator { /// Returns current [`NetworkParams`] pub fn network_params(&self) -> &NetworkParams { - &self.inner.network_params + self.inner.network_params } /// The underlying [`UtxoContext`] (if available). diff --git a/wallet/core/src/utxo/context.rs b/wallet/core/src/utxo/context.rs index 47a5e9f32..349e1be9f 100644 --- a/wallet/core/src/utxo/context.rs +++ b/wallet/core/src/utxo/context.rs @@ -299,7 +299,7 @@ impl UtxoContext { context.mature.sorted_insert_binary_asc_by_key(utxo_entry.clone(), |entry| entry.amount_as_ref()); } else { let params = NetworkParams::from(self.processor().network_id()?); - match utxo_entry.maturity(¶ms, current_daa_score) { + match utxo_entry.maturity(params, current_daa_score) { Maturity::Stasis => { context.stasis.insert(utxo_entry.id().clone(), utxo_entry.clone()); self.processor() @@ -428,7 +428,7 @@ impl UtxoContext { for utxo_entry in utxo_entries.into_iter() { if let std::collections::hash_map::Entry::Vacant(e) = context.map.entry(utxo_entry.id()) { e.insert(utxo_entry.clone()); - match utxo_entry.maturity(¶ms, current_daa_score) { + match utxo_entry.maturity(params, current_daa_score) { Maturity::Stasis => { context.stasis.insert(utxo_entry.id().clone(), utxo_entry.clone()); self.processor() @@ -531,7 +531,7 @@ impl UtxoContext { let force_maturity_if_outgoing = outgoing_transaction.is_some(); let is_coinbase_stasis = - utxos.first().map(|utxo| matches!(utxo.maturity(¶ms, current_daa_score), Maturity::Stasis)).unwrap_or_default(); + utxos.first().map(|utxo| matches!(utxo.maturity(params, current_daa_score), Maturity::Stasis)).unwrap_or_default(); let is_batch = outgoing_transaction.as_ref().map_or_else(|| false, |tx| tx.is_batch()); if !is_batch { for utxo in utxos.iter() { diff --git a/wallet/core/src/utxo/processor.rs b/wallet/core/src/utxo/processor.rs index bfda16a6f..f6271e5c1 100644 --- a/wallet/core/src/utxo/processor.rs +++ b/wallet/core/src/utxo/processor.rs @@ -181,10 +181,11 @@ impl UtxoProcessor { (*self.inner.network_id.lock().unwrap()).ok_or(Error::MissingNetworkId) } - // pub fn network_params(&self) -> Result<&'static NetworkParams> { - pub fn network_params(&self) -> Result { + pub fn network_params(&self) -> Result<&'static NetworkParams> { + // pub fn network_params(&self) -> Result { let network_id = (*self.inner.network_id.lock().unwrap()).ok_or(Error::MissingNetworkId)?; - Ok(network_id.into()) + Ok(NetworkParams::from(network_id)) + // Ok(network_id.into()) } pub fn pending(&self) -> &DashMap { @@ -274,7 +275,7 @@ impl UtxoProcessor { // scan and remove any pending entries that gained maturity let mut mature_entries = vec![]; let pending_entries = &self.inner.pending; - pending_entries.retain(|_, pending_entry| match pending_entry.maturity(¶ms, current_daa_score) { + pending_entries.retain(|_, pending_entry| match pending_entry.maturity(params, current_daa_score) { Maturity::Confirmed => { mature_entries.push(pending_entry.clone()); false @@ -287,7 +288,7 @@ impl UtxoProcessor { let mut revived_entries = vec![]; let stasis_entries = &self.inner.stasis; stasis_entries.retain(|_, stasis_entry| { - match stasis_entry.maturity(¶ms, current_daa_score) { + match stasis_entry.maturity(params, current_daa_score) { Maturity::Confirmed => { mature_entries.push(stasis_entry.clone()); false diff --git a/wallet/core/src/utxo/scan.rs b/wallet/core/src/utxo/scan.rs index fda8ea67c..f01257c96 100644 --- a/wallet/core/src/utxo/scan.rs +++ b/wallet/core/src/utxo/scan.rs @@ -107,7 +107,7 @@ impl Scan { } let balance: Balance = refs.iter().fold(Balance::default(), |mut balance, r| { - let entry_balance = r.balance(¶ms, self.current_daa_score); + let entry_balance = r.balance(params, self.current_daa_score); balance.mature += entry_balance.mature; balance.pending += entry_balance.pending; balance.mature_utxo_count += entry_balance.mature_utxo_count; @@ -151,7 +151,7 @@ impl Scan { let refs: Vec = resp.into_iter().map(UtxoEntryReference::from).collect(); let balance: Balance = refs.iter().fold(Balance::default(), |mut balance, r| { - let entry_balance = r.balance(¶ms, self.current_daa_score); + let entry_balance = r.balance(params, self.current_daa_score); balance.mature += entry_balance.mature; balance.pending += entry_balance.pending; balance.mature_utxo_count += entry_balance.mature_utxo_count; diff --git a/wallet/core/src/utxo/settings.rs b/wallet/core/src/utxo/settings.rs index 61767483e..1d29f71b2 100644 --- a/wallet/core/src/utxo/settings.rs +++ b/wallet/core/src/utxo/settings.rs @@ -6,7 +6,7 @@ use crate::imports::*; #[derive(Debug)] -pub struct Inner { +pub struct NetworkParams { pub coinbase_transaction_maturity_period_daa: AtomicU64, pub coinbase_transaction_stasis_period_daa: u64, pub user_transaction_maturity_period_daa: AtomicU64, @@ -14,43 +14,38 @@ pub struct Inner { pub additional_compound_transaction_mass: u64, } -#[derive(Debug, Clone)] -pub struct NetworkParams { - inner: Arc, -} - impl NetworkParams { #[inline] pub fn coinbase_transaction_maturity_period_daa(&self) -> u64 { - self.inner.coinbase_transaction_maturity_period_daa.load(Ordering::Relaxed) + self.coinbase_transaction_maturity_period_daa.load(Ordering::Relaxed) } #[inline] pub fn coinbase_transaction_stasis_period_daa(&self) -> u64 { - self.inner.coinbase_transaction_stasis_period_daa + self.coinbase_transaction_stasis_period_daa } #[inline] pub fn user_transaction_maturity_period_daa(&self) -> u64 { - self.inner.user_transaction_maturity_period_daa.load(Ordering::Relaxed) + self.user_transaction_maturity_period_daa.load(Ordering::Relaxed) } #[inline] pub fn mass_combination_strategy(&self) -> MassCombinationStrategy { - self.inner.mass_combination_strategy + self.mass_combination_strategy } #[inline] pub fn additional_compound_transaction_mass(&self) -> u64 { - self.inner.additional_compound_transaction_mass + self.additional_compound_transaction_mass } pub fn set_coinbase_transaction_maturity_period_daa(&self, value: u64) { - self.inner.coinbase_transaction_maturity_period_daa.store(value, Ordering::Relaxed); + self.coinbase_transaction_maturity_period_daa.store(value, Ordering::Relaxed); } pub fn set_user_transaction_maturity_period_daa(&self, value: u64) { - self.inner.user_transaction_maturity_period_daa.store(value, Ordering::Relaxed); + self.user_transaction_maturity_period_daa.store(value, Ordering::Relaxed); } } @@ -58,13 +53,11 @@ static MAINNET_NETWORK_PARAMS: OnceLock = OnceLock::new(); pub fn mainnet_network_params() -> &'static NetworkParams { MAINNET_NETWORK_PARAMS.get_or_init(|| NetworkParams { - inner: Arc::new(Inner { - coinbase_transaction_maturity_period_daa: AtomicU64::new(100), - coinbase_transaction_stasis_period_daa: 50, - user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, - }), + coinbase_transaction_maturity_period_daa: AtomicU64::new(100), + coinbase_transaction_stasis_period_daa: 50, + user_transaction_maturity_period_daa: AtomicU64::new(10), + mass_combination_strategy: MassCombinationStrategy::Max, + additional_compound_transaction_mass: 0, }) } @@ -72,13 +65,11 @@ static TESTNET10_NETWORK_PARAMS: OnceLock = OnceLock::new(); pub fn testnet10_network_params() -> &'static NetworkParams { TESTNET10_NETWORK_PARAMS.get_or_init(|| NetworkParams { - inner: Arc::new(Inner { - coinbase_transaction_maturity_period_daa: AtomicU64::new(100), - coinbase_transaction_stasis_period_daa: 50, - user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, - }), + coinbase_transaction_maturity_period_daa: AtomicU64::new(100), + coinbase_transaction_stasis_period_daa: 50, + user_transaction_maturity_period_daa: AtomicU64::new(10), + mass_combination_strategy: MassCombinationStrategy::Max, + additional_compound_transaction_mass: 0, }) } @@ -86,13 +77,11 @@ static TESTNET11_NETWORK_PARAMS: OnceLock = OnceLock::new(); pub fn testnet11_network_params() -> &'static NetworkParams { TESTNET11_NETWORK_PARAMS.get_or_init(|| NetworkParams { - inner: Arc::new(Inner { - coinbase_transaction_maturity_period_daa: AtomicU64::new(1_000), - coinbase_transaction_stasis_period_daa: 500, - user_transaction_maturity_period_daa: AtomicU64::new(100), - mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, - }), + coinbase_transaction_maturity_period_daa: AtomicU64::new(1_000), + coinbase_transaction_stasis_period_daa: 500, + user_transaction_maturity_period_daa: AtomicU64::new(100), + mass_combination_strategy: MassCombinationStrategy::Max, + additional_compound_transaction_mass: 0, }) } @@ -100,13 +89,11 @@ static SIMNET_NETWORK_PARAMS: OnceLock = OnceLock::new(); pub fn simnet_network_params() -> &'static NetworkParams { SIMNET_NETWORK_PARAMS.get_or_init(|| NetworkParams { - inner: Arc::new(Inner { - coinbase_transaction_maturity_period_daa: AtomicU64::new(100), - coinbase_transaction_stasis_period_daa: 50, - user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, - }), + coinbase_transaction_maturity_period_daa: AtomicU64::new(100), + coinbase_transaction_stasis_period_daa: 50, + user_transaction_maturity_period_daa: AtomicU64::new(10), + mass_combination_strategy: MassCombinationStrategy::Max, + additional_compound_transaction_mass: 0, }) } @@ -114,28 +101,26 @@ static DEVNET_NETWORK_PARAMS: OnceLock = OnceLock::new(); pub fn devnet_network_params() -> &'static NetworkParams { DEVNET_NETWORK_PARAMS.get_or_init(|| NetworkParams { - inner: Arc::new(Inner { - coinbase_transaction_maturity_period_daa: AtomicU64::new(100), - coinbase_transaction_stasis_period_daa: 50, - user_transaction_maturity_period_daa: AtomicU64::new(10), - mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, - }), + coinbase_transaction_maturity_period_daa: AtomicU64::new(100), + coinbase_transaction_stasis_period_daa: 50, + user_transaction_maturity_period_daa: AtomicU64::new(10), + mass_combination_strategy: MassCombinationStrategy::Max, + additional_compound_transaction_mass: 0, }) } -impl From for NetworkParams { - fn from(value: NetworkId) -> Self { +impl NetworkParams { + pub fn from(value: NetworkId) -> &'static NetworkParams { match value.network_type { - NetworkType::Mainnet => mainnet_network_params().clone(), + NetworkType::Mainnet => mainnet_network_params(), NetworkType::Testnet => match value.suffix { - Some(10) => testnet10_network_params().clone(), - Some(11) => testnet11_network_params().clone(), + Some(10) => testnet10_network_params(), + Some(11) => testnet11_network_params(), Some(x) => panic!("Testnet suffix {} is not supported", x), None => panic!("Testnet suffix not provided"), }, - NetworkType::Devnet => devnet_network_params().clone(), - NetworkType::Simnet => simnet_network_params().clone(), + NetworkType::Devnet => devnet_network_params(), + NetworkType::Simnet => simnet_network_params(), } } } diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index cc522fd8e..5c3193fc0 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -21,7 +21,7 @@ impl MassCalculator { pub fn new(cp: ConsensusParams) -> Self { let consensus_params = Params::from(cp); let network_params = NetworkParams::from(consensus_params.net); - Self { mc: Arc::new(mass::MassCalculator::new(&consensus_params, &network_params)) } + Self { mc: Arc::new(mass::MassCalculator::new(&consensus_params, network_params)) } } #[wasm_bindgen(js_name=isDust)] From ed7f056625664f76dca4b896c1d26f39c93d97cf Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 26 Jul 2024 09:59:24 +0300 Subject: [PATCH 05/10] make signatureScript optional on ITransactionInput (WASM32) --- consensus/client/src/input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/client/src/input.rs b/consensus/client/src/input.rs index d25e5f7d9..523927bd1 100644 --- a/consensus/client/src/input.rs +++ b/consensus/client/src/input.rs @@ -13,7 +13,7 @@ const TS_TRANSACTION: &'static str = r#" */ export interface ITransactionInput { previousOutpoint: ITransactionOutpoint; - signatureScript: HexString; + signatureScript?: HexString; sequence: bigint; sigOpCount: number; utxo?: UtxoEntryReference; @@ -187,7 +187,7 @@ impl TryCastFromJs for TransactionInput { Self::resolve_cast(&value, || { if let Some(object) = Object::try_from(value.as_ref()) { let previous_outpoint: TransactionOutpoint = object.get_value("previousOutpoint")?.as_ref().try_into()?; - let signature_script = object.get_vec_u8("signatureScript")?; + let signature_script = object.get_vec_u8("signatureScript").unwrap_or_default(); let sequence = object.get_u64("sequence")?; let sig_op_count = object.get_u8("sigOpCount")?; let utxo = object.try_get_cast::("utxo")?.map(Cast::into_owned); From fcdb1659dfbe0c0838ca288b47b3e5b4ea6b366f Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 26 Jul 2024 10:02:00 +0300 Subject: [PATCH 06/10] WIP mass calc (WASM32) --- consensus/client/src/sign.rs | 2 +- consensus/client/src/transaction.rs | 39 +++++++++++++++++++++++----- consensus/client/src/utxo.rs | 6 +++++ wallet/core/src/tx/generator/test.rs | 4 +-- wallet/core/src/tx/mass.rs | 14 ++++------ wallet/core/src/wasm/signer.rs | 4 +-- wallet/core/src/wasm/tx/mass.rs | 28 +++++++++++++++++--- 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/consensus/client/src/sign.rs b/consensus/client/src/sign.rs index fdab66a60..b4afc11e6 100644 --- a/consensus/client/src/sign.rs +++ b/consensus/client/src/sign.rs @@ -44,7 +44,7 @@ pub fn sign_with_multiple_v3(tx: Transaction, privkeys: &[[u8; 32]]) -> crate::r let mut additional_signatures_required = false; { let input_len = tx.inner().inputs.len(); - let (cctx, utxos) = tx.tx_and_utxos(); + let (cctx, utxos) = tx.tx_and_utxos()?; let populated_transaction = PopulatedTransaction::new(&cctx, utxos); for i in 0..input_len { let script_pub_key = match tx.inner().inputs[i].script_public_key() { diff --git a/consensus/client/src/transaction.rs b/consensus/client/src/transaction.rs index d03e6a512..7f51416c7 100644 --- a/consensus/client/src/transaction.rs +++ b/consensus/client/src/transaction.rs @@ -359,18 +359,18 @@ impl Transaction { }) } - pub fn tx_and_utxos(&self) -> (cctx::Transaction, Vec) { - let mut utxos = vec![]; + pub fn tx_and_utxos(&self) -> Result<(cctx::Transaction, Vec)> { + let mut inputs = vec![]; let inner = self.inner(); - let inputs: Vec = inner + let utxos: Vec = inner .inputs .clone() .into_iter() .map(|input| { - utxos.push((&input.get_utxo().unwrap().entry()).into()); - input.as_ref().into() + inputs.push(input.as_ref().into()); + Ok(input.get_utxo().ok_or(Error::MissingUtxoEntry)?.entry().as_ref().into()) }) - .collect::>(); + .collect::>>()?; let outputs: Vec = inner.outputs.clone().into_iter().map(|output| output.as_ref().into()).collect::>(); let tx = cctx::Transaction::new( @@ -383,7 +383,32 @@ impl Transaction { inner.payload.clone(), ); - (tx, utxos) + Ok((tx, utxos)) + } + + pub fn utxo_entry_references(&self) -> Result> { + let inner = self.inner(); + let utxo_entry_references = inner + .inputs + .clone() + .into_iter() + .map(|input| { + input.get_utxo().ok_or(Error::MissingUtxoEntry) + }) + .collect::>>()?; + Ok(utxo_entry_references) + } + + pub fn outputs(&self) -> Vec { + let inner = self.inner(); + let outputs = inner + .outputs + .iter() + .map(|output| { + output.into() + }) + .collect::>(); + outputs } pub fn set_signature_script(&self, input_index: usize, signature_script: Vec) -> Result<()> { diff --git a/consensus/client/src/utxo.rs b/consensus/client/src/utxo.rs index 8c248a6d3..afcf87c4b 100644 --- a/consensus/client/src/utxo.rs +++ b/consensus/client/src/utxo.rs @@ -101,6 +101,12 @@ impl UtxoEntry { } } +impl AsRef for UtxoEntry { + fn as_ref(&self) -> &UtxoEntry { + self + } +} + impl From<&UtxoEntry> for cctx::UtxoEntry { fn from(utxo: &UtxoEntry) -> Self { cctx::UtxoEntry { diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index d7f5e7f83..aa9f0f23c 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -173,7 +173,7 @@ fn validate(pt: &PendingTransaction) { let compute_mass = calc.calc_mass_for_signed_transaction(&tx, 1); let utxo_entries = pt.utxo_entries().values().cloned().collect::>(); - let storage_mass = calc.calc_storage_mass_for_transaction(false, &utxo_entries, &tx.outputs).unwrap_or_default(); + let storage_mass = calc.calc_storage_mass_for_transaction(&utxo_entries, &tx.outputs).unwrap_or_default(); let calculated_mass = calc.combine_mass(compute_mass, storage_mass) + additional_mass; @@ -204,7 +204,7 @@ where let compute_mass = calc.calc_mass_for_signed_transaction(&tx, 1); let utxo_entries = pt.utxo_entries().values().cloned().collect::>(); - let storage_mass = calc.calc_storage_mass_for_transaction(false, &utxo_entries, &tx.outputs).unwrap_or_default(); + let storage_mass = calc.calc_storage_mass_for_transaction(&utxo_entries, &tx.outputs).unwrap_or_default(); if DISPLAY_LOGS && storage_mass != 0 { println!( "calculated storage mass: {} calculated_compute_mass: {} total: {}", diff --git a/wallet/core/src/tx/mass.rs b/wallet/core/src/tx/mass.rs index c76d10905..d5552d208 100644 --- a/wallet/core/src/tx/mass.rs +++ b/wallet/core/src/tx/mass.rs @@ -243,7 +243,7 @@ impl MassCalculator { } } - pub fn calc_mass_for_transaction(&self, tx: &Transaction) -> u64 { + pub fn calc_compute_mass_for_transaction(&self, tx: &Transaction) -> u64 { self.blank_transaction_mass() + self.calc_mass_for_payload(tx.payload.len()) + self.calc_mass_for_outputs(&tx.outputs) @@ -290,16 +290,16 @@ impl MassCalculator { } pub fn calc_mass_for_signed_transaction(&self, tx: &Transaction, minimum_signatures: u16) -> u64 { - self.calc_mass_for_transaction(tx) + self.calc_signature_mass_for_inputs(tx.inputs.len(), minimum_signatures) + self.calc_compute_mass_for_transaction(tx) + self.calc_signature_mass_for_inputs(tx.inputs.len(), minimum_signatures) } pub fn calc_minium_transaction_relay_fee(&self, tx: &Transaction, minimum_signatures: u16) -> u64 { - let mass = self.calc_mass_for_transaction(tx) + self.calc_signature_mass_for_inputs(tx.inputs.len(), minimum_signatures); + let mass = self.calc_compute_mass_for_transaction(tx) + self.calc_signature_mass_for_inputs(tx.inputs.len(), minimum_signatures); calc_minimum_required_transaction_relay_fee(mass) } - pub fn calc_tx_storage_fee(&self, is_coinbase: bool, inputs: &[UtxoEntryReference], outputs: &[TransactionOutput]) -> u64 { - self.calc_fee_for_storage_mass(self.calc_storage_mass_for_transaction(is_coinbase, inputs, outputs).unwrap_or(u64::MAX)) + pub fn calc_tx_storage_fee(&self, inputs: &[UtxoEntryReference], outputs: &[TransactionOutput]) -> u64 { + self.calc_fee_for_storage_mass(self.calc_storage_mass_for_transaction(inputs, outputs).unwrap_or(u64::MAX)) } pub fn calc_fee_for_storage_mass(&self, mass: u64) -> u64 { @@ -315,13 +315,9 @@ impl MassCalculator { pub fn calc_storage_mass_for_transaction( &self, - is_coinbase: bool, inputs: &[UtxoEntryReference], outputs: &[TransactionOutput], ) -> Option { - if is_coinbase { - return Some(0); - } /* The code below computes the following formula: max( 0 , C·( |O|/H(O) - |I|/A(I) ) ) diff --git a/wallet/core/src/wasm/signer.rs b/wallet/core/src/wasm/signer.rs index 106988a3c..9e0ce26b2 100644 --- a/wallet/core/src/wasm/signer.rs +++ b/wallet/core/src/wasm/signer.rs @@ -53,7 +53,7 @@ pub fn js_sign_transaction(tx: Transaction, signer: PrivateKeyArrayT, verify_sig pub fn sign_transaction(tx: Transaction, private_keys: &[[u8; 32]], verify_sig: bool) -> Result { let tx = sign(tx, private_keys)?; if verify_sig { - let (cctx, utxos) = tx.tx_and_utxos(); + let (cctx, utxos) = tx.tx_and_utxos()?; let populated_transaction = PopulatedTransaction::new(&cctx, utxos); verify(&populated_transaction)?; } @@ -76,7 +76,7 @@ pub fn create_input_signature( private_key: &PrivateKey, sighash_type: Option, ) -> Result { - let (cctx, _) = tx.tx_and_utxos(); + let (cctx, _) = tx.tx_and_utxos()?; let mutable_tx = SignableTransaction::new(cctx); let signature = diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index cc522fd8e..cf6020bde 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -70,12 +70,32 @@ impl MassCalculator { mass::calc_minimum_required_transaction_relay_fee(mass as u64) as u32 } - #[wasm_bindgen(js_name=calcMassForTransaction)] - pub fn calc_mass_for_transaction(&self, tx: &JsValue) -> Result { + #[wasm_bindgen(js_name=calcComputeMassForTransaction)] + pub fn calc_compute_mass_for_transaction(&self, tx: &TransactionT) -> Result { let tx = Transaction::try_cast_from(tx)?; let tx = cctx::Transaction::from(tx.as_ref()); - Ok(self.mc.calc_mass_for_transaction(&tx) as u32) - } + Ok(self.mc.calc_compute_mass_for_transaction(&tx) as u32) + } + + pub fn calc_storage_mass_for_transaction( + &self, + tx: &TransactionT, + ) -> Result { + let tx = Transaction::try_owned_from(tx)?; + let utxos = tx.utxo_entry_references()?; + let outputs = tx.outputs(); + Ok(self.mc.calc_storage_mass_for_transaction(&utxos, &outputs).unwrap_or_default()) + } + + // pub fn calc_tx_overall_mass( + // &self, + // // tx: &impl VerifiableTransaction, + // cached_compute_mass: Option, + // version: Kip9Version, + // ) -> Option { + // let tx = cctx::Transaction::from(tx); + // self.mc.calc_tx_overall_mass(&tx, cached_compute_mass, version) + // } #[wasm_bindgen(js_name=blankTransactionSerializedByteSize)] pub fn blank_transaction_serialized_byte_size() -> u32 { From 5be5ff0e5f6c0c6d80e7933a688a0cf293ff33c3 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 26 Jul 2024 18:49:55 +0300 Subject: [PATCH 07/10] remove WASM32 mass calc, replace with dedicated functions --- consensus/client/src/input.rs | 4 + consensus/client/src/output.rs | 4 +- consensus/client/src/transaction.rs | 33 ++-- wallet/core/src/tx/generator/generator.rs | 12 +- wallet/core/src/tx/generator/test.rs | 8 +- wallet/core/src/tx/mass.rs | 83 ++++++---- wallet/core/src/wasm/tx/consensus.rs | 43 ----- wallet/core/src/wasm/tx/mass.rs | 186 +++------------------- wallet/core/src/wasm/tx/mod.rs | 2 - wallet/core/src/wasm/tx/utils.rs | 28 +--- 10 files changed, 123 insertions(+), 280 deletions(-) delete mode 100644 wallet/core/src/wasm/tx/consensus.rs diff --git a/consensus/client/src/input.rs b/consensus/client/src/input.rs index 523927bd1..81b1d009a 100644 --- a/consensus/client/src/input.rs +++ b/consensus/client/src/input.rs @@ -95,6 +95,10 @@ impl TransactionInput { self.inner().sig_op_count } + pub fn signature_script_length(&self) -> usize { + self.inner().signature_script.len() + } + pub fn utxo(&self) -> Option { self.inner().utxo.clone() } diff --git a/consensus/client/src/output.rs b/consensus/client/src/output.rs index 4dddb5067..f4ee1ad1e 100644 --- a/consensus/client/src/output.rs +++ b/consensus/client/src/output.rs @@ -65,7 +65,7 @@ impl TransactionOutput { self.inner.lock().unwrap() } - pub fn script_length(&self) -> usize { + pub fn script_public_key_length(&self) -> usize { self.inner().script_public_key.script().len() } } @@ -79,7 +79,7 @@ impl TransactionOutput { } #[wasm_bindgen(getter, js_name = value)] - pub fn get_value(&self) -> u64 { + pub fn value(&self) -> u64 { self.inner().value } diff --git a/consensus/client/src/transaction.rs b/consensus/client/src/transaction.rs index 7f51416c7..0c1c8fa6a 100644 --- a/consensus/client/src/transaction.rs +++ b/consensus/client/src/transaction.rs @@ -392,25 +392,30 @@ impl Transaction { .inputs .clone() .into_iter() - .map(|input| { - input.get_utxo().ok_or(Error::MissingUtxoEntry) - }) + .map(|input| input.get_utxo().ok_or(Error::MissingUtxoEntry)) .collect::>>()?; Ok(utxo_entry_references) } pub fn outputs(&self) -> Vec { let inner = self.inner(); - let outputs = inner - .outputs - .iter() - .map(|output| { - output.into() - }) - .collect::>(); + let outputs = inner.outputs.iter().map(|output| output.into()).collect::>(); outputs } + pub fn inputs(&self) -> Vec { + let inner = self.inner(); + let inputs = inner.inputs.iter().map(Into::into).collect::>(); + inputs + } + + pub fn inputs_outputs(&self) -> (Vec, Vec) { + let inner = self.inner(); + let inputs = inner.inputs.iter().map(Into::into).collect::>(); + let outputs = inner.outputs.iter().map(Into::into).collect::>(); + (inputs, outputs) + } + pub fn set_signature_script(&self, input_index: usize, signature_script: Vec) -> Result<()> { if self.inner().inputs.len() <= input_index { return Err(Error::Custom("Input index is invalid".to_string())); @@ -418,6 +423,14 @@ impl Transaction { self.inner().inputs[input_index].set_signature_script(signature_script); Ok(()) } + + pub fn payload(&self) -> Vec { + self.inner().payload.clone() + } + + pub fn payload_len(&self) -> usize { + self.inner().payload.len() + } } #[wasm_bindgen] diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index 87dacfe11..8f8d83b3c 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -215,7 +215,7 @@ struct Data { impl Data { fn new(calc: &MassCalculator) -> Self { - let aggregate_mass = calc.blank_transaction_mass(); + let aggregate_mass = calc.blank_transaction_compute_mass(); Data { inputs: vec![], @@ -402,11 +402,11 @@ impl Generator { } let standard_change_output_mass = - mass_calculator.calc_mass_for_output(&TransactionOutput::new(0, pay_to_address_script(&change_address))); - let signature_mass_per_input = mass_calculator.calc_signature_mass(minimum_signatures); - let final_transaction_outputs_compute_mass = mass_calculator.calc_mass_for_outputs(&final_transaction_outputs); + mass_calculator.calc_compute_mass_for_output(&TransactionOutput::new(0, pay_to_address_script(&change_address))); + let signature_mass_per_input = mass_calculator.calc_compute_mass_for_signature(minimum_signatures); + let final_transaction_outputs_compute_mass = mass_calculator.calc_compute_mass_for_outputs(&final_transaction_outputs); let final_transaction_payload = final_transaction_payload.unwrap_or_default(); - let final_transaction_payload_mass = mass_calculator.calc_mass_for_payload(final_transaction_payload.len()); + let final_transaction_payload_mass = mass_calculator.calc_compute_mass_for_payload(final_transaction_payload.len()); let final_transaction_outputs_harmonic = mass_calculator.calc_storage_mass_output_harmonic(&final_transaction_outputs).ok_or(Error::MassCalculationError)?; @@ -660,7 +660,7 @@ impl Generator { let input = TransactionInput::new(utxo.outpoint.clone().into(), vec![], 0, self.inner.sig_op_count); let input_amount = utxo.amount(); - let input_compute_mass = calc.calc_mass_for_input(&input) + self.inner.signature_mass_per_input; + let input_compute_mass = calc.calc_compute_mass_for_input(&input) + self.inner.signature_mass_per_input; // NOTE: relay transactions have no storage mass // mass threshold reached, yield transaction diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index aa9f0f23c..045128a7b 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -170,10 +170,10 @@ fn validate(pt: &PendingTransaction) { let calc = MassCalculator::new(&pt.network_type().into(), network_params); let additional_mass = if pt.is_final() { 0 } else { network_params.additional_compound_transaction_mass() }; - let compute_mass = calc.calc_mass_for_signed_transaction(&tx, 1); + let compute_mass = calc.calc_compute_mass_for_signed_transaction(&tx, 1); let utxo_entries = pt.utxo_entries().values().cloned().collect::>(); - let storage_mass = calc.calc_storage_mass_for_transaction(&utxo_entries, &tx.outputs).unwrap_or_default(); + let storage_mass = calc.calc_storage_mass_for_transaction_parts(&utxo_entries, &tx.outputs).unwrap_or_default(); let calculated_mass = calc.combine_mass(compute_mass, storage_mass) + additional_mass; @@ -201,10 +201,10 @@ where let calc = MassCalculator::new(&pt.network_type().into(), network_params); let additional_mass = if pt.is_final() { 0 } else { network_params.additional_compound_transaction_mass() }; - let compute_mass = calc.calc_mass_for_signed_transaction(&tx, 1); + let compute_mass = calc.calc_compute_mass_for_signed_transaction(&tx, 1); let utxo_entries = pt.utxo_entries().values().cloned().collect::>(); - let storage_mass = calc.calc_storage_mass_for_transaction(&utxo_entries, &tx.outputs).unwrap_or_default(); + let storage_mass = calc.calc_storage_mass_for_transaction_parts(&utxo_entries, &tx.outputs).unwrap_or_default(); if DISPLAY_LOGS && storage_mass != 0 { println!( "calculated storage mass: {} calculated_compute_mass: {} total: {}", diff --git a/wallet/core/src/tx/mass.rs b/wallet/core/src/tx/mass.rs index 5816df78c..09545bf13 100644 --- a/wallet/core/src/tx/mass.rs +++ b/wallet/core/src/tx/mass.rs @@ -2,7 +2,9 @@ //! Transaction mass calculator. //! +use crate::result::Result; use crate::utxo::NetworkParams; +use kaspa_consensus_client as kcc; use kaspa_consensus_client::UtxoEntryReference; use kaspa_consensus_core::mass::Kip9Version; use kaspa_consensus_core::tx::{Transaction, TransactionInput, TransactionOutput, SCRIPT_VECTOR_SIZE}; @@ -236,44 +238,45 @@ impl MassCalculator { } } - pub fn calc_compute_mass_for_transaction(&self, tx: &Transaction) -> u64 { - self.blank_transaction_mass() - + self.calc_mass_for_payload(tx.payload.len()) - + self.calc_mass_for_outputs(&tx.outputs) - + self.calc_mass_for_inputs(&tx.inputs) + pub fn calc_transaction_compute_mass(&self, tx: &Transaction) -> u64 { + let payload_len = tx.payload.len(); + self.blank_transaction_compute_mass() + + self.calc_compute_mass_for_payload(payload_len) + + self.calc_compute_mass_for_outputs(&tx.outputs) + + self.calc_compute_mass_for_inputs(&tx.inputs) } - pub fn blank_transaction_mass(&self) -> u64 { + pub(crate) fn blank_transaction_compute_mass(&self) -> u64 { blank_transaction_serialized_byte_size() * self.mass_per_tx_byte } - pub fn calc_mass_for_payload(&self, payload_byte_size: usize) -> u64 { + pub(crate) fn calc_compute_mass_for_payload(&self, payload_byte_size: usize) -> u64 { payload_byte_size as u64 * self.mass_per_tx_byte } - pub fn calc_mass_for_outputs(&self, outputs: &[TransactionOutput]) -> u64 { - outputs.iter().map(|output| self.calc_mass_for_output(output)).sum() + pub(crate) fn calc_compute_mass_for_outputs(&self, outputs: &[TransactionOutput]) -> u64 { + outputs.iter().map(|output| self.calc_compute_mass_for_output(output)).sum() } - pub fn calc_mass_for_inputs(&self, inputs: &[TransactionInput]) -> u64 { - inputs.iter().map(|input| self.calc_mass_for_input(input)).sum::() + pub(crate) fn calc_compute_mass_for_inputs(&self, inputs: &[TransactionInput]) -> u64 { + inputs.iter().map(|input| self.calc_compute_mass_for_input(input)).sum::() } - pub fn calc_mass_for_output(&self, output: &TransactionOutput) -> u64 { + pub(crate) fn calc_compute_mass_for_output(&self, output: &TransactionOutput) -> u64 { self.mass_per_script_pub_key_byte * (2 + output.script_public_key.script().len() as u64) + transaction_output_serialized_byte_size(output) * self.mass_per_tx_byte } - pub fn calc_mass_for_input(&self, input: &TransactionInput) -> u64 { + pub(crate) fn calc_compute_mass_for_input(&self, input: &TransactionInput) -> u64 { input.sig_op_count as u64 * self.mass_per_sig_op + transaction_input_serialized_byte_size(input) * self.mass_per_tx_byte } - pub fn calc_signature_mass(&self, minimum_signatures: u16) -> u64 { + pub(crate) fn calc_compute_mass_for_signature(&self, minimum_signatures: u16) -> u64 { let minimum_signatures = std::cmp::max(1, minimum_signatures); SIGNATURE_SIZE * self.mass_per_tx_byte * minimum_signatures as u64 } - pub fn calc_signature_mass_for_inputs(&self, number_of_inputs: usize, minimum_signatures: u16) -> u64 { + pub fn calc_signature_compute_mass_for_inputs(&self, number_of_inputs: usize, minimum_signatures: u16) -> u64 { let minimum_signatures = std::cmp::max(1, minimum_signatures); SIGNATURE_SIZE * self.mass_per_tx_byte * minimum_signatures as u64 * number_of_inputs as u64 } @@ -282,20 +285,21 @@ impl MassCalculator { calc_minimum_required_transaction_relay_fee(mass) } - pub fn calc_mass_for_signed_transaction(&self, tx: &Transaction, minimum_signatures: u16) -> u64 { - self.calc_compute_mass_for_transaction(tx) + self.calc_signature_mass_for_inputs(tx.inputs.len(), minimum_signatures) + pub fn calc_compute_mass_for_signed_transaction(&self, tx: &Transaction, minimum_signatures: u16) -> u64 { + self.calc_transaction_compute_mass(tx) + self.calc_signature_compute_mass_for_inputs(tx.inputs.len(), minimum_signatures) } - pub fn calc_minium_transaction_relay_fee(&self, tx: &Transaction, minimum_signatures: u16) -> u64 { - let mass = self.calc_compute_mass_for_transaction(tx) + self.calc_signature_mass_for_inputs(tx.inputs.len(), minimum_signatures); - calc_minimum_required_transaction_relay_fee(mass) + pub fn calc_transaction_storage_fee(&self, inputs: &[UtxoEntryReference], outputs: &[TransactionOutput]) -> u64 { + self.calc_fee_for_storage_mass(self.calc_storage_mass_for_transaction_parts(inputs, outputs).unwrap_or(u64::MAX)) } - pub fn calc_tx_storage_fee(&self, inputs: &[UtxoEntryReference], outputs: &[TransactionOutput]) -> u64 { - self.calc_fee_for_storage_mass(self.calc_storage_mass_for_transaction(inputs, outputs).unwrap_or(u64::MAX)) + // provisional + pub fn calc_fee_for_storage_mass(&self, mass: u64) -> u64 { + mass } - pub fn calc_fee_for_storage_mass(&self, mass: u64) -> u64 { + // provisional + pub fn calc_fee_for_mass(&self, mass: u64) -> u64 { mass } @@ -306,14 +310,35 @@ impl MassCalculator { } } - pub fn calc_storage_mass_for_transaction( + /// Calculates the overall mass of this transaction, combining both compute and storage masses. + pub fn calc_tx_overall_mass(&self, tx: &kcc::Transaction) -> Result> { + let cctx = Transaction::from(tx); + let mass = match self.kip9_version { + Kip9Version::Alpha => self + .calc_storage_mass_for_transaction(tx)? + .and_then(|mass| mass.checked_add(self.calc_transaction_compute_mass(&cctx))), + Kip9Version::Beta => { + self.calc_storage_mass_for_transaction(tx)?.map(|mass| mass.max(self.calc_transaction_compute_mass(&cctx))) + } + }; + + Ok(mass) + } + + pub fn calc_storage_mass_for_transaction(&self, tx: &kcc::Transaction) -> Result> { + let utxos = tx.utxo_entry_references()?; + let outputs = tx.outputs(); + Ok(self.calc_storage_mass_for_transaction_parts(&utxos, &outputs)) + } + + pub fn calc_storage_mass_for_transaction_parts( &self, inputs: &[UtxoEntryReference], outputs: &[TransactionOutput], ) -> Option { /* The code below computes the following formula: - max( 0 , C·( |O|/H(O) - |I|/A(I) ) ) + max( 0 , C·( |O|/H(O) - |I|/A(I) ) ) where C is the mass storage parameter, O is the set of output values, I is the set of input values, H(S) := |S|/sum_{s in S} 1 / s is the harmonic mean over the set S and @@ -340,11 +365,11 @@ impl MassCalculator { let ins_len = inputs.len() as u64; /* - KIP-0009 relaxed formula for the cases |O| = 1 OR |O| <= |I| <= 2: - max( 0 , C·( |O|/H(O) - |I|/H(I) ) ) + KIP-0009 relaxed formula for the cases |O| = 1 OR |O| <= |I| <= 2: + max( 0 , C·( |O|/H(O) - |I|/H(I) ) ) - Note: in the case |I| = 1 both formulas are equal, yet the following code (harmonic_ins) is a bit more efficient. - Hence, we transform the condition to |O| = 1 OR |I| = 1 OR |O| = |I| = 2 which is equivalent (and faster). + Note: in the case |I| = 1 both formulas are equal, yet the following code (harmonic_ins) is a bit more efficient. + Hence, we transform the condition to |O| = 1 OR |I| = 1 OR |O| = |I| = 2 which is equivalent (and faster). */ if self.kip9_version == Kip9Version::Beta && (outs_len == 1 || ins_len == 1 || (outs_len == 2 && ins_len == 2)) { diff --git a/wallet/core/src/wasm/tx/consensus.rs b/wallet/core/src/wasm/tx/consensus.rs deleted file mode 100644 index b1e722a61..000000000 --- a/wallet/core/src/wasm/tx/consensus.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::tx::consensus as core; -use kaspa_addresses::Address; -use kaspa_consensus_core::{config::params::Params, network::NetworkType}; -use wasm_bindgen::prelude::*; - -/// -/// `ConsensusParams` can be obtained using `getConsensusParametersByNetwork` or `getConsensusParametersByAddress`. -/// -/// @see {@link getConsensusParametersByNetwork} -/// @see {@link getConsensusParametersByAddress} -/// -/// @category Wallet SDK -/// -#[wasm_bindgen] -pub struct ConsensusParams { - params: Params, -} - -impl From for ConsensusParams { - fn from(params: Params) -> Self { - Self { params } - } -} - -impl From for Params { - fn from(cp: ConsensusParams) -> Self { - cp.params - } -} - -/// find Consensus parameters for given Address -/// @category Wallet SDK -#[wasm_bindgen(js_name = getConsensusParametersByAddress)] -pub fn get_consensus_params_by_address(address: &Address) -> ConsensusParams { - core::get_consensus_params_by_address(address).into() -} - -/// find Consensus parameters for given NetworkType -/// @category Wallet SDK -#[wasm_bindgen(js_name = getConsensusParametersByNetwork)] -pub fn get_consensus_params_by_network(network: NetworkType) -> ConsensusParams { - core::get_consensus_params_by_network(&network).into() -} diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index cf6020bde..24f48f20d 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -1,171 +1,37 @@ use crate::imports::NetworkParams; use crate::result::Result; use crate::tx::mass; -use crate::wasm::tx::*; use kaspa_consensus_client::*; use kaspa_consensus_core::config::params::Params; -use kaspa_consensus_core::tx as cctx; -use std::sync::Arc; +use kaspa_consensus_core::network::{NetworkId, NetworkIdT}; use wasm_bindgen::prelude::*; use workflow_wasm::convert::*; -/// @category Wallet SDK -#[wasm_bindgen] -pub struct MassCalculator { - mc: Arc, +/// `calculateTransactionMass()` returns the mass of the passed transaction. +/// If the transaction is invalid, the function throws an error. +/// If the mass is larger than the transaction mass allowed by the network, the function +/// returns `undefined` which can be treated as a mass overflow condition. +#[wasm_bindgen(js_name = calculateTransactionMass)] +pub fn calculate_transaction_mass(network_id: NetworkIdT, tx: &TransactionT) -> Result> { + let tx = Transaction::try_cast_from(tx)?; + let network_id = NetworkId::try_owned_from(network_id)?; + let consensus_params = Params::from(network_id); + let network_params = NetworkParams::from(network_id); + let mc = mass::MassCalculator::new(&consensus_params, &network_params); + mc.calc_tx_overall_mass(tx.as_ref()) } -#[wasm_bindgen] -impl MassCalculator { - #[wasm_bindgen(constructor)] - pub fn new(cp: ConsensusParams) -> Self { - let consensus_params = Params::from(cp); - let network_params = NetworkParams::from(consensus_params.net); - Self { mc: Arc::new(mass::MassCalculator::new(&consensus_params, &network_params)) } - } - - #[wasm_bindgen(js_name=isDust)] - pub fn is_dust(&self, amount: u64) -> bool { - self.mc.is_dust(amount) - } - - /// `isTransactionOutputDust()` returns whether or not the passed transaction output - /// amount is considered dust or not based on the configured minimum transaction - /// relay fee. - /// - /// Dust is defined in terms of the minimum transaction relay fee. In particular, - /// if the cost to the network to spend coins is more than 1/3 of the minimum - /// transaction relay fee, it is considered dust. - /// - /// It is exposed by `MiningManager` for use by transaction generators and wallets. - #[wasm_bindgen(js_name=isTransactionOutputDust)] - pub fn is_transaction_output_dust(transaction_output: &JsValue) -> Result { - let transaction_output = TransactionOutput::try_from(transaction_output)?; - let transaction_output = cctx::TransactionOutput::from(&transaction_output); - Ok(mass::is_transaction_output_dust(&transaction_output)) - } - - /// `minimumRelayTransactionFee()` specifies the minimum transaction fee for a transaction to be accepted to - /// the mempool and relayed. It is specified in sompi per 1kg (or 1000 grams) of transaction mass. - /// - /// `pub(crate) const MINIMUM_RELAY_TRANSACTION_FEE: u64 = 1000;` - #[wasm_bindgen(js_name=minimumRelayTransactionFee)] - pub fn minimum_relay_transaction_fee() -> u32 { - mass::MINIMUM_RELAY_TRANSACTION_FEE as u32 - } - - /// `maximumStandardTransactionMass()` is the maximum mass allowed for transactions that - /// are considered standard and will therefore be relayed and considered for mining. - /// - /// `pub const MAXIMUM_STANDARD_TRANSACTION_MASS: u64 = 100_000;` - #[wasm_bindgen(js_name=maximumStandardTransactionMass)] - pub fn maximum_standard_transaction_mass() -> u32 { - mass::MAXIMUM_STANDARD_TRANSACTION_MASS as u32 - } - - /// minimum_required_transaction_relay_fee returns the minimum transaction fee required - /// for a transaction with the passed mass to be accepted into the mempool and relayed. - #[wasm_bindgen(js_name=minimumRequiredTransactionRelayFee)] - pub fn calc_minimum_required_transaction_relay_fee(mass: u32) -> u32 { - mass::calc_minimum_required_transaction_relay_fee(mass as u64) as u32 - } - - #[wasm_bindgen(js_name=calcComputeMassForTransaction)] - pub fn calc_compute_mass_for_transaction(&self, tx: &TransactionT) -> Result { - let tx = Transaction::try_cast_from(tx)?; - let tx = cctx::Transaction::from(tx.as_ref()); - Ok(self.mc.calc_compute_mass_for_transaction(&tx) as u32) - } - - pub fn calc_storage_mass_for_transaction( - &self, - tx: &TransactionT, - ) -> Result { - let tx = Transaction::try_owned_from(tx)?; - let utxos = tx.utxo_entry_references()?; - let outputs = tx.outputs(); - Ok(self.mc.calc_storage_mass_for_transaction(&utxos, &outputs).unwrap_or_default()) - } - - // pub fn calc_tx_overall_mass( - // &self, - // // tx: &impl VerifiableTransaction, - // cached_compute_mass: Option, - // version: Kip9Version, - // ) -> Option { - // let tx = cctx::Transaction::from(tx); - // self.mc.calc_tx_overall_mass(&tx, cached_compute_mass, version) - // } - - #[wasm_bindgen(js_name=blankTransactionSerializedByteSize)] - pub fn blank_transaction_serialized_byte_size() -> u32 { - mass::blank_transaction_serialized_byte_size() as u32 - } - - #[wasm_bindgen(js_name=blankTransactionMass)] - pub fn blank_transaction_mass(&self) -> u32 { - self.mc.blank_transaction_mass() as u32 - } - - #[wasm_bindgen(js_name=calcMassForPayload)] - pub fn calc_mass_for_payload(&self, payload_byte_size: usize) -> u32 { - self.mc.calc_mass_for_payload(payload_byte_size) as u32 - } - - #[wasm_bindgen(js_name=calcMassForOutputs)] - pub fn calc_mass_for_outputs(&self, outputs: JsValue) -> Result { - let outputs = outputs - .dyn_into::()? - .iter() - .map(TransactionOutput::try_from) - .collect::, kaspa_consensus_client::error::Error>>()?; - let outputs = outputs.iter().map(|output| self.calc_mass_for_output(output)).collect::>>()?; - Ok(outputs.iter().sum()) - } - - #[wasm_bindgen(js_name=calcMassForInputs)] - pub fn calc_mass_for_inputs(&self, inputs: JsValue) -> Result { - let inputs = inputs - .dyn_into::()? - .iter() - .map(TransactionInput::try_owned_from) - .collect::, kaspa_consensus_client::error::Error>>()?; - let inputs = inputs.iter().map(|input| self.calc_mass_for_input(input)).collect::>>()?; - Ok(inputs.iter().sum()) - } - - #[wasm_bindgen(js_name=calcMassForOutput)] - pub fn calc_mass_for_output(&self, output: &TransactionOutput) -> Result { - // let output = TransactionOutput::try_from(output)?; - let output = cctx::TransactionOutput::from(output); - Ok(self.mc.calc_mass_for_output(&output) as u32) - } - - #[wasm_bindgen(js_name=calcMassForInput)] - pub fn calc_mass_for_input(&self, input: &TransactionInput) -> Result { - // let input = TransactionInput::try_from(input)?; - let input = cctx::TransactionInput::from(input); - Ok(self.mc.calc_mass_for_input(&input) as u32) - } - - #[wasm_bindgen(js_name=calcSignatureMass)] - pub fn calc_signature_mass(&self, minimum_signatures: u16) -> u32 { - self.mc.calc_signature_mass(minimum_signatures) as u32 - } - - #[wasm_bindgen(js_name=calcSignatureMassForInputs)] - pub fn calc_signature_mass_for_inputs(&self, number_of_inputs: usize, minimum_signatures: u16) -> u32 { - self.mc.calc_signature_mass_for_inputs(number_of_inputs, minimum_signatures) as u32 - } - - #[wasm_bindgen(js_name=calcMinimumTransactionRelayFeeFromMass)] - pub fn calc_minimum_transaction_relay_fee_from_mass(&self, mass: u64) -> u32 { - self.mc.calc_minimum_transaction_fee_from_mass(mass) as u32 - } - - #[wasm_bindgen(js_name=calcMiniumTxRelayFee)] - pub fn calc_minimum_transaction_relay_fee(&self, transaction: &Transaction, minimum_signatures: u16) -> Result { - let tx = cctx::Transaction::from(transaction); - Ok(self.mc.calc_minium_transaction_relay_fee(&tx, minimum_signatures) as u32) - } +/// `calculateTransactionFees()` returns minimum fees needed for the transaction to be +/// accepted by the network. If the transaction is invalid, the function throws an error. +/// If the mass of the transaction is larger than the maximum allowed by the network, the +/// function returns `undefined` which can be treated as a mass overflow condition. +#[wasm_bindgen(js_name = calculateTransactionFees)] +pub fn calculate_transaction_fees(network_id: NetworkIdT, tx: &TransactionT) -> Result> { + let tx = Transaction::try_cast_from(tx)?; + let network_id = NetworkId::try_owned_from(network_id)?; + let consensus_params = Params::from(network_id); + let network_params = NetworkParams::from(network_id); + let mc = mass::MassCalculator::new(&consensus_params, &network_params); + let fee = mc.calc_tx_overall_mass(tx.as_ref())?.map(|mass| mc.calc_fee_for_mass(mass)); + Ok(fee) } diff --git a/wallet/core/src/wasm/tx/mod.rs b/wallet/core/src/wasm/tx/mod.rs index df826a997..742bb89ca 100644 --- a/wallet/core/src/wasm/tx/mod.rs +++ b/wallet/core/src/wasm/tx/mod.rs @@ -1,10 +1,8 @@ -pub mod consensus; pub mod fees; pub mod generator; pub mod mass; pub mod utils; -pub use self::consensus::*; pub use self::fees::*; pub use self::generator::*; pub use self::mass::*; diff --git a/wallet/core/src/wasm/tx/utils.rs b/wallet/core/src/wasm/tx/utils.rs index 0d911af76..352966a43 100644 --- a/wallet/core/src/wasm/tx/utils.rs +++ b/wallet/core/src/wasm/tx/utils.rs @@ -1,14 +1,11 @@ use crate::imports::*; use crate::result::Result; use crate::tx::{IPaymentOutputArray, PaymentOutputs}; -use crate::wasm::tx::consensus::get_consensus_params_by_address; use crate::wasm::tx::generator::*; -use crate::wasm::tx::mass::MassCalculator; -use kaspa_addresses::{Address, AddressT}; use kaspa_consensus_client::*; use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE; -//use kaspa_consensus_wasm::*; use kaspa_wallet_macros::declare_typescript_wasm_interface as declare; +use kaspa_wasm_core::types::BinaryT; use workflow_core::runtime::is_web; /// Create a basic transaction without any mass limit checks. @@ -17,16 +14,10 @@ use workflow_core::runtime::is_web; pub fn create_transaction_js( utxo_entry_source: IUtxoEntryArray, outputs: IPaymentOutputArray, - change_address: AddressT, priority_fee: BigInt, - payload: JsValue, - sig_op_count: JsValue, - minimum_signatures: JsValue, + payload: BinaryT, + sig_op_count: Option, ) -> crate::result::Result { - let change_address = Address::try_cast_from(change_address)?; - let params = get_consensus_params_by_address(change_address.as_ref()); - let mc = MassCalculator::new(params); - let utxo_entries = if let Some(utxo_entries) = utxo_entry_source.dyn_ref::() { utxo_entries.to_vec().iter().map(UtxoEntryReference::try_cast_from).collect::, _>>()? } else { @@ -35,14 +26,7 @@ pub fn create_transaction_js( let priority_fee: u64 = priority_fee.try_into().map_err(|err| Error::custom(format!("invalid fee value: {err}")))?; let payload = payload.try_as_vec_u8().ok().unwrap_or_default(); let outputs = PaymentOutputs::try_owned_from(outputs)?; - let sig_op_count = - if !sig_op_count.is_undefined() { sig_op_count.as_f64().expect("sigOpCount should be a number") as u8 } else { 1 }; - - let minimum_signatures = if !minimum_signatures.is_undefined() { - minimum_signatures.as_f64().expect("minimumSignatures should be a number") as u16 - } else { - 1 - }; + let sig_op_count = sig_op_count.unwrap_or(1); // --- @@ -64,12 +48,8 @@ pub fn create_transaction_js( return Err(format!("priority fee({priority_fee}) > amount({total_input_amount})").into()); } - // TODO - Calculate mass and fees - let outputs: Vec = outputs.into(); let transaction = Transaction::new(None, 0, inputs, outputs, 0, SUBNETWORK_ID_NATIVE, 0, payload)?; - let _fee = mc.calc_minimum_transaction_relay_fee(&transaction, minimum_signatures); - //let mtx = SignableTransaction::new(transaction, entries.into()); Ok(transaction) } From 85c0d07921f4f62d4d2f17b2b3b22cbba15fc269 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 26 Jul 2024 18:58:46 +0300 Subject: [PATCH 08/10] use OnceCell for NetworkParams (wallet-core) --- wallet/core/src/utxo/settings.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wallet/core/src/utxo/settings.rs b/wallet/core/src/utxo/settings.rs index 1d29f71b2..38e86a5e2 100644 --- a/wallet/core/src/utxo/settings.rs +++ b/wallet/core/src/utxo/settings.rs @@ -57,7 +57,7 @@ pub fn mainnet_network_params() -> &'static NetworkParams { coinbase_transaction_stasis_period_daa: 50, user_transaction_maturity_period_daa: AtomicU64::new(10), mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, + additional_compound_transaction_mass: 100, }) } @@ -69,7 +69,7 @@ pub fn testnet10_network_params() -> &'static NetworkParams { coinbase_transaction_stasis_period_daa: 50, user_transaction_maturity_period_daa: AtomicU64::new(10), mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, + additional_compound_transaction_mass: 100, }) } @@ -81,7 +81,7 @@ pub fn testnet11_network_params() -> &'static NetworkParams { coinbase_transaction_stasis_period_daa: 500, user_transaction_maturity_period_daa: AtomicU64::new(100), mass_combination_strategy: MassCombinationStrategy::Max, - additional_compound_transaction_mass: 0, + additional_compound_transaction_mass: 100, }) } From 40c6df95c861c5eeab8bd69e31838e51315903a7 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 26 Jul 2024 19:45:25 +0300 Subject: [PATCH 09/10] Update changelog --- wasm/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wasm/CHANGELOG.md b/wasm/CHANGELOG.md index 71b6375d2..d572dfd1d 100644 --- a/wasm/CHANGELOG.md +++ b/wasm/CHANGELOG.md @@ -2,6 +2,12 @@ Latest online documentation available at: https://kaspa.aspectron.org/docs/ ### Latest Release +- Replace `MassCalculator` with `calculateTransactionMass` and `calculateTransactionFee` functions. +- Change `createTransaction` function signature (remove requirement for change address). +- Make `ITransactionInput.signatureScript` optional (if not supplied, the signatureScript is assigned an empty vector). + +### Release 2024-07-17 + - Fix issues with deserializing manually-created objects matching `IUtxoEntry` interface. - Allow arguments expecting ScriptPublicKey to receive `{ version, script }` object or a hex string. - Fix `Transaction::serializeToObject()` return type (now returning `ISerializeTransaction` interface). From c95140b639d7287ec7459daa6b0cfe54eedce1d0 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 26 Jul 2024 19:48:24 +0300 Subject: [PATCH 10/10] fmt --- wallet/core/src/wasm/tx/mass.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index 3975e003a..5dd09051f 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -11,9 +11,9 @@ use workflow_wasm::convert::*; /// If the transaction is invalid, the function throws an error. /// If the mass is larger than the transaction mass allowed by the network, the function /// returns `undefined` which can be treated as a mass overflow condition. -/// +/// /// @category Wallet SDK -/// +/// #[wasm_bindgen(js_name = calculateTransactionMass)] pub fn calculate_transaction_mass(network_id: NetworkIdT, tx: &TransactionT) -> Result> { let tx = Transaction::try_cast_from(tx)?; @@ -28,9 +28,9 @@ pub fn calculate_transaction_mass(network_id: NetworkIdT, tx: &TransactionT) -> /// accepted by the network. If the transaction is invalid, the function throws an error. /// If the mass of the transaction is larger than the maximum allowed by the network, the /// function returns `undefined` which can be treated as a mass overflow condition. -/// +/// /// @category Wallet SDK -/// +/// #[wasm_bindgen(js_name = calculateTransactionFee)] pub fn calculate_transaction_fee(network_id: NetworkIdT, tx: &TransactionT) -> Result> { let tx = Transaction::try_cast_from(tx)?;