diff --git a/cli/src/modules/account.rs b/cli/src/modules/account.rs index 384a19bc82..cec7f00f6c 100644 --- a/cli/src/modules/account.rs +++ b/cli/src/modules/account.rs @@ -117,7 +117,7 @@ impl Account { if let Some(txid) = txid { tprintln!( ctx_, - "Scan detected {} KSHat index {}; transfer txid: {}", + "Scan detected {} KSH at index {}; transfer txid: {}", sompi_to_kash_string(balance), processed, txid @@ -257,7 +257,7 @@ impl Account { if let Some(txid) = txid { tprintln!( ctx_, - "Scan detected {} KSHat index {}; transfer txid: {}", + "Scan detected {} KSH at index {}; transfer txid: {}", sompi_to_kash_string(balance), processed, txid diff --git a/cli/src/modules/estimate.rs b/cli/src/modules/estimate.rs index 5858750813..b7febcffe0 100644 --- a/cli/src/modules/estimate.rs +++ b/cli/src/modules/estimate.rs @@ -1,8 +1,9 @@ use crate::imports::*; +use kash_consensus_core::tx::TransactionKind; use kash_wallet_core::tx::PaymentDestination; #[derive(Default, Handler)] -#[help("Estimate the fees for a transaction of a given amount")] +#[help("Estimate the fees for a transaction of a given amount and transaction kind")] pub struct Estimate; impl Estimate { @@ -11,19 +12,26 @@ impl Estimate { let account = ctx.wallet().account()?; - if argv.is_empty() { - tprintln!(ctx, "usage: estimate []"); + // Expecting at least two arguments: amount and transaction kind + if argv.len() < 2 { + tprintln!(ctx, "usage: estimate []"); return Ok(()); } - let amount_sompi = try_parse_required_nonzero_kash_as_sompi_u64(argv.first())?; - let priority_fee_sompi = try_parse_optional_kash_as_sompi_i64(argv.get(1))?.unwrap_or(0); + let tx_kind_str = &argv[0]; + let tx_kind = + TransactionKind::try_from(tx_kind_str).map_err(|_| Error::custom(format!("Invalid transaction kind: {}", tx_kind_str)))?; + + let amount_sompi = try_parse_required_nonzero_kash_as_sompi_u64(argv.get(1))?; + let priority_fee_sompi = try_parse_optional_kash_as_sompi_i64(argv.get(2))?.unwrap_or(0); let abortable = Abortable::default(); - // just use any address for an estimate (change address) + // Just use any address for an estimate (change address) let change_address = account.change_address()?; - let destination = PaymentDestination::PaymentOutputs(PaymentOutputs::from((change_address.clone(), amount_sompi))); - let estimate = account.estimate(destination, priority_fee_sompi.into(), None, &abortable).await?; + let destination_asset_type = tx_kind.asset_transfer_types().1; + let destination = + PaymentDestination::PaymentOutputs(PaymentOutputs::from((change_address.clone(), amount_sompi, destination_asset_type))); + let estimate = account.estimate(tx_kind, destination, priority_fee_sompi.into(), None, &abortable).await?; tprintln!(ctx, "Estimate - {estimate}"); diff --git a/cli/src/modules/send.rs b/cli/src/modules/send.rs index 23ced5cd1c..2e497002d8 100644 --- a/cli/src/modules/send.rs +++ b/cli/src/modules/send.rs @@ -1,4 +1,6 @@ use crate::imports::*; +use kash_consensus_core::asset_type::AssetType; +use std::convert::TryFrom; #[derive(Default, Handler)] #[help("Send a Kash transaction to a public address")] @@ -6,26 +8,28 @@ pub struct Send; impl Send { async fn main(self: Arc, ctx: &Arc, argv: Vec, _cmd: &str) -> Result<()> { - // address, amount, priority fee let ctx = ctx.clone().downcast_arc::()?; - let account = ctx.wallet().account()?; - if argv.len() < 2 { - tprintln!(ctx, "usage: send
"); + // Checking minimum argument length + if argv.len() < 3 { + tprintln!(ctx, "usage: send
"); return Ok(()); } - let address = Address::try_from(argv.first().unwrap().as_str())?; - let amount_sompi = try_parse_required_nonzero_kash_as_sompi_u64(argv.get(1))?; - let priority_fee_sompi = try_parse_optional_kash_as_sompi_i64(argv.get(2))?.unwrap_or(0); - let outputs = PaymentOutputs::from((address.clone(), amount_sompi)); + // Parsing asset type, address, and amounts + let asset_type = AssetType::from(argv.first().unwrap().as_str()); + let address = Address::try_from(argv.get(1).unwrap().as_str())?; + let amount_sompi = try_parse_required_nonzero_kash_as_sompi_u64(argv.get(2))?; + let priority_fee_sompi = try_parse_optional_kash_as_sompi_i64(argv.get(3))?.unwrap_or(0); + + let outputs = PaymentOutputs::from((address.clone(), amount_sompi, asset_type)); let abortable = Abortable::default(); let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; - // let ctx_ = ctx.clone(); let (summary, _ids) = account .send( + asset_type, outputs.into(), priority_fee_sompi.into(), None, @@ -39,8 +43,6 @@ impl Send { .await?; tprintln!(ctx, "Send - {summary}"); - // tprintln!(ctx, "\nSending {} KSHto {address}, tx ids:", sompi_to_kash_string(amount_sompi)); - // tprintln!(ctx, "{}\n", ids.into_iter().map(|a| a.to_string()).collect::>().join("\n")); Ok(()) } diff --git a/cli/src/modules/sweep.rs b/cli/src/modules/sweep.rs index 8d0bc4487e..54ecebd219 100644 --- a/cli/src/modules/sweep.rs +++ b/cli/src/modules/sweep.rs @@ -12,7 +12,7 @@ impl Sweep { let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; let abortable = Abortable::default(); // let ctx_ = ctx.clone(); - let (summary, _ids) = account + let (summaries, _ids) = account .sweep( wallet_secret, payment_secret, @@ -23,7 +23,9 @@ impl Sweep { ) .await?; - tprintln!(ctx, "Sweep: {summary}"); + for summary in summaries.iter() { + tprintln!(ctx, "Sweep - {summary}"); + } Ok(()) } diff --git a/cli/src/modules/transfer.rs b/cli/src/modules/transfer.rs index c860fc4ef6..0e9821af35 100644 --- a/cli/src/modules/transfer.rs +++ b/cli/src/modules/transfer.rs @@ -1,4 +1,5 @@ use crate::imports::*; +use kash_consensus_core::asset_type::AssetType; #[derive(Default, Handler)] #[help("Transfer funds between wallet accounts")] @@ -10,27 +11,35 @@ impl Transfer { let account = ctx.wallet().account()?; - if argv.len() < 2 { - tprintln!(ctx, "usage: transfer "); + // Checking minimum argument length, now expecting 3 arguments + if argv.len() < 3 { + tprintln!(ctx, "usage: transfer "); return Ok(()); } - let target_account = argv.first().unwrap(); - let target_account = ctx.find_accounts_by_name_or_id(target_account).await?; + // Parsing asset type + let asset_type_str = argv.first().unwrap(); + let asset_type = AssetType::from(asset_type_str.as_str()); + + let target_account_str = argv.get(1).unwrap(); + let target_account = ctx.find_accounts_by_name_or_id(target_account_str).await?; if target_account.id() == account.id() { return Err("Cannot transfer to the same account".into()); } - let amount_sompi = try_parse_required_nonzero_kash_as_sompi_u64(argv.get(1))?; - let priority_fee_sompi = try_parse_optional_kash_as_sompi_i64(argv.get(2))?.unwrap_or(0); + + // Parsing amount and priority fee + let amount_sompi = try_parse_required_nonzero_kash_as_sompi_u64(argv.get(2))?; + let priority_fee_sompi = try_parse_optional_kash_as_sompi_i64(argv.get(3))?.unwrap_or(0); + let target_address = target_account.receive_address()?; let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; let abortable = Abortable::default(); - let outputs = PaymentOutputs::from((target_address.clone(), amount_sompi)); + let outputs = PaymentOutputs::from((target_address.clone(), amount_sompi, asset_type)); - // let ctx_ = ctx.clone(); let (summary, _ids) = account .send( + asset_type, outputs.into(), priority_fee_sompi.into(), None, diff --git a/consensus/core/benches/serde_benchmark.rs b/consensus/core/benches/serde_benchmark.rs index cc3bc6566e..71ea943daa 100644 --- a/consensus/core/benches/serde_benchmark.rs +++ b/consensus/core/benches/serde_benchmark.rs @@ -1,7 +1,8 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use kash_consensus_core::asset_type::AssetType::KSH; use kash_consensus_core::subnets::SUBNETWORK_ID_COINBASE; use kash_consensus_core::tx::{ - ScriptPublicKey, Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput, + ScriptPublicKey, Transaction, TransactionId, TransactionInput, TransactionKind, TransactionOutpoint, TransactionOutput, }; use smallvec::smallvec; use std::time::{Duration, Instant}; @@ -43,9 +44,10 @@ fn serialize_benchmark(c: &mut Criterion) { }, ], vec![ - TransactionOutput { value: 300, script_public_key: script_public_key.clone() }, - TransactionOutput { value: 300, script_public_key }, + TransactionOutput { value: 300, script_public_key: script_public_key.clone(), asset_type: KSH }, + TransactionOutput { value: 300, script_public_key, asset_type: KSH }, ], + TransactionKind::TransferKSH, 0, SUBNETWORK_ID_COINBASE, 0, @@ -105,9 +107,10 @@ fn deserialize_benchmark(c: &mut Criterion) { }, ], vec![ - TransactionOutput { value: 300, script_public_key: script_public_key.clone() }, - TransactionOutput { value: 300, script_public_key }, + TransactionOutput { value: 300, script_public_key: script_public_key.clone(), asset_type: KSH }, + TransactionOutput { value: 300, script_public_key, asset_type: KSH }, ], + TransactionKind::TransferKSH, 0, SUBNETWORK_ID_COINBASE, 0, diff --git a/consensus/core/src/asset_type.rs b/consensus/core/src/asset_type.rs new file mode 100644 index 0000000000..098e21e103 --- /dev/null +++ b/consensus/core/src/asset_type.rs @@ -0,0 +1,88 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +/// Enum representing different types of assets in the Kash blockchain. +/// This allows for the representation of multiple currencies such as KSH, KUSD, and KRV. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[wasm_bindgen(js_name = assetType)] +pub enum AssetType { + KSH = 0, + KUSD = 1, + KRV = 2, +} + +impl From for AssetType { + fn from(value: u32) -> Self { + match value { + 0 => AssetType::KSH, + 1 => AssetType::KUSD, + 2 => AssetType::KRV, + _ => panic!("Invalid AssetType value: {}", value), + } + } +} + +impl From for u32 { + fn from(val: AssetType) -> Self { + match val { + AssetType::KSH => 0, + AssetType::KUSD => 1, + AssetType::KRV => 2, + } + } +} + +impl From for AssetType { + fn from(value: u8) -> Self { + match value { + 0 => AssetType::KSH, + 1 => AssetType::KUSD, + 2 => AssetType::KRV, + _ => panic!("Invalid AssetType value: {}", value), + } + } +} + +impl From<&str> for AssetType { + fn from(value: &str) -> Self { + match value { + "KSH" => AssetType::KSH, + "KUSD" => AssetType::KUSD, + "KRV" => AssetType::KRV, + _ => panic!("Invalid AssetType value: {}", value), + } + } +} + +impl TryFrom for AssetType { + type Error = JsValue; + + fn try_from(js_value: JsValue) -> Result { + if let Some(value) = js_value.as_f64() { + match value as u8 { + 0 => Ok(AssetType::KSH), + 1 => Ok(AssetType::KUSD), + 2 => Ok(AssetType::KRV), + _ => Err(JsValue::from_str("Invalid AssetType value")), + } + } else { + Err(JsValue::from_str("Invalid AssetType value")) + } + } +} + +impl fmt::Display for AssetType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + AssetType::KSH => "KSH", + AssetType::KUSD => "KUSD", + AssetType::KRV => "KRV", + } + ) + } +} diff --git a/consensus/core/src/config/genesis.rs b/consensus/core/src/config/genesis.rs index 20fc20107c..6149db7b09 100644 --- a/consensus/core/src/config/genesis.rs +++ b/consensus/core/src/config/genesis.rs @@ -1,3 +1,4 @@ +use crate::tx::TransactionKind; use crate::{block::Block, header::Header, subnets::SUBNETWORK_ID_COINBASE, tx::Transaction}; use kash_hashes::{Hash, ZERO_HASH}; use kash_muhash::EMPTY_MUHASH; @@ -18,7 +19,16 @@ pub struct GenesisBlock { impl GenesisBlock { pub fn build_genesis_transactions(&self) -> Vec { - vec![Transaction::new(0, Vec::new(), Vec::new(), 0, SUBNETWORK_ID_COINBASE, 0, self.coinbase_payload.to_vec())] + vec![Transaction::new( + 0, + Vec::new(), + Vec::new(), + TransactionKind::TransferKSH, + 0, + SUBNETWORK_ID_COINBASE, + 0, + self.coinbase_payload.to_vec(), + )] } } diff --git a/consensus/core/src/errors/asset_type.rs b/consensus/core/src/errors/asset_type.rs new file mode 100644 index 0000000000..62383be8f3 --- /dev/null +++ b/consensus/core/src/errors/asset_type.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Error, Debug, Clone)] +pub enum AssetTypeError { + #[error("asset type {0} is not supported")] + UnsupportedAssetType(u8), +} diff --git a/consensus/core/src/errors/mod.rs b/consensus/core/src/errors/mod.rs index c65ea57c51..ea62e813b0 100644 --- a/consensus/core/src/errors/mod.rs +++ b/consensus/core/src/errors/mod.rs @@ -1,3 +1,4 @@ +pub mod asset_type; pub mod block; pub mod coinbase; pub mod config; diff --git a/consensus/core/src/errors/tx.rs b/consensus/core/src/errors/tx.rs index 6f90c1aa9c..3d457f6eaa 100644 --- a/consensus/core/src/errors/tx.rs +++ b/consensus/core/src/errors/tx.rs @@ -82,6 +82,9 @@ pub enum TxRuleError { #[error("input {0} sig op count is {1}, but the calculated value is {2}")] WrongSigOpCount(usize, u64, u64), + + #[error("invalid transaction type: {0}")] + InvalidTransactionType(String), } pub type TxResult = std::result::Result; diff --git a/consensus/core/src/hashing/sighash.rs b/consensus/core/src/hashing/sighash.rs index f84ded5cd5..04005e0e70 100644 --- a/consensus/core/src/hashing/sighash.rs +++ b/consensus/core/src/hashing/sighash.rs @@ -182,9 +182,11 @@ pub fn calc_ecdsa_signature_hash( mod tests { use std::{str::FromStr, vec}; + use consensus_core::tx::TransactionKind; use smallvec::SmallVec; use crate::{ + asset_type::AssetType::KSH, hashing::sighash_type::{SIG_HASH_ALL, SIG_HASH_ANY_ONE_CAN_PAY, SIG_HASH_NONE, SIG_HASH_SINGLE}, subnets::SubnetworkId, tx::{PopulatedTransaction, Transaction, TransactionId, TransactionInput, UtxoEntry}, @@ -227,9 +229,18 @@ mod tests { }, ], vec![ - TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()) }, - TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }, + TransactionOutput { + value: 300, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()), + asset_type: KSH, + }, + TransactionOutput { + value: 300, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }, ], + TransactionKind::TransferKSH, 1615462089000, SUBNETWORK_ID_NATIVE, 0, @@ -244,18 +255,21 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 200, script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, ], ); @@ -272,18 +286,21 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 200, script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key_2), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, ], ); diff --git a/consensus/core/src/hashing/tx.rs b/consensus/core/src/hashing/tx.rs index 729ddd23d3..20936a5abd 100644 --- a/consensus/core/src/hashing/tx.rs +++ b/consensus/core/src/hashing/tx.rs @@ -72,9 +72,11 @@ fn write_output(hasher: &mut T, output: &TransactionOutput) { mod tests { use super::*; use crate::{ + asset_type::AssetType, subnets::{self, SubnetworkId}, tx::{scriptvec, ScriptPublicKey}, }; + use consensus_core::tx::TransactionKind; use std::str::FromStr; #[test] @@ -88,7 +90,16 @@ mod tests { let mut tests = vec![ // Test #1 Test { - tx: Transaction::new(0, Vec::new(), Vec::new(), 0, SubnetworkId::from_byte(0), 0, Vec::new()), + tx: Transaction::new( + 0, + Vec::new(), + Vec::new(), + TransactionKind::TransferKSH, + 0, + SubnetworkId::from_byte(0), + 0, + Vec::new(), + ), expected_id: "2c18d5e59ca8fc4c23d9560da3bf738a8f40935c11c162017fbf2c907b7e665c", expected_hash: "c9e29784564c269ce2faaffd3487cb4684383018ace11133de082dce4bb88b0b", }, @@ -98,23 +109,50 @@ mod tests { // Test #2 tests.push(Test { - tx: Transaction::new(1, inputs.clone(), Vec::new(), 0, SubnetworkId::from_byte(0), 0, Vec::new()), + tx: Transaction::new( + 1, + inputs.clone(), + Vec::new(), + TransactionKind::TransferKSH, + 0, + SubnetworkId::from_byte(0), + 0, + Vec::new(), + ), expected_id: "dafa415216d26130a899422203559c809d3efe72e20d48505fb2f08787bc4f49", expected_hash: "e4045023768d98839c976918f80c9419c6a93003724eda97f7c61a5b68de851b", }); - let outputs = vec![TransactionOutput::new(1564, ScriptPublicKey::new(7, scriptvec![1, 2, 3, 4, 5]))]; + let outputs = vec![TransactionOutput::new(1564, ScriptPublicKey::new(7, scriptvec![1, 2, 3, 4, 5]), AssetType::KSH)]; // Test #3 tests.push(Test { - tx: Transaction::new(1, inputs.clone(), outputs.clone(), 0, SubnetworkId::from_byte(0), 0, Vec::new()), + tx: Transaction::new( + 1, + inputs.clone(), + outputs.clone(), + TransactionKind::TransferKSH, + 0, + SubnetworkId::from_byte(0), + 0, + Vec::new(), + ), expected_id: "d1cd9dc1f26955832ccd12c27afaef4b71443aa7e7487804baf340952ca927e5", expected_hash: "e5523c70f6b986cad9f6959e63f080e6ac5f93bc2a9e0e01a89ca9bf6908f51c", }); // Test #4 tests.push(Test { - tx: Transaction::new(2, inputs, outputs.clone(), 54, SubnetworkId::from_byte(0), 3, Vec::new()), + tx: Transaction::new( + 2, + inputs, + outputs.clone(), + TransactionKind::TransferKSH, + 54, + SubnetworkId::from_byte(0), + 3, + Vec::new(), + ), expected_id: "59b3d6dc6cdc660c389c3fdb5704c48c598d279cdf1bab54182db586a4c95dd5", expected_hash: "b70f2f14c2f161a29b77b9a78997887a8e727bb57effca38cd246cb270b19cd5", }); @@ -128,21 +166,48 @@ mod tests { // Test #5 tests.push(Test { - tx: Transaction::new(2, inputs.clone(), outputs.clone(), 54, SubnetworkId::from_byte(0), 3, Vec::new()), + tx: Transaction::new( + 2, + inputs.clone(), + outputs.clone(), + TransactionKind::TransferKSH, + 54, + SubnetworkId::from_byte(0), + 3, + Vec::new(), + ), expected_id: "9d106623860567915b19cea33af486286a31b4bfc68627c6d4d377287afb40ad", expected_hash: "cd575e69fbf5f97fbfd4afb414feb56f8463b3948d6ac30f0ecdd9622672fab9", }); // Test #6 tests.push(Test { - tx: Transaction::new(2, inputs.clone(), outputs.clone(), 54, subnets::SUBNETWORK_ID_COINBASE, 3, Vec::new()), + tx: Transaction::new( + 2, + inputs.clone(), + outputs.clone(), + TransactionKind::TransferKSH, + 54, + subnets::SUBNETWORK_ID_COINBASE, + 3, + Vec::new(), + ), expected_id: "3fad809b11bd5a4af027aa4ac3fbde97e40624fd40965ba3ee1ee1b57521ad10", expected_hash: "b4eb5f0cab5060bf336af5dcfdeb2198cc088b693b35c87309bd3dda04f1cfb9", }); // Test #7 tests.push(Test { - tx: Transaction::new(2, inputs.clone(), outputs.clone(), 54, subnets::SUBNETWORK_ID_REGISTRY, 3, Vec::new()), + tx: Transaction::new( + 2, + inputs.clone(), + outputs.clone(), + TransactionKind::TransferKSH, + 54, + subnets::SUBNETWORK_ID_REGISTRY, + 3, + Vec::new(), + ), expected_id: "c542a204ab9416df910b01540b0c51b85e6d4e1724e081e224ea199a9e54e1b3", expected_hash: "31da267d5c34f0740c77b8c9ebde0845a01179ec68074578227b804bac306361", }); diff --git a/consensus/core/src/lib.rs b/consensus/core/src/lib.rs index deef3a8532..267e43d624 100644 --- a/consensus/core/src/lib.rs +++ b/consensus/core/src/lib.rs @@ -9,6 +9,7 @@ pub use kash_hashes::Hash; pub mod acceptance_data; pub mod api; +pub mod asset_type; pub mod block; pub mod block_count; pub mod blockhash; diff --git a/consensus/core/src/merkle.rs b/consensus/core/src/merkle.rs index 8908d6bd76..cb3167a7b3 100644 --- a/consensus/core/src/merkle.rs +++ b/consensus/core/src/merkle.rs @@ -10,9 +10,11 @@ pub fn calc_hash_merkle_root<'a>(txs: impl ExactSizeIterator Result<(), Error> { #[cfg(test)] mod tests { use super::*; - use crate::{subnets::SubnetworkId, tx::*}; + use crate::{asset_type::AssetType::KSH, subnets::SubnetworkId, tx::*}; use secp256k1::{rand, Secp256k1}; use std::str::FromStr; @@ -143,9 +143,10 @@ mod tests { }, ], vec![ - TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()) }, - TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()) }, + TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), asset_type: KSH }, + TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), asset_type: KSH }, ], + TransactionKind::TransferKSH, 1615462089000, SubnetworkId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -158,18 +159,21 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 200, script_public_key: ScriptPublicKey::new(0, script_pub_key), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key2), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, ]; let signed_tx = sign_with_multiple( diff --git a/consensus/core/src/tx.rs b/consensus/core/src/tx.rs index dcef038a41..bc0940d060 100644 --- a/consensus/core/src/tx.rs +++ b/consensus/core/src/tx.rs @@ -12,7 +12,9 @@ use std::{ }; use wasm_bindgen::prelude::*; +use crate::errors::tx::TxRuleError; use crate::{ + asset_type::AssetType, hashing, subnets::{self, SubnetworkId}, }; @@ -36,11 +38,19 @@ pub struct UtxoEntry { pub block_daa_score: u64, #[wasm_bindgen(js_name = isCoinbase)] pub is_coinbase: bool, + #[wasm_bindgen(js_name = assetType)] + pub asset_type: AssetType, } impl UtxoEntry { - pub fn new(amount: u64, script_public_key: ScriptPublicKey, block_daa_score: u64, is_coinbase: bool) -> Self { - Self { amount, script_public_key, block_daa_score, is_coinbase } + pub fn new( + amount: u64, + script_public_key: ScriptPublicKey, + block_daa_score: u64, + is_coinbase: bool, + asset_type: AssetType, + ) -> Self { + Self { amount, script_public_key, block_daa_score, is_coinbase, asset_type } } } @@ -90,11 +100,129 @@ impl TransactionInput { pub struct TransactionOutput { pub value: u64, pub script_public_key: ScriptPublicKey, + pub asset_type: AssetType, } impl TransactionOutput { - pub fn new(value: u64, script_public_key: ScriptPublicKey) -> Self { - Self { value, script_public_key } + pub fn new(value: u64, script_public_key: ScriptPublicKey, asset_type: AssetType) -> Self { + Self { value, script_public_key, asset_type } + } +} + +/// Defines the kind of a transaction +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)] +#[serde(rename_all = "camelCase")] +#[wasm_bindgen(js_name = transactionKind)] +#[derive(Default)] +pub enum TransactionKind { + /// Regular KSH transfer: KSH -> KSH + #[default] + TransferKSH, + /// Regular KUSD transfer: KUSD -> KUSD + TransferKUSD, + /// Regular KRV transfer: KRV -> KRV + TransferKRV, + /// Minting KUSD using KSH: KSH -> KUSD + MintKUSD, + /// Staking KSH to get KRV: KSH -> KRV + StakeKSH, + /// Redeeming KSH using KRV: KRV -> KSH + RedeemKSH, +} + +impl TransactionKind { + /// Returns the types of assets involved in the transaction. + /// For each transaction kind, it provides the source (`FromAssetType`) and the destination (`ToAssetType`) asset types. + pub fn asset_transfer_types(&self) -> (AssetType, AssetType) { + match self { + TransactionKind::TransferKSH => (AssetType::KSH, AssetType::KSH), + TransactionKind::TransferKUSD => (AssetType::KUSD, AssetType::KUSD), + TransactionKind::TransferKRV => (AssetType::KRV, AssetType::KRV), + TransactionKind::MintKUSD => (AssetType::KSH, AssetType::KUSD), + TransactionKind::StakeKSH => (AssetType::KSH, AssetType::KRV), + TransactionKind::RedeemKSH => (AssetType::KRV, AssetType::KSH), + } + } +} + +impl From for u32 { + fn from(kind: TransactionKind) -> u32 { + // Convert the TransactionKind enum into a u32 value based on its variant + match kind { + TransactionKind::TransferKSH => 0, + TransactionKind::TransferKUSD => 1, + TransactionKind::TransferKRV => 2, + TransactionKind::MintKUSD => 3, + TransactionKind::StakeKSH => 4, + TransactionKind::RedeemKSH => 5, + } + } +} + +impl From for TransactionKind { + fn from(value: u32) -> TransactionKind { + // Convert the u32 value into a TransactionKind enum based on its variant + match value { + 0 => TransactionKind::TransferKSH, + 1 => TransactionKind::TransferKUSD, + 2 => TransactionKind::TransferKRV, + 3 => TransactionKind::MintKUSD, + 4 => TransactionKind::StakeKSH, + 5 => TransactionKind::RedeemKSH, + _ => TransactionKind::TransferKSH, // Handle unknown values gracefully + } + } +} + +impl From for TransactionKind { + fn from(value: String) -> TransactionKind { + // Convert the string value into a TransactionKind enum based on its variant + match value.as_str() { + "TransferKSH" => TransactionKind::TransferKSH, + "TransferKUSD" => TransactionKind::TransferKUSD, + "TransferKRV" => TransactionKind::TransferKRV, + "MintKUSD" => TransactionKind::MintKUSD, + "StakeKSH" => TransactionKind::StakeKSH, + "RedeemKSH" => TransactionKind::RedeemKSH, + _ => TransactionKind::TransferKSH, // Handle unknown values gracefully + } + } +} + +impl TryFrom<&String> for TransactionKind { + type Error = TxRuleError; + fn try_from(value: &String) -> Result { + // Convert the string value into a TransactionKind enum based on its variant + match value.as_str() { + "TransferKSH" => Ok(TransactionKind::TransferKSH), + "TransferKUSD" => Ok(TransactionKind::TransferKUSD), + "TransferKRV" => Ok(TransactionKind::TransferKRV), + "MintKUSD" => Ok(TransactionKind::MintKUSD), + "StakeKSH" => Ok(TransactionKind::StakeKSH), + "RedeemKSH" => Ok(TransactionKind::RedeemKSH), + _ => Err(TxRuleError::InvalidTransactionType(value.clone())), + } + } +} + +impl TryFrom for TransactionKind { + type Error = JsValue; + + fn try_from(js_value: JsValue) -> Result { + if let Some(value) = js_value.as_f64() { + let value = value as u8; + match value { + 0 => Ok(TransactionKind::TransferKSH), + 1 => Ok(TransactionKind::TransferKUSD), + 2 => Ok(TransactionKind::TransferKRV), + 3 => Ok(TransactionKind::MintKUSD), + 4 => Ok(TransactionKind::StakeKSH), + 5 => Ok(TransactionKind::RedeemKSH), + _ => Err(JsValue::from_str("Invalid TransactionKind value")), + } + } else { + Err(JsValue::from_str("Invalid TransactionKind value")) + } } } @@ -105,6 +233,7 @@ pub struct Transaction { pub version: u16, pub inputs: Vec, pub outputs: Vec, + pub kind: TransactionKind, pub lock_time: u64, pub subnetwork_id: SubnetworkId, pub gas: u64, @@ -122,6 +251,7 @@ impl Transaction { version: u16, inputs: Vec, outputs: Vec, + kind: TransactionKind, lock_time: u64, subnetwork_id: SubnetworkId, gas: u64, @@ -131,6 +261,7 @@ impl Transaction { version, inputs, outputs, + kind, lock_time, subnetwork_id, gas, @@ -378,6 +509,7 @@ pub type SignableTransaction = MutableTransaction; #[cfg(test)] mod tests { use super::*; + use crate::tx::TransactionKind::TransferKSH; use consensus_core::subnets::SUBNETWORK_ID_COINBASE; use smallvec::smallvec; @@ -424,9 +556,10 @@ mod tests { }, ], vec![ - TransactionOutput { value: 6, script_public_key: script_public_key.clone() }, - TransactionOutput { value: 7, script_public_key }, + TransactionOutput { value: 6, script_public_key: script_public_key.clone(), asset_type: AssetType::KSH }, + TransactionOutput { value: 7, script_public_key, asset_type: AssetType::KSH }, ], + TransferKSH, 8, SUBNETWORK_ID_COINBASE, 9, @@ -455,15 +588,15 @@ mod tests { 198, 0, 0, 0, 251, 255, 255, 255, 32, 0, 0, 0, 0, 0, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 4, 0, 0, 0, 0, 0, 0, 0, 5, 2, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185, - 117, 220, 118, 217, 0, 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185, 117, 220, 118, 217, 0, - 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, - 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 69, 146, 193, - 64, 98, 49, 45, 0, 77, 32, 25, 122, 77, 15, 211, 252, 61, 210, 82, 177, 39, 153, 127, 33, 188, 172, 138, 38, 67, 75, 241, - 176, + 117, 220, 118, 217, 0, 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 118, 169, 33, 3, 47, 126, 67, 10, 164, 201, 209, 89, 67, 126, 132, 185, 117, 220, 118, + 217, 0, 59, 240, 146, 44, 243, 170, 69, 40, 70, 75, 171, 120, 13, 186, 94, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 69, 146, 193, 64, 98, 49, 45, 0, 77, 32, 25, 122, 77, 15, 211, 252, 61, 210, 82, 177, 39, 153, 127, 33, + 188, 172, 138, 38, 67, 75, 241, 176, ]; assert_eq!(expected_bts, bts); assert_eq!(tx, bincode::deserialize(&bts).unwrap()); @@ -498,13 +631,16 @@ mod tests { "outputs": [ { "value": 6, - "scriptPublicKey": "000076a921032f7e430aa4c9d159437e84b975dc76d9003bf0922cf3aa4528464bab780dba5e" + "scriptPublicKey": "000076a921032f7e430aa4c9d159437e84b975dc76d9003bf0922cf3aa4528464bab780dba5e", + "assetType": "KSH" }, { "value": 7, - "scriptPublicKey": "000076a921032f7e430aa4c9d159437e84b975dc76d9003bf0922cf3aa4528464bab780dba5e" + "scriptPublicKey": "000076a921032f7e430aa4c9d159437e84b975dc76d9003bf0922cf3aa4528464bab780dba5e", + "assetType": "KSH" } ], + "kind": "transferKSH", "lockTime": 8, "subnetworkId": "0100000000000000000000000000000000000000", "gas": 9, diff --git a/consensus/core/src/utxo/utxo_diff.rs b/consensus/core/src/utxo/utxo_diff.rs index 5eecaf0354..7cce5a74a8 100644 --- a/consensus/core/src/utxo/utxo_diff.rs +++ b/consensus/core/src/utxo/utxo_diff.rs @@ -224,7 +224,8 @@ impl UtxoDiff { for (i, output) in transaction.outputs().iter().enumerate() { let outpoint = TransactionOutpoint::new(tx_id, i as u32); - let entry = UtxoEntry::new(output.value, output.script_public_key.clone(), block_daa_score, is_coinbase); + let entry = + UtxoEntry::new(output.value, output.script_public_key.clone(), block_daa_score, is_coinbase, output.asset_type); self.add_entry(outpoint, entry)?; } Ok(()) @@ -256,6 +257,7 @@ impl UtxoDiff { #[cfg(test)] mod tests { use super::*; + use crate::asset_type::AssetType; use crate::tx::{ScriptPublicKey, TransactionId}; use std::str::FromStr; @@ -263,8 +265,8 @@ mod tests { fn test_utxo_diff_rules() { let tx_id0 = TransactionId::from_str("0".repeat(64).as_str()).unwrap(); let outpoint0 = TransactionOutpoint::new(tx_id0, 0); - let utxo_entry1 = UtxoEntry::new(10, ScriptPublicKey::default(), 0, true); - let utxo_entry2 = UtxoEntry::new(20, ScriptPublicKey::default(), 1, true); + let utxo_entry1 = UtxoEntry::new(10, ScriptPublicKey::default(), 0, true, AssetType::KSH); + let utxo_entry2 = UtxoEntry::new(20, ScriptPublicKey::default(), 1, true, AssetType::KSH); struct Test { name: &'static str, diff --git a/consensus/src/consensus/test_consensus.rs b/consensus/src/consensus/test_consensus.rs index 1c9705a117..93be56faa8 100644 --- a/consensus/src/consensus/test_consensus.rs +++ b/consensus/src/consensus/test_consensus.rs @@ -12,6 +12,7 @@ use kash_database::utils::DbLifetime; use kash_hashes::Hash; use parking_lot::RwLock; +use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_database::create_temp_db; use kash_database::prelude::ConnBuilder; use std::future::Future; @@ -173,7 +174,7 @@ impl TestConsensus { .chain((0_u8).to_le_bytes().iter().copied()) // Script public key length .collect(); - let cb = Transaction::new(TX_VERSION, vec![], vec![], 0, SUBNETWORK_ID_COINBASE, 0, cb_payload); + let cb = Transaction::new(TX_VERSION, vec![], vec![], TransferKSH, 0, SUBNETWORK_ID_COINBASE, 0, cb_payload); txs.insert(0, cb); header.hash_merkle_root = calc_hash_merkle_root(txs.iter()); MutableBlock::new(header, txs) diff --git a/consensus/src/pipeline/body_processor/body_validation_in_context.rs b/consensus/src/pipeline/body_processor/body_validation_in_context.rs index 5beea48c46..6584931c8f 100644 --- a/consensus/src/pipeline/body_processor/body_validation_in_context.rs +++ b/consensus/src/pipeline/body_processor/body_validation_in_context.rs @@ -92,6 +92,7 @@ mod tests { params::DEVNET_PARAMS, processes::{transaction_validator::errors::TxRuleError, window::WindowManager}, }; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ api::ConsensusApi, merkle::calc_hash_merkle_root, @@ -221,6 +222,7 @@ mod tests { TX_VERSION, vec![TransactionInput::new(TransactionOutpoint::new(1.into(), 0), vec![], sequence, 0)], vec![], + TransferKSH, lock_time, SUBNETWORK_ID_NATIVE, 0, diff --git a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs index eba1b326ef..6e0e330292 100644 --- a/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs +++ b/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs @@ -112,6 +112,8 @@ mod tests { errors::RuleError, params::MAINNET_PARAMS, }; + use kash_consensus_core::asset_type::AssetType::KSH; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ api::{BlockValidationFutures, ConsensusApi}, block::MutableBlock, @@ -169,7 +171,9 @@ mod tests { 0xba, 0x30, 0xcd, 0x5a, 0x4b, 0x87 ), ), + asset_type: KSH, }], + TransferKSH, 0, SUBNETWORK_ID_COINBASE, 0, @@ -204,6 +208,7 @@ mod tests { }, ], vec![], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -249,6 +254,7 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }, TransactionOutput { value: 0x108e20f00, @@ -263,8 +269,10 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }, ], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -309,6 +317,7 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }, TransactionOutput { value: 0x11d260c0, @@ -323,8 +332,10 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }, ], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -369,7 +380,9 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, diff --git a/consensus/src/processes/coinbase.rs b/consensus/src/processes/coinbase.rs index d2d1bd0643..e3aa229ea0 100644 --- a/consensus/src/processes/coinbase.rs +++ b/consensus/src/processes/coinbase.rs @@ -1,3 +1,5 @@ +use kash_consensus_core::asset_type::AssetType::KSH; +use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ coinbase::*, errors::coinbase::{CoinbaseError, CoinbaseResult}, @@ -105,8 +107,11 @@ impl CoinbaseManager { for blue in ghostdag_data.mergeset_blues.iter().filter(|h| !mergeset_non_daa.contains(h)) { let reward_data = mergeset_rewards.get(blue).unwrap(); if reward_data.subsidy + reward_data.total_fees > 0 { - outputs - .push(TransactionOutput::new(reward_data.subsidy + reward_data.total_fees, reward_data.script_public_key.clone())); + outputs.push(TransactionOutput::new( + reward_data.subsidy + reward_data.total_fees, + reward_data.script_public_key.clone(), + KSH, + )); } } @@ -118,7 +123,7 @@ impl CoinbaseManager { red_reward += reward_data.subsidy + reward_data.total_fees; } if red_reward > 0 { - outputs.push(TransactionOutput::new(red_reward, miner_data.script_public_key.clone())); + outputs.push(TransactionOutput::new(red_reward, miner_data.script_public_key.clone(), KSH)); } // Build the current block's payload @@ -126,7 +131,7 @@ impl CoinbaseManager { let payload = self.serialize_coinbase_payload(&CoinbaseData { blue_score: ghostdag_data.blue_score, subsidy, miner_data })?; Ok(CoinbaseTransactionTemplate { - tx: Transaction::new(constants::TX_VERSION, vec![], outputs, 0, subnets::SUBNETWORK_ID_COINBASE, 0, payload), + tx: Transaction::new(constants::TX_VERSION, vec![], outputs, TransferKSH, 0, subnets::SUBNETWORK_ID_COINBASE, 0, payload), has_red_reward: red_reward > 0, }) } diff --git a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs index 081dca0ef9..5510910cd5 100644 --- a/consensus/src/processes/transaction_validator/transaction_validator_populated.rs +++ b/consensus/src/processes/transaction_validator/transaction_validator_populated.rs @@ -137,8 +137,10 @@ mod tests { use super::super::errors::TxRuleError; use core::str::FromStr; use itertools::Itertools; + use kash_consensus_core::asset_type::AssetType::KSH; use kash_consensus_core::sign::sign; use kash_consensus_core::subnets::SubnetworkId; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::tx::{MutableTransaction, PopulatedTransaction, ScriptVec, TransactionId, UtxoEntry}; use kash_consensus_core::tx::{ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput}; use kash_txscript_errors::TxScriptError; @@ -187,9 +189,18 @@ mod tests { sig_op_count: 1, }], vec![ - TransactionOutput { value: 10360487799, script_public_key: ScriptPublicKey::new(0, script_pub_key_2) }, - TransactionOutput { value: 10518958752, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }, + TransactionOutput { + value: 10360487799, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2), + asset_type: KSH, + }, + TransactionOutput { + value: 10518958752, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }, ], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -203,6 +214,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 32022768, is_coinbase: false, + asset_type: KSH, }], ); @@ -249,9 +261,18 @@ mod tests { sig_op_count: 1, }], vec![ - TransactionOutput { value: 10360487799, script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()) }, - TransactionOutput { value: 10518958752, script_public_key: ScriptPublicKey::new(0, script_pub_key_1) }, + TransactionOutput { + value: 10360487799, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2.clone()), + asset_type: KSH, + }, + TransactionOutput { + value: 10518958752, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1), + asset_type: KSH, + }, ], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -265,6 +286,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_2), block_daa_score: 32022768, is_coinbase: false, + asset_type: KSH, }], ); @@ -312,9 +334,18 @@ mod tests { sig_op_count: 4, }], vec![ - TransactionOutput { value: 10000000000000, script_public_key: ScriptPublicKey::new(0, script_pub_key_2) }, - TransactionOutput { value: 2792999990000, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }, + TransactionOutput { + value: 10000000000000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2), + asset_type: KSH, + }, + TransactionOutput { + value: 2792999990000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }, ], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -328,6 +359,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 36151168, is_coinbase: false, + asset_type: KSH, }], ); tv.check_scripts(&populated_tx).expect("Signature check failed"); @@ -374,9 +406,18 @@ mod tests { sig_op_count: 4, }], vec![ - TransactionOutput { value: 10000000000000, script_public_key: ScriptPublicKey::new(0, script_pub_key_2) }, - TransactionOutput { value: 2792999990000, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }, + TransactionOutput { + value: 10000000000000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2), + asset_type: KSH, + }, + TransactionOutput { + value: 2792999990000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }, ], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -390,6 +431,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 36151168, is_coinbase: false, + asset_type: KSH, }], ); @@ -437,9 +479,18 @@ mod tests { sig_op_count: 4, }], vec![ - TransactionOutput { value: 10000000000000, script_public_key: ScriptPublicKey::new(0, script_pub_key_2) }, - TransactionOutput { value: 2792999990000, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }, + TransactionOutput { + value: 10000000000000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2), + asset_type: KSH, + }, + TransactionOutput { + value: 2792999990000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }, ], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -453,6 +504,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 36151168, is_coinbase: false, + asset_type: KSH, }], ); @@ -500,9 +552,18 @@ mod tests { sig_op_count: 4, }], vec![ - TransactionOutput { value: 10000000000000, script_public_key: ScriptPublicKey::new(0, script_pub_key_2) }, - TransactionOutput { value: 2792999990000, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }, + TransactionOutput { + value: 10000000000000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_2), + asset_type: KSH, + }, + TransactionOutput { + value: 2792999990000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }, ], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -516,6 +577,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 36151168, is_coinbase: false, + asset_type: KSH, }], ); @@ -557,7 +619,12 @@ mod tests { sequence: 0, sig_op_count: 4, }], - vec![TransactionOutput { value: 2792999990000, script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()) }], + vec![TransactionOutput { + value: 2792999990000, + script_public_key: ScriptPublicKey::new(0, script_pub_key_1.clone()), + asset_type: KSH, + }], + TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -571,6 +638,7 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key_1), block_daa_score: 36151168, is_coinbase: false, + asset_type: KSH, }], ); @@ -622,9 +690,10 @@ mod tests { }, ], vec![ - TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()) }, - TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()) }, + TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), asset_type: KSH }, + TransactionOutput { value: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), asset_type: KSH }, ], + TransferKSH, 1615462089000, SubnetworkId::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -637,18 +706,21 @@ mod tests { script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 200, script_public_key: ScriptPublicKey::new(0, script_pub_key.clone()), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, UtxoEntry { amount: 300, script_public_key: ScriptPublicKey::new(0, script_pub_key), block_daa_score: 0, is_coinbase: false, + asset_type: KSH, }, ]; let schnorr_key = secp256k1::KeyPair::from_seckey_slice(secp256k1::SECP256K1, &secret_key.secret_bytes()).unwrap(); diff --git a/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs b/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs index a9fd1c8c79..e8e26d11bd 100644 --- a/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs +++ b/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs @@ -148,6 +148,8 @@ fn check_transaction_output_value_ranges(tx: &Transaction) -> TxResult<()> { #[cfg(test)] mod tests { + use kash_consensus_core::asset_type::AssetType::KSH; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ subnets::{SUBNETWORK_ID_COINBASE, SUBNETWORK_ID_NATIVE}, tx::{scriptvec, ScriptPublicKey, Transaction, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput}, @@ -188,7 +190,9 @@ mod tests { 0x30, 0xcd, 0x5a, 0x4b, 0x87 ), ), + asset_type: KSH, }], + TransferKSH, 0, SUBNETWORK_ID_COINBASE, 0, @@ -237,6 +241,7 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }, TransactionOutput { value: 0x108e20f00, @@ -251,8 +256,10 @@ mod tests { 0xac // OP_CHECKSIG ), ), + asset_type: KSH, }, ], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, diff --git a/consensus/src/test_helpers.rs b/consensus/src/test_helpers.rs index 0c78873cc5..9e2e9a91b8 100644 --- a/consensus/src/test_helpers.rs +++ b/consensus/src/test_helpers.rs @@ -1,3 +1,5 @@ +use kash_consensus_core::asset_type::AssetType::KSH; +use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ block::Block, header::Header, @@ -49,6 +51,7 @@ pub fn generate_random_utxo_from_script_public_key_pool( script_public_key_pool.choose(rng).expect("expected_script_public key").clone(), rng.gen(), rng.gen_bool(0.5), + KSH, ) } @@ -58,6 +61,7 @@ pub fn generate_random_utxo(rng: &mut SmallRng) -> UtxoEntry { generate_random_p2pk_script_public_key(rng), rng.gen(), rng.gen_bool(0.5), + KSH, ) } @@ -117,6 +121,7 @@ pub fn generate_random_transaction(rng: &mut SmallRng, input_amount: usize, outp rng.gen(), generate_random_transaction_inputs(rng, input_amount), generate_random_transaction_outputs(rng, output_amount), + TransferKSH, rng.gen(), SubnetworkId::from_byte(rng.gen()), rng.gen(), @@ -144,6 +149,7 @@ pub fn generate_random_transaction_output(rng: &mut SmallRng) -> TransactionOutp TransactionOutput::new( rng.gen_range(1..100_000), //we choose small amounts as to not overflow with large utxosets. generate_random_p2pk_script_public_key(rng), + KSH, ) } diff --git a/consensus/wasm/src/output.rs b/consensus/wasm/src/output.rs index 348fefcc7a..854170f38b 100644 --- a/consensus/wasm/src/output.rs +++ b/consensus/wasm/src/output.rs @@ -1,4 +1,5 @@ use crate::imports::*; +use kash_consensus_core::asset_type::AssetType; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -7,6 +8,7 @@ pub struct TransactionOutputInner { pub value: u64, #[wasm_bindgen(js_name = scriptPublicKey, getter_with_clone)] pub script_public_key: ScriptPublicKey, + pub asset_type: AssetType, } /// Represents a Kashd transaction output @@ -42,8 +44,10 @@ impl TransactionOutput { impl TransactionOutput { #[wasm_bindgen(constructor)] /// TransactionOutput constructor - pub fn new(value: u64, script_public_key: &ScriptPublicKey) -> TransactionOutput { - Self { inner: Arc::new(Mutex::new(TransactionOutputInner { value, script_public_key: script_public_key.clone() })) } + pub fn new(value: u64, script_public_key: &ScriptPublicKey, asset_type: AssetType) -> TransactionOutput { + Self { + inner: Arc::new(Mutex::new(TransactionOutputInner { value, script_public_key: script_public_key.clone(), asset_type })), + } } #[wasm_bindgen(getter, js_name = value)] @@ -81,14 +85,14 @@ impl AsRef for TransactionOutput { impl From for TransactionOutput { fn from(output: cctx::TransactionOutput) -> Self { - TransactionOutput::new(output.value, &output.script_public_key) + TransactionOutput::new(output.value, &output.script_public_key, output.asset_type) } } impl From<&TransactionOutput> for cctx::TransactionOutput { fn from(output: &TransactionOutput) -> Self { let inner = output.inner(); - cctx::TransactionOutput::new(inner.value, inner.script_public_key.clone()) + cctx::TransactionOutput::new(inner.value, inner.script_public_key.clone(), inner.asset_type) } } @@ -101,7 +105,8 @@ impl TryFrom for TransactionOutput { workflow_log::log_trace!("js_value->TransactionOutput: has_address:{has_address:?}"); let value = object.get_u64("value")?; let script_public_key = ScriptPublicKey::try_from(object.get_value("scriptPublicKey")?)?; - Ok(TransactionOutput::new(value, &script_public_key)) + let asset_type = AssetType::try_from(object.get_value("assetType")?)?; + Ok(TransactionOutput::new(value, &script_public_key, asset_type)) } else { Err("TransactionInput must be an object".into()) } diff --git a/consensus/wasm/src/transaction.rs b/consensus/wasm/src/transaction.rs index 662d1c45b2..c5096655e6 100644 --- a/consensus/wasm/src/transaction.rs +++ b/consensus/wasm/src/transaction.rs @@ -3,6 +3,7 @@ use crate::input::TransactionInput; use crate::output::TransactionOutput; use crate::result::Result; use kash_consensus_core::subnets::{self, SubnetworkId}; +use kash_consensus_core::tx::TransactionKind; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -10,6 +11,7 @@ pub struct TransactionInner { pub version: u16, pub inputs: Vec, pub outputs: Vec, + pub kind: TransactionKind, pub lock_time: u64, pub subnetwork_id: SubnetworkId, pub gas: u64, @@ -32,6 +34,7 @@ impl Transaction { version: u16, inputs: Vec, outputs: Vec, + kind: TransactionKind, lock_time: u64, subnetwork_id: SubnetworkId, gas: u64, @@ -42,6 +45,7 @@ impl Transaction { version, inputs, outputs, + kind, lock_time, subnetwork_id, gas, @@ -218,7 +222,8 @@ impl TryFrom<&JsValue> for Transaction { .into_iter() .map(|jsv| jsv.try_into()) .collect::, Error>>()?; - Transaction::new(version, inputs, outputs, lock_time, subnetwork_id, gas, payload) + let kind = object.get_u32("kind")?.into(); + Transaction::new(version, inputs, outputs, kind, lock_time, subnetwork_id, gas, payload) } } else { Err("Transaction must be an object".into()) @@ -235,6 +240,7 @@ impl From for Transaction { version: tx.version, inputs, outputs, + kind: tx.kind, lock_time: tx.lock_time, gas: tx.gas, payload: tx.payload, @@ -256,6 +262,7 @@ impl From<&Transaction> for cctx::Transaction { inner.version, inputs, outputs, + inner.kind, inner.lock_time, inner.subnetwork_id.clone(), inner.gas, diff --git a/consensus/wasm/src/utxo.rs b/consensus/wasm/src/utxo.rs index 2164ca09c9..a4635acf7e 100644 --- a/consensus/wasm/src/utxo.rs +++ b/consensus/wasm/src/utxo.rs @@ -2,6 +2,7 @@ use crate::imports::*; use crate::result::Result; use crate::{TransactionOutpoint, TransactionOutpointInner}; use kash_addresses::Address; +use kash_consensus_core::asset_type::AssetType; use workflow_wasm::abi::ref_from_abi; pub type UtxoEntryId = TransactionOutpointInner; @@ -283,11 +284,13 @@ impl TryFrom<&JsValue> for UtxoEntryReference { let script_public_key = ScriptPublicKey::try_from(utxo_entry.get_value("scriptPublicKey")?)?; let block_daa_score = utxo_entry.get_u64("blockDaaScore")?; let is_coinbase = utxo_entry.get_bool("isCoinbase")?; + let asset_type = AssetType::try_from(object.get_value("assetType")?)?; + // Construct UtxoEntry let utxo_entry = UtxoEntry { address: Some(address), outpoint, - entry: cctx::UtxoEntry { amount, script_public_key, block_daa_score, is_coinbase }, + entry: cctx::UtxoEntry { amount, script_public_key, block_daa_score, is_coinbase, asset_type }, }; Ok(UtxoEntryReference::from(utxo_entry)) @@ -298,22 +301,26 @@ impl TryFrom<&JsValue> for UtxoEntryReference { } impl UtxoEntryReference { + // Creates a fake UtxoEntryReference with specified amount. pub fn fake(amount: u64) -> Self { use kash_addresses::{Prefix, Version}; let address = Address::new(Prefix::Testnet, Version::PubKey, &[0; 32]); Self::fake_with_address(amount, &address) } + // Creates a fake UtxoEntryReference with specified amount and address. pub fn fake_with_address(amount: u64, address: &Address) -> Self { let outpoint = TransactionOutpoint::fake(); let script_public_key = kash_txscript::pay_to_address_script(address); let block_daa_score = 0; let is_coinbase = true; + let asset_type = AssetType::KSH; // Default asset type for fake UtxoEntry + // Construct UtxoEntry let utxo_entry = UtxoEntry { address: Some(address.clone()), outpoint, - entry: cctx::UtxoEntry { amount, script_public_key, block_daa_score, is_coinbase }, + entry: cctx::UtxoEntry { amount, script_public_key, block_daa_score, is_coinbase, asset_type }, }; UtxoEntryReference::from(utxo_entry) diff --git a/crypto/txscript/src/lib.rs b/crypto/txscript/src/lib.rs index a5d22690f4..759a2ee1c6 100644 --- a/crypto/txscript/src/lib.rs +++ b/crypto/txscript/src/lib.rs @@ -508,6 +508,8 @@ mod tests { use crate::opcodes::codes::{OpBlake2b, OpCheckSig, OpData1, OpData2, OpData32, OpDup, OpEqual, OpPushData1, OpTrue}; use super::*; + use kash_consensus_core::asset_type::AssetType; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::tx::{ PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput, }; @@ -554,10 +556,14 @@ mod tests { sequence: 4294967295, sig_op_count: 0, }; - let output = TransactionOutput { value: 1000000000, script_public_key: ScriptPublicKey::new(0, test.script.into()) }; + let output = TransactionOutput { + value: 1000000000, + script_public_key: ScriptPublicKey::new(0, test.script.into()), + asset_type: AssetType::KSH, + }; - let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], 0, Default::default(), 0, vec![]); - let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, tx.is_coinbase()); + let tx = Transaction::new(1, vec![input.clone()], vec![output.clone()], TransferKSH, 0, Default::default(), 0, vec![]); + let utxo_entry = UtxoEntry::new(output.value, output.script_public_key.clone(), 0, tx.is_coinbase(), output.asset_type); let populated_tx = PopulatedTransaction::new(&tx, vec![utxo_entry.clone()]); @@ -924,9 +930,10 @@ mod bitcoind_tests { use super::*; use crate::script_builder::ScriptBuilderError; - use kash_consensus_core::constants::MAX_TX_IN_SEQUENCE_NUM; - use kash_consensus_core::tx::{ - PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput, + use kash_consensus_core::{ + asset_type::AssetType::{self, KSH}, + constants::MAX_TX_IN_SEQUENCE_NUM, + tx::{PopulatedTransaction, ScriptPublicKey, Transaction, TransactionId, TransactionOutpoint, TransactionOutput}, }; #[derive(PartialEq, Eq, Debug, Clone)] @@ -949,7 +956,7 @@ mod bitcoind_tests { Comment((String,)), } - fn create_spending_transaction(sig_script: Vec, script_public_key: ScriptPublicKey) -> Transaction { + fn create_spending_transaction(sig_script: Vec, script_public_key: ScriptPublicKey, asset_type: AssetType) -> Transaction { let coinbase = Transaction::new( 1, vec![TransactionInput::new( @@ -958,7 +965,8 @@ mod bitcoind_tests { MAX_TX_IN_SEQUENCE_NUM, Default::default(), )], - vec![TransactionOutput::new(0, script_public_key)], + vec![TransactionOutput::new(0, script_public_key, asset_type)], + Default::default(), Default::default(), Default::default(), Default::default(), @@ -973,7 +981,8 @@ mod bitcoind_tests { MAX_TX_IN_SEQUENCE_NUM, Default::default(), )], - vec![TransactionOutput::new(0, Default::default())], + vec![TransactionOutput::new(0, Default::default(), asset_type)], + Default::default(), Default::default(), Default::default(), Default::default(), @@ -1008,8 +1017,8 @@ mod bitcoind_tests { ScriptPublicKey::from_vec(0, opcodes::parse_short_form(script_pub_key).map_err(UnifiedError::ScriptBuilderError)?); // Create transaction - let tx = create_spending_transaction(script_sig, script_pub_key.clone()); - let entry = UtxoEntry::new(0, script_pub_key.clone(), 0, true); + let tx = create_spending_transaction(script_sig, script_pub_key.clone(), KSH); + let entry = UtxoEntry::new(0, script_pub_key.clone(), 0, true, KSH); let populated_tx = PopulatedTransaction::new(&tx, vec![entry]); // Run transaction diff --git a/crypto/txscript/src/opcodes/mod.rs b/crypto/txscript/src/opcodes/mod.rs index 721b02071c..3c1713b166 100644 --- a/crypto/txscript/src/opcodes/mod.rs +++ b/crypto/txscript/src/opcodes/mod.rs @@ -977,12 +977,13 @@ mod test { use crate::opcodes::{OpCodeExecution, OpCodeImplementation}; use crate::{opcodes, pay_to_address_script, TxScriptEngine, TxScriptError, LOCK_TIME_THRESHOLD}; use kash_addresses::{Address, Prefix, Version}; + use kash_consensus_core::asset_type::AssetType; use kash_consensus_core::constants::{SOMPI_PER_KASH, TX_VERSION}; use kash_consensus_core::hashing::sighash::SigHashReusedValues; use kash_consensus_core::subnets::SUBNETWORK_ID_NATIVE; use kash_consensus_core::tx::{ - PopulatedTransaction, ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, - VerifiableTransaction, + PopulatedTransaction, ScriptPublicKey, Transaction, TransactionInput, TransactionKind, TransactionOutpoint, TransactionOutput, + UtxoEntry, VerifiableTransaction, }; struct TestCase<'a> { @@ -2720,18 +2721,19 @@ mod test { let addr = Address::new(Prefix::Testnet, Version::PubKey, &addr_hash); let dummy_script_public_key = pay_to_address_script(&addr); - let dummy_tx_out = TransactionOutput::new(SOMPI_PER_KASH, dummy_script_public_key); + let dummy_tx_out = TransactionOutput::new(SOMPI_PER_KASH, dummy_script_public_key, AssetType::KSH); let tx = VerifiableTransactionMock(Transaction::new( TX_VERSION + 1, vec![dummy_tx_input.clone()], vec![dummy_tx_out.clone()], + TransactionKind::TransferKSH, lock_time, SUBNETWORK_ID_NATIVE, 0, vec![], )); - let utxo_entry = UtxoEntry::new(0, ScriptPublicKey::default(), 0, false); + let utxo_entry = UtxoEntry::new(0, ScriptPublicKey::default(), 0, false, AssetType::KSH); (tx, dummy_tx_input, utxo_entry) } diff --git a/crypto/txscript/src/standard.rs b/crypto/txscript/src/standard.rs index 8c36cb62b9..5e602bd197 100644 --- a/crypto/txscript/src/standard.rs +++ b/crypto/txscript/src/standard.rs @@ -86,7 +86,9 @@ pub fn extract_script_pub_key_address(script_public_key: &ScriptPublicKey, prefi pub mod test_helpers { use super::*; use crate::{opcodes::codes::OpTrue, MAX_TX_IN_SEQUENCE_NUM}; + use kash_consensus_core::tx::TransactionKind; use kash_consensus_core::{ + asset_type::AssetType::KSH, constants::TX_VERSION, subnets::SUBNETWORK_ID_NATIVE, tx::{Transaction, TransactionInput, TransactionOutpoint, TransactionOutput}, @@ -108,8 +110,8 @@ pub mod test_helpers { let signature_script = pay_to_script_hash_signature_script(redeem_script, vec![]).expect("the script is canonical"); let previous_outpoint = TransactionOutpoint::new(tx_to_spend.id(), 0); let input = TransactionInput::new(previous_outpoint, signature_script, MAX_TX_IN_SEQUENCE_NUM, 1); - let output = TransactionOutput::new(tx_to_spend.outputs[0].value - fee, script_public_key); - Transaction::new(TX_VERSION, vec![input], vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![]) + let output = TransactionOutput::new(tx_to_spend.outputs[0].value - fee, script_public_key, KSH); + Transaction::new(TX_VERSION, vec![input], vec![output], TransactionKind::TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![]) } } diff --git a/crypto/txscript/src/standard/multisig.rs b/crypto/txscript/src/standard/multisig.rs index 150fc00794..f4242c1fc3 100644 --- a/crypto/txscript/src/standard/multisig.rs +++ b/crypto/txscript/src/standard/multisig.rs @@ -72,6 +72,7 @@ mod tests { use super::*; use crate::{caches::Cache, opcodes::codes::OpData65, pay_to_script_hash_script, TxScriptEngine}; use core::str::FromStr; + use kash_consensus_core::asset_type::AssetType::KSH; use kash_consensus_core::{ hashing::{ sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValues}, @@ -140,6 +141,7 @@ mod tests { sig_op_count: 4, }], vec![], + TransactionKind::TransferKSH, 0, SubnetworkId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 0, @@ -151,6 +153,7 @@ mod tests { script_public_key: pay_to_script_hash_script(&script), block_daa_score: 36151168, is_coinbase: false, + asset_type: KSH, }]; let mut tx = MutableTransaction::with_entries(tx, entries); diff --git a/indexes/core/src/indexed_utxos.rs b/indexes/core/src/indexed_utxos.rs index 3e0528e277..8566667ba1 100644 --- a/indexes/core/src/indexed_utxos.rs +++ b/indexes/core/src/indexed_utxos.rs @@ -1,3 +1,4 @@ +use kash_consensus_core::asset_type::AssetType; use kash_consensus_core::tx::{ScriptPublicKey, TransactionOutpoint, UtxoEntry}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -21,16 +22,22 @@ pub struct CompactUtxoEntry { pub amount: u64, pub block_daa_score: u64, pub is_coinbase: bool, + pub asset_type: AssetType, } impl CompactUtxoEntry { /// Creates a new [`CompactUtxoEntry`] - pub fn new(amount: u64, block_daa_score: u64, is_coinbase: bool) -> Self { - Self { amount, block_daa_score, is_coinbase } + pub fn new(amount: u64, block_daa_score: u64, is_coinbase: bool, asset_type: AssetType) -> Self { + Self { amount, block_daa_score, is_coinbase, asset_type } } } impl From for CompactUtxoEntry { fn from(utxo_entry: UtxoEntry) -> Self { - Self { amount: utxo_entry.amount, block_daa_score: utxo_entry.block_daa_score, is_coinbase: utxo_entry.is_coinbase } + Self { + amount: utxo_entry.amount, + block_daa_score: utxo_entry.block_daa_score, + is_coinbase: utxo_entry.is_coinbase, + asset_type: utxo_entry.asset_type, + } } } diff --git a/indexes/utxoindex/src/update_container.rs b/indexes/utxoindex/src/update_container.rs index 77730f2354..b396374c65 100644 --- a/indexes/utxoindex/src/update_container.rs +++ b/indexes/utxoindex/src/update_container.rs @@ -38,7 +38,7 @@ impl UtxoIndexChanges { self.utxo_changes.added.insert_into_nested( utxo_entry.script_public_key, transaction_outpoint, - CompactUtxoEntry::new(utxo_entry.amount, utxo_entry.block_daa_score, utxo_entry.is_coinbase), + CompactUtxoEntry::new(utxo_entry.amount, utxo_entry.block_daa_score, utxo_entry.is_coinbase, utxo_entry.asset_type), ); } @@ -48,7 +48,7 @@ impl UtxoIndexChanges { self.utxo_changes.removed.insert_into_nested( utxo_entry.script_public_key, transaction_outpoint, - CompactUtxoEntry::new(utxo_entry.amount, utxo_entry.block_daa_score, utxo_entry.is_coinbase), + CompactUtxoEntry::new(utxo_entry.amount, utxo_entry.block_daa_score, utxo_entry.is_coinbase, utxo_entry.asset_type), ); } } @@ -63,7 +63,7 @@ impl UtxoIndexChanges { self.utxo_changes.added.insert_into_nested( utxo_entry.script_public_key, transaction_outpoint, - CompactUtxoEntry::new(utxo_entry.amount, utxo_entry.block_daa_score, utxo_entry.is_coinbase), + CompactUtxoEntry::new(utxo_entry.amount, utxo_entry.block_daa_score, utxo_entry.is_coinbase, utxo_entry.asset_type), ); } } diff --git a/kashd/src/args.rs b/kashd/src/args.rs index 690fc8ffb5..55c7c22712 100644 --- a/kashd/src/args.rs +++ b/kashd/src/args.rs @@ -16,6 +16,13 @@ use kash_consensus_core::{ network::{NetworkId, NetworkType}, }; +// The import of `KSH` is used within `#[cfg(feature = "devnet-prealloc")]` blocks. +// However, `cargo clippy` may not recognize its usage in conditional compilation contexts, +// leading to a warning about an unused import. The `allow(unused_imports)` directive is +// used here to prevent this warning. +#[allow(unused_imports)] +use kash_consensus_core::asset_type::AssetType::KSH; + use kash_core::kashd_env::version; use kash_utils::networking::ContextualNetAddress; @@ -145,7 +152,13 @@ impl Args { .map(|i| { ( TransactionOutpoint { transaction_id: i.into(), index: 0 }, - UtxoEntry { amount: self.prealloc_amount, script_public_key: spk.clone(), block_daa_score: 0, is_coinbase: false }, + UtxoEntry { + amount: self.prealloc_amount, + script_public_key: spk.clone(), + block_daa_score: 0, + is_coinbase: false, + asset_type: KSH, + }, ) }) .collect() diff --git a/mining/src/block_template/selector.rs b/mining/src/block_template/selector.rs index 677cdab058..c37d087b79 100644 --- a/mining/src/block_template/selector.rs +++ b/mining/src/block_template/selector.rs @@ -264,7 +264,9 @@ impl TemplateTransactionSelector for TransactionsSelector { mod tests { use super::*; use itertools::Itertools; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ + asset_type::AssetType::KSH, constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASH, TX_VERSION}, mass::transaction_estimated_serialized_size, subnets::SUBNETWORK_ID_NATIVE, @@ -313,8 +315,8 @@ mod tests { let signature_script = pay_to_script_hash_signature_script(redeem_script, vec![]).expect("the redeem script is canonical"); let input = TransactionInput::new(previous_outpoint, signature_script, MAX_TX_IN_SEQUENCE_NUM, 1); - let output = TransactionOutput::new(value - DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE, script_public_key); - let tx = Arc::new(Transaction::new(TX_VERSION, vec![input], vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![])); + let output = TransactionOutput::new(value - DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE, script_public_key, KSH); + let tx = Arc::new(Transaction::new(TX_VERSION, vec![input], vec![output], TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![])); let calculated_mass = transaction_estimated_serialized_size(&tx); let calculated_fee = DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE; diff --git a/mining/src/manager_tests.rs b/mining/src/manager_tests.rs index 3954cdc5ea..e06083f709 100644 --- a/mining/src/manager_tests.rs +++ b/mining/src/manager_tests.rs @@ -14,6 +14,8 @@ mod tests { MiningCounters, }; use kash_addresses::{Address, Prefix, Version}; + use kash_consensus_core::asset_type::AssetType::KSH; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ api::ConsensusApi, block::TemplateBuildMode, @@ -920,9 +922,9 @@ mod tests { let signature_script = pay_to_script_hash_signature_script(redeem_script, vec![]).expect("the redeem script is canonical"); let input = TransactionInput::new(previous_outpoint, signature_script, MAX_TX_IN_SEQUENCE_NUM, 1); - let entry = UtxoEntry::new(SOMPI_PER_KASH, script_public_key.clone(), block_daa_score, true); - let output = TransactionOutput::new(SOMPI_PER_KASH - DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE, script_public_key); - let transaction = Transaction::new(TX_VERSION, vec![input], vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + let entry = UtxoEntry::new(SOMPI_PER_KASH, script_public_key.clone(), block_daa_score, true, KSH); + let output = TransactionOutput::new(SOMPI_PER_KASH - DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE, script_public_key, KSH); + let transaction = Transaction::new(TX_VERSION, vec![input], vec![output], TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); let mut mutable_tx = MutableTransaction::from_tx(transaction); mutable_tx.calculated_fee = Some(DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE); @@ -939,9 +941,7 @@ mod tests { ) -> (Vec, Vec) { // Make the funding amounts always different so that funding txs have different ids (0..count) - .map(|i| { - create_parent_and_children_transactions(consensus, vec![500 * SOMPI_PER_KASH, 3_000 * SOMPI_PER_KASH + i as u64]) - }) + .map(|i| create_parent_and_children_transactions(consensus, vec![500 * SOMPI_PER_KASH, 3_000 * SOMPI_PER_KASH + i as u64])) .unzip() } @@ -966,8 +966,8 @@ mod tests { fn create_transaction_without_input(output_values: Vec) -> Transaction { let (script_public_key, _) = op_true_script(); - let outputs = output_values.iter().map(|value| TransactionOutput::new(*value, script_public_key.clone())).collect(); - Transaction::new(TX_VERSION, vec![], outputs, 0, SUBNETWORK_ID_NATIVE, 0, vec![]) + let outputs = output_values.iter().map(|value| TransactionOutput::new(*value, script_public_key.clone(), KSH)).collect(); + Transaction::new(TX_VERSION, vec![], outputs, TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![]) } fn contained_by>(transaction_id: TransactionId, transactions: &[T]) -> bool { @@ -983,7 +983,7 @@ mod tests { } fn get_dummy_coinbase_tx() -> Transaction { - Transaction::new(TX_VERSION, vec![], vec![], 0, SUBNETWORK_ID_NATIVE, 0, vec![]) + Transaction::new(TX_VERSION, vec![], vec![], TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![]) } fn build_block_transactions<'a>(transactions: impl Iterator) -> Vec { diff --git a/mining/src/mempool/check_transaction_standard.rs b/mining/src/mempool/check_transaction_standard.rs index 6062a50a19..c2c70ed8c4 100644 --- a/mining/src/mempool/check_transaction_standard.rs +++ b/mining/src/mempool/check_transaction_standard.rs @@ -229,6 +229,8 @@ mod tests { MiningCounters, }; use kash_addresses::{Address, Prefix, Version}; + use kash_consensus_core::asset_type::AssetType::KSH; + use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ config::params::Params, constants::{MAX_TX_IN_SEQUENCE_NUM, SOMPI_PER_KASH, TX_VERSION}, @@ -319,33 +321,33 @@ mod tests { // Any value is allowed with a zero relay fee. Test { name: "zero value with zero relay fee", - tx_out: TransactionOutput::new(0, script_public_key.clone()), + tx_out: TransactionOutput::new(0, script_public_key.clone(), KSH), minimum_relay_transaction_fee: 0, is_dust: false, }, // Zero value is dust with any relay fee" Test { name: "zero value with very small tx fee", - tx_out: TransactionOutput::new(0, script_public_key.clone()), + tx_out: TransactionOutput::new(0, script_public_key.clone(), KSH), minimum_relay_transaction_fee: 1, is_dust: true, }, Test { name: "36 byte public key script with value 605", - tx_out: TransactionOutput::new(605, script_public_key.clone()), + tx_out: TransactionOutput::new(605, script_public_key.clone(), KSH), minimum_relay_transaction_fee: 1000, is_dust: true, }, Test { name: "36 byte public key script with value 606", - tx_out: TransactionOutput::new(606, script_public_key.clone()), + tx_out: TransactionOutput::new(606, script_public_key.clone(), KSH), minimum_relay_transaction_fee: 1000, is_dust: false, }, // Maximum allowed value is never dust. Test { name: "max sompi amount is never dust", - tx_out: TransactionOutput::new(MAX_SOMPI, script_public_key.clone()), + tx_out: TransactionOutput::new(MAX_SOMPI, script_public_key.clone(), KSH), minimum_relay_transaction_fee: 1000, is_dust: false, }, @@ -353,14 +355,14 @@ mod tests { // Rust rewrite: caution, this differs from the golang version Test { name: "maximum uint64 value", - tx_out: TransactionOutput::new(u64::MAX, script_public_key), + tx_out: TransactionOutput::new(u64::MAX, script_public_key, KSH), minimum_relay_transaction_fee: u64::MAX, is_dust: false, }, // Unspendable script_public_key due to an invalid public key script. Test { name: "unspendable script_public_key", - tx_out: TransactionOutput::new(5000, invalid_script_public_key), + tx_out: TransactionOutput::new(5000, invalid_script_public_key, KSH), minimum_relay_transaction_fee: 0, is_dust: true, }, @@ -393,7 +395,7 @@ mod tests { let addr = Address::new(Prefix::Testnet, Version::PubKey, &addr_hash); let dummy_script_public_key = kash_txscript::pay_to_address_script(&addr); - let dummy_tx_out = TransactionOutput::new(SOMPI_PER_KASH, dummy_script_public_key); + let dummy_tx_out = TransactionOutput::new(SOMPI_PER_KASH, dummy_script_public_key, KSH); struct Test { name: &'static str, @@ -415,6 +417,7 @@ mod tests { TX_VERSION, vec![dummy_tx_input.clone()], vec![dummy_tx_out.clone()], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -431,6 +434,7 @@ mod tests { TX_VERSION + 1, vec![dummy_tx_input.clone()], vec![dummy_tx_out.clone()], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -452,7 +456,9 @@ mod tests { MAX_SCRIPT_PUBLIC_KEY_VERSION, ScriptVec::from_vec(vec![0u8; MAXIMUM_STANDARD_TRANSACTION_MASS as usize + 1]), ), + KSH, )], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -474,6 +480,7 @@ mod tests { 1, )], vec![dummy_tx_out.clone()], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -495,7 +502,9 @@ mod tests { MAX_SCRIPT_PUBLIC_KEY_VERSION, ScriptBuilder::new().add_op(OpTrue).unwrap().script().into(), ), + KSH, )], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -511,7 +520,8 @@ mod tests { Transaction::new( TX_VERSION, vec![dummy_tx_input.clone()], - vec![TransactionOutput::new(0, dummy_tx_out.script_public_key)], + vec![TransactionOutput::new(0, dummy_tx_out.script_public_key, KSH)], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, @@ -533,7 +543,9 @@ mod tests { MAX_SCRIPT_PUBLIC_KEY_VERSION, ScriptBuilder::new().add_op(OpReturn).unwrap().script().into(), ), + KSH, )], + TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, diff --git a/mining/src/mempool/model/utxo_set.rs b/mining/src/mempool/model/utxo_set.rs index 2b114a92bb..3d304d0288 100644 --- a/mining/src/mempool/model/utxo_set.rs +++ b/mining/src/mempool/model/utxo_set.rs @@ -38,7 +38,7 @@ impl MempoolUtxoSet { for (i, output) in transaction.tx.outputs.iter().enumerate() { let outpoint = TransactionOutpoint::new(transaction_id, i as u32); - let entry = UtxoEntry::new(output.value, output.script_public_key.clone(), UNACCEPTED_DAA_SCORE, false); + let entry = UtxoEntry::new(output.value, output.script_public_key.clone(), UNACCEPTED_DAA_SCORE, false, output.asset_type); self.pool_unspent_outputs.insert(outpoint, entry); } } diff --git a/mining/src/mempool/populate_entries_and_try_validate.rs b/mining/src/mempool/populate_entries_and_try_validate.rs index c3b53c4aba..a654209f1d 100644 --- a/mining/src/mempool/populate_entries_and_try_validate.rs +++ b/mining/src/mempool/populate_entries_and_try_validate.rs @@ -7,8 +7,13 @@ impl Mempool { for (i, input) in transaction.tx.inputs.iter().enumerate() { if let Some(parent) = self.transaction_pool.get(&input.previous_outpoint.transaction_id) { let output = &parent.mtx.tx.outputs[input.previous_outpoint.index as usize]; - transaction.entries[i] = - Some(UtxoEntry::new(output.value, output.script_public_key.clone(), UNACCEPTED_DAA_SCORE, false)); + transaction.entries[i] = Some(UtxoEntry::new( + output.value, + output.script_public_key.clone(), + UNACCEPTED_DAA_SCORE, + false, + output.asset_type, + )); } } } diff --git a/mining/src/mempool/validate_and_insert_transaction.rs b/mining/src/mempool/validate_and_insert_transaction.rs index 3a04f7b082..602cf5775d 100644 --- a/mining/src/mempool/validate_and_insert_transaction.rs +++ b/mining/src/mempool/validate_and_insert_transaction.rs @@ -140,7 +140,13 @@ impl Mempool { for (i, input) in orphan.mtx.tx.inputs.iter().enumerate() { if input.previous_outpoint == outpoint { if orphan.mtx.entries[i].is_none() { - let entry = UtxoEntry::new(output.value, output.script_public_key.clone(), UNACCEPTED_DAA_SCORE, false); + let entry = UtxoEntry::new( + output.value, + output.script_public_key.clone(), + UNACCEPTED_DAA_SCORE, + false, + output.asset_type, + ); orphan.mtx.entries[i] = Some(entry); if orphan.mtx.is_verifiable() { orphan_id = Some(orphan.id()); diff --git a/mining/src/testutils/coinbase_mock.rs b/mining/src/testutils/coinbase_mock.rs index 61668b742b..4b92afe171 100644 --- a/mining/src/testutils/coinbase_mock.rs +++ b/mining/src/testutils/coinbase_mock.rs @@ -1,3 +1,5 @@ +use kash_consensus_core::asset_type::AssetType::KSH; +use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ coinbase::{CoinbaseData, CoinbaseTransactionTemplate, MinerData}, constants::{SOMPI_PER_KASH, TX_VERSION}, @@ -18,12 +20,12 @@ impl CoinbaseManagerMock { pub(super) fn expected_coinbase_transaction(&self, miner_data: MinerData) -> CoinbaseTransactionTemplate { const SUBSIDY: u64 = 500 * SOMPI_PER_KASH; - let output = TransactionOutput::new(SUBSIDY, miner_data.script_public_key.clone()); + let output = TransactionOutput::new(SUBSIDY, miner_data.script_public_key.clone(), KSH); let payload = self.serialize_coinbase_payload(&CoinbaseData { blue_score: 1, subsidy: SUBSIDY, miner_data }); CoinbaseTransactionTemplate { - tx: Transaction::new(TX_VERSION, vec![], vec![output], 0, SUBNETWORK_ID_COINBASE, 0, payload), + tx: Transaction::new(TX_VERSION, vec![], vec![output], TransferKSH, 0, SUBNETWORK_ID_COINBASE, 0, payload), has_red_reward: false, } } diff --git a/mining/src/testutils/consensus_mock.rs b/mining/src/testutils/consensus_mock.rs index ec33806531..1b3674daec 100644 --- a/mining/src/testutils/consensus_mock.rs +++ b/mining/src/testutils/consensus_mock.rs @@ -18,6 +18,7 @@ use kash_consensus_core::{ use kash_core::time::unix_now; use kash_hashes::ZERO_HASH; +use kash_consensus_core::asset_type::AssetType::KSH; use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -53,7 +54,7 @@ impl ConsensusMock { transaction.tx.outputs.iter().enumerate().for_each(|(i, x)| { utxos.insert( TransactionOutpoint::new(transaction.id(), i as u32), - UtxoEntry::new(x.value, x.script_public_key.clone(), block_daa_score, transaction.tx.is_coinbase()), + UtxoEntry::new(x.value, x.script_public_key.clone(), block_daa_score, transaction.tx.is_coinbase(), KSH), ); }); // Register the transaction diff --git a/protocol/flows/src/v5/mod.rs b/protocol/flows/src/v5/mod.rs index d5f7574df4..f0d974196e 100644 --- a/protocol/flows/src/v5/mod.rs +++ b/protocol/flows/src/v5/mod.rs @@ -119,8 +119,7 @@ pub fn register(ctx: FlowContext, router: Arc) -> Vec> { Box::new(RelayTransactionsFlow::new( ctx.clone(), router.clone(), - router - .subscribe_with_capacity(vec![KashdMessagePayloadType::InvTransactions], RelayTransactionsFlow::invs_channel_size()), + router.subscribe_with_capacity(vec![KashdMessagePayloadType::InvTransactions], RelayTransactionsFlow::invs_channel_size()), router.subscribe_with_capacity( vec![KashdMessagePayloadType::Transaction, KashdMessagePayloadType::TransactionNotFound], RelayTransactionsFlow::txs_channel_size(), diff --git a/protocol/flows/src/v5/request_ibd_blocks.rs b/protocol/flows/src/v5/request_ibd_blocks.rs index a9d53e2f67..37f591a0f2 100644 --- a/protocol/flows/src/v5/request_ibd_blocks.rs +++ b/protocol/flows/src/v5/request_ibd_blocks.rs @@ -1,8 +1,6 @@ use crate::{flow_context::FlowContext, flow_trait::Flow}; use kash_core::debug; -use kash_p2p_lib::{ - common::ProtocolError, dequeue_with_request_id, make_response, pb::kashd_message::Payload, IncomingRoute, Router, -}; +use kash_p2p_lib::{common::ProtocolError, dequeue_with_request_id, make_response, pb::kashd_message::Payload, IncomingRoute, Router}; use std::sync::Arc; pub struct HandleIbdBlockRequests { diff --git a/protocol/flows/src/v6/mod.rs b/protocol/flows/src/v6/mod.rs index e06d9c7f18..30c5c42985 100644 --- a/protocol/flows/src/v6/mod.rs +++ b/protocol/flows/src/v6/mod.rs @@ -100,8 +100,7 @@ pub fn register(ctx: FlowContext, router: Arc) -> Vec> { Box::new(RelayTransactionsFlow::new( ctx.clone(), router.clone(), - router - .subscribe_with_capacity(vec![KashdMessagePayloadType::InvTransactions], RelayTransactionsFlow::invs_channel_size()), + router.subscribe_with_capacity(vec![KashdMessagePayloadType::InvTransactions], RelayTransactionsFlow::invs_channel_size()), router.subscribe_with_capacity( vec![KashdMessagePayloadType::Transaction, KashdMessagePayloadType::TransactionNotFound], RelayTransactionsFlow::txs_channel_size(), diff --git a/protocol/p2p/proto/p2p.proto b/protocol/p2p/proto/p2p.proto index b1a2a6b96f..5b1e7021e8 100644 --- a/protocol/p2p/proto/p2p.proto +++ b/protocol/p2p/proto/p2p.proto @@ -26,9 +26,10 @@ message TransactionMessage{ uint32 version = 1; repeated TransactionInput inputs = 2; repeated TransactionOutput outputs = 3; - uint64 lockTime = 4; - SubnetworkId subnetworkId = 5; - uint64 gas = 6; + uint32 kind = 4; + uint64 lockTime = 5; + SubnetworkId subnetworkId = 6; + uint64 gas = 7; bytes payload = 8; } @@ -55,6 +56,7 @@ message ScriptPublicKey { message TransactionOutput{ uint64 value = 1; ScriptPublicKey scriptPublicKey = 2; + uint32 assetType = 3; } message BlockMessage{ @@ -170,6 +172,7 @@ message UtxoEntry { ScriptPublicKey scriptPublicKey = 2; uint64 blockDaaScore = 3; bool isCoinbase = 4; + uint32 assetType = 5; } message RequestNextPruningPointUtxoSetChunkMessage { diff --git a/protocol/p2p/src/convert/tx.rs b/protocol/p2p/src/convert/tx.rs index 84e55a9f99..bce9a7b8a4 100644 --- a/protocol/p2p/src/convert/tx.rs +++ b/protocol/p2p/src/convert/tx.rs @@ -53,7 +53,7 @@ impl From<&TransactionInput> for protowire::TransactionInput { impl From<&TransactionOutput> for protowire::TransactionOutput { fn from(output: &TransactionOutput) -> Self { - Self { value: output.value, script_public_key: Some((&output.script_public_key).into()) } + Self { value: output.value, script_public_key: Some((&output.script_public_key).into()), asset_type: output.asset_type.into() } } } @@ -63,6 +63,7 @@ impl From<&Transaction> for protowire::TransactionMessage { version: tx.version as u32, inputs: tx.inputs.iter().map(|input| input.into()).collect(), outputs: tx.outputs.iter().map(|output| output.into()).collect(), + kind: tx.kind.into(), lock_time: tx.lock_time, subnetwork_id: Some((&tx.subnetwork_id).into()), gas: tx.gas, @@ -103,7 +104,13 @@ impl TryFrom for UtxoEntry { type Error = ConversionError; fn try_from(value: protowire::UtxoEntry) -> Result { - Ok(Self::new(value.amount, value.script_public_key.try_into_ex()?, value.block_daa_score, value.is_coinbase)) + Ok(Self::new( + value.amount, + value.script_public_key.try_into_ex()?, + value.block_daa_score, + value.is_coinbase, + value.asset_type.into(), + )) } } @@ -127,7 +134,7 @@ impl TryFrom for TransactionOutput { type Error = ConversionError; fn try_from(output: protowire::TransactionOutput) -> Result { - Ok(Self::new(output.value, output.script_public_key.try_into_ex()?)) + Ok(Self::new(output.value, output.script_public_key.try_into_ex()?, output.asset_type.into())) } } @@ -139,6 +146,7 @@ impl TryFrom for Transaction { tx.version.try_into()?, tx.inputs.into_iter().map(|i| i.try_into()).collect::, Self::Error>>()?, tx.outputs.into_iter().map(|i| i.try_into()).collect::, Self::Error>>()?, + tx.kind.into(), tx.lock_time, tx.subnetwork_id.try_into_ex()?, tx.gas, diff --git a/protocol/p2p/src/convert/utxo.rs b/protocol/p2p/src/convert/utxo.rs index 2550873360..75ec58db96 100644 --- a/protocol/p2p/src/convert/utxo.rs +++ b/protocol/p2p/src/convert/utxo.rs @@ -12,6 +12,7 @@ impl From<&UtxoEntry> for protowire::UtxoEntry { script_public_key: Some((&entry.script_public_key).into()), block_daa_score: entry.block_daa_score, is_coinbase: entry.is_coinbase, + asset_type: entry.asset_type.into(), } } } diff --git a/protocol/p2p/src/core/payload_type.rs b/protocol/p2p/src/core/payload_type.rs index 61e2ead627..17d83c05f5 100644 --- a/protocol/p2p/src/core/payload_type.rs +++ b/protocol/p2p/src/core/payload_type.rs @@ -76,9 +76,7 @@ impl From<&KashdMessagePayload> for KashdMessagePayloadType { KashdMessagePayloadType::RequestNextPruningPointUtxoSetChunk } KashdMessagePayload::DonePruningPointUtxoSetChunks(_) => KashdMessagePayloadType::DonePruningPointUtxoSetChunks, - KashdMessagePayload::IbdBlockLocatorHighestHashNotFound(_) => { - KashdMessagePayloadType::IbdBlockLocatorHighestHashNotFound - } + KashdMessagePayload::IbdBlockLocatorHighestHashNotFound(_) => KashdMessagePayloadType::IbdBlockLocatorHighestHashNotFound, KashdMessagePayload::BlockWithTrustedData(_) => KashdMessagePayloadType::BlockWithTrustedData, KashdMessagePayload::DoneBlocksWithTrustedData(_) => KashdMessagePayloadType::DoneBlocksWithTrustedData, KashdMessagePayload::RequestPruningPointAndItsAnticone(_) => KashdMessagePayloadType::RequestPruningPointAndItsAnticone, diff --git a/rothschild/src/main.rs b/rothschild/src/main.rs index a043d7033d..a0da431ea1 100644 --- a/rothschild/src/main.rs +++ b/rothschild/src/main.rs @@ -3,6 +3,8 @@ use std::{collections::HashMap, time::Duration}; use clap::{Arg, Command}; use itertools::Itertools; use kash_addresses::Address; +use kash_consensus_core::asset_type::AssetType::KSH; +use kash_consensus_core::tx::TransactionKind; use kash_consensus_core::{ config::params::{TESTNET11_PARAMS, TESTNET_PARAMS}, constants::TX_VERSION, @@ -106,11 +108,8 @@ async fn main() { return; }; - let kash_addr = Address::new( - kash_addresses::Prefix::Testnet, - kash_addresses::Version::PubKey, - &schnorr_key.x_only_public_key().0.serialize(), - ); + let kash_addr = + Address::new(kash_addresses::Prefix::Testnet, kash_addresses::Version::PubKey, &schnorr_key.x_only_public_key().0.serialize()); info!("Using Rothschild with private key {} and address {}", schnorr_key.display_secret(), String::from(&kash_addr)); let info = rpc_client.get_block_dag_info().await.unwrap(); @@ -327,9 +326,9 @@ fn generate_tx( .collect_vec(); let outputs = (0..num_outs) - .map(|_| TransactionOutput { value: send_amount / num_outs, script_public_key: script_public_key.clone() }) + .map(|_| TransactionOutput { value: send_amount / num_outs, script_public_key: script_public_key.clone(), asset_type: KSH }) .collect_vec(); - let unsigned_tx = Transaction::new(TX_VERSION, inputs, outputs, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + let unsigned_tx = Transaction::new(TX_VERSION, inputs, outputs, TransactionKind::TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); let signed_tx = sign(MutableTransaction::with_entries(unsigned_tx, utxos.iter().map(|(_, entry)| entry.clone()).collect_vec()), schnorr_key); signed_tx.tx diff --git a/rpc/core/src/convert/tx.rs b/rpc/core/src/convert/tx.rs index e72c3512c9..59f59823b1 100644 --- a/rpc/core/src/convert/tx.rs +++ b/rpc/core/src/convert/tx.rs @@ -11,6 +11,7 @@ impl From<&Transaction> for RpcTransaction { version: item.version, inputs: item.inputs.iter().map(RpcTransactionInput::from).collect(), outputs: item.outputs.iter().map(RpcTransactionOutput::from).collect(), + kind: item.kind, lock_time: item.lock_time, subnetwork_id: item.subnetwork_id.clone(), gas: item.gas, @@ -26,6 +27,7 @@ impl From<&TransactionOutput> for RpcTransactionOutput { Self { value: item.value, script_public_key: item.script_public_key.clone(), + asset_type: item.asset_type, // TODO: Implement a populating process inspired from kashd\app\rpc\rpccontext\verbosedata.go verbose_data: None, } @@ -62,6 +64,7 @@ impl TryFrom<&RpcTransaction> for Transaction { .iter() .map(kash_consensus_core::tx::TransactionOutput::try_from) .collect::>>()?, + item.kind, item.lock_time, item.subnetwork_id.clone(), item.gas, @@ -73,7 +76,7 @@ impl TryFrom<&RpcTransaction> for Transaction { impl TryFrom<&RpcTransactionOutput> for TransactionOutput { type Error = RpcError; fn try_from(item: &RpcTransactionOutput) -> RpcResult { - Ok(Self::new(item.value, item.script_public_key.clone())) + Ok(Self::new(item.value, item.script_public_key.clone(), item.asset_type)) } } diff --git a/rpc/core/src/convert/utxo.rs b/rpc/core/src/convert/utxo.rs index 1b631e90d7..abd0b2d3ff 100644 --- a/rpc/core/src/convert/utxo.rs +++ b/rpc/core/src/convert/utxo.rs @@ -17,7 +17,13 @@ pub fn utxo_set_into_rpc(item: &UtxoSetByScriptPublicKey, prefix: Option .map(|(outpoint, entry)| RpcUtxosByAddressesEntry { address: address.clone(), outpoint: *outpoint, - utxo_entry: UtxoEntry::new(entry.amount, script_public_key.clone(), entry.block_daa_score, entry.is_coinbase), + utxo_entry: UtxoEntry::new( + entry.amount, + script_public_key.clone(), + entry.block_daa_score, + entry.is_coinbase, + entry.asset_type, + ), }) .collect::>() }) diff --git a/rpc/core/src/model/tx.rs b/rpc/core/src/model/tx.rs index 2db2594a98..4be7b4c264 100644 --- a/rpc/core/src/model/tx.rs +++ b/rpc/core/src/model/tx.rs @@ -1,7 +1,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use kash_addresses::Address; +use kash_consensus_core::asset_type::AssetType; use kash_consensus_core::tx::{ - ScriptPublicKey, ScriptVec, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, + ScriptPublicKey, ScriptVec, TransactionId, TransactionInput, TransactionKind, TransactionOutpoint, TransactionOutput, UtxoEntry, }; use serde::{Deserialize, Serialize}; @@ -57,6 +58,7 @@ pub struct RpcTransactionInputVerboseData {} pub struct RpcTransactionOutput { pub value: u64, pub script_public_key: RpcScriptPublicKey, + pub asset_type: AssetType, pub verbose_data: Option, } @@ -68,7 +70,7 @@ impl RpcTransactionOutput { impl From for RpcTransactionOutput { fn from(output: TransactionOutput) -> Self { - Self { value: output.value, script_public_key: output.script_public_key, verbose_data: None } + Self { value: output.value, script_public_key: output.script_public_key, verbose_data: None, asset_type: output.asset_type } } } @@ -87,6 +89,7 @@ pub struct RpcTransaction { pub version: u16, pub inputs: Vec, pub outputs: Vec, + pub kind: TransactionKind, pub lock_time: u64, pub subnetwork_id: RpcSubnetworkId, pub gas: u64, diff --git a/rpc/core/src/wasm/mod.rs b/rpc/core/src/wasm/mod.rs index 3295d4dd00..4843cdbea3 100644 --- a/rpc/core/src/wasm/mod.rs +++ b/rpc/core/src/wasm/mod.rs @@ -31,7 +31,12 @@ impl From for RpcTransactionInput { impl From for RpcTransactionOutput { fn from(output: TransactionOutput) -> Self { let inner = output.inner(); - RpcTransactionOutput { value: inner.value, script_public_key: inner.script_public_key.clone(), verbose_data: None } + RpcTransactionOutput { + value: inner.value, + script_public_key: inner.script_public_key.clone(), + verbose_data: None, + asset_type: inner.asset_type, + } } } @@ -53,6 +58,7 @@ impl From<&Transaction> for RpcTransaction { version: inner.version, inputs, outputs, + kind: inner.kind, lock_time: inner.lock_time, subnetwork_id: inner.subnetwork_id.clone(), gas: inner.gas, @@ -70,6 +76,7 @@ impl From for RpcTransaction { version: tx.version, inputs: RpcTransactionInput::from_transaction_inputs(tx.inputs), outputs: RpcTransactionOutput::from_transaction_outputs(tx.outputs), + kind: tx.kind, lock_time: tx.lock_time, subnetwork_id: tx.subnetwork_id, gas: tx.gas, diff --git a/rpc/grpc/core/proto/rpc.proto b/rpc/grpc/core/proto/rpc.proto index ae5020d322..80249209ca 100644 --- a/rpc/grpc/core/proto/rpc.proto +++ b/rpc/grpc/core/proto/rpc.proto @@ -59,9 +59,10 @@ message RpcTransaction { uint32 version = 1; repeated RpcTransactionInput inputs = 2; repeated RpcTransactionOutput outputs = 3; - uint64 lockTime = 4; - string subnetworkId = 5; - uint64 gas = 6; + uint32 kind = 4; + uint64 lockTime = 5; + string subnetworkId = 6; + uint64 gas = 7; string payload = 8; RpcTransactionVerboseData verboseData = 9; } @@ -82,7 +83,8 @@ message RpcScriptPublicKey { message RpcTransactionOutput { uint64 amount = 1; RpcScriptPublicKey scriptPublicKey = 2; - RpcTransactionOutputVerboseData verboseData = 3; + uint32 assetType = 3; + RpcTransactionOutputVerboseData verboseData = 4; } message RpcOutpoint { @@ -95,6 +97,7 @@ message RpcUtxoEntry { RpcScriptPublicKey scriptPublicKey = 2; uint64 blockDaaScore = 3; bool isCoinbase = 4; + uint32 assetType = 5; } message RpcTransactionVerboseData{ diff --git a/rpc/grpc/core/src/convert/message.rs b/rpc/grpc/core/src/convert/message.rs index 4724184553..5de0c127a7 100644 --- a/rpc/grpc/core/src/convert/message.rs +++ b/rpc/grpc/core/src/convert/message.rs @@ -893,10 +893,7 @@ mod tests { protowire: protowire::SubmitBlockResponseMessage, } impl Test { - fn new( - rpc_core: RpcResult, - protowire: protowire::SubmitBlockResponseMessage, - ) -> Self { + fn new(rpc_core: RpcResult, protowire: protowire::SubmitBlockResponseMessage) -> Self { Self { rpc_core, protowire } } } diff --git a/rpc/grpc/core/src/convert/tx.rs b/rpc/grpc/core/src/convert/tx.rs index fcf43970fb..d272f14635 100644 --- a/rpc/grpc/core/src/convert/tx.rs +++ b/rpc/grpc/core/src/convert/tx.rs @@ -1,5 +1,6 @@ use crate::protowire; use crate::{from, try_from}; +use kash_consensus_core::asset_type::AssetType; use kash_rpc_core::{FromRpcHex, RpcError, RpcHash, RpcResult, RpcScriptVec, ToRpcHex}; use std::str::FromStr; @@ -12,6 +13,7 @@ from!(item: &kash_rpc_core::RpcTransaction, protowire::RpcTransaction, { version: item.version.into(), inputs: item.inputs.iter().map(protowire::RpcTransactionInput::from).collect(), outputs: item.outputs.iter().map(protowire::RpcTransactionOutput::from).collect(), + kind: item.kind.into(), lock_time: item.lock_time, subnetwork_id: item.subnetwork_id.to_string(), gas: item.gas, @@ -35,6 +37,7 @@ from!(item: &kash_rpc_core::RpcTransactionOutput, protowire::RpcTransactionOutpu amount: item.value, script_public_key: Some((&item.script_public_key).into()), verbose_data: item.verbose_data.as_ref().map(|x| x.into()), + asset_type: item.asset_type.into(), } }); @@ -48,6 +51,7 @@ from!(item: &kash_rpc_core::RpcUtxoEntry, protowire::RpcUtxoEntry, { script_public_key: Some((&item.script_public_key).into()), block_daa_score: item.block_daa_score, is_coinbase: item.is_coinbase, + asset_type: item.asset_type.into(), } }); @@ -106,6 +110,7 @@ try_from!(item: &protowire::RpcTransaction, kash_rpc_core::RpcTransaction, { .iter() .map(kash_rpc_core::RpcTransactionOutput::try_from) .collect::>>()?, + kind: item.kind.into(), lock_time: item.lock_time, subnetwork_id: kash_rpc_core::RpcSubnetworkId::from_str(&item.subnetwork_id)?, gas: item.gas, @@ -137,6 +142,7 @@ try_from!(item: &protowire::RpcTransactionOutput, kash_rpc_core::RpcTransactionO .ok_or_else(|| RpcError::MissingRpcFieldError("RpcTransactionOutput".to_string(), "script_public_key".to_string()))? .try_into()?, verbose_data: item.verbose_data.as_ref().map(kash_rpc_core::RpcTransactionOutputVerboseData::try_from).transpose()?, + asset_type: AssetType::from(item.asset_type), } }); @@ -154,6 +160,7 @@ try_from!(item: &protowire::RpcUtxoEntry, kash_rpc_core::RpcUtxoEntry, { .try_into()?, block_daa_score: item.block_daa_score, is_coinbase: item.is_coinbase, + asset_type: AssetType::from(item.asset_type), } }); diff --git a/rpc/grpc/core/src/ext/kashd.rs b/rpc/grpc/core/src/ext/kashd.rs index 822e0d604a..2cadb88233 100644 --- a/rpc/grpc/core/src/ext/kashd.rs +++ b/rpc/grpc/core/src/ext/kashd.rs @@ -1,10 +1,9 @@ use kash_notify::{scope::Scope, subscription::Command}; use crate::protowire::{ - kashd_request, kashd_response, KashdRequest, KashdResponse, NotifyBlockAddedRequestMessage, - NotifyFinalityConflictRequestMessage, NotifyNewBlockTemplateRequestMessage, NotifyPruningPointUtxoSetOverrideRequestMessage, - NotifySinkBlueScoreChangedRequestMessage, NotifyUtxosChangedRequestMessage, NotifyVirtualChainChangedRequestMessage, - NotifyVirtualDaaScoreChangedRequestMessage, + kashd_request, kashd_response, KashdRequest, KashdResponse, NotifyBlockAddedRequestMessage, NotifyFinalityConflictRequestMessage, + NotifyNewBlockTemplateRequestMessage, NotifyPruningPointUtxoSetOverrideRequestMessage, NotifySinkBlueScoreChangedRequestMessage, + NotifyUtxosChangedRequestMessage, NotifyVirtualChainChangedRequestMessage, NotifyVirtualDaaScoreChangedRequestMessage, }; impl KashdRequest { @@ -24,9 +23,7 @@ impl kashd_request::Payload { kashd_request::Payload::NotifyBlockAddedRequest(NotifyBlockAddedRequestMessage { command: command.into() }) } Scope::NewBlockTemplate(_) => { - kashd_request::Payload::NotifyNewBlockTemplateRequest(NotifyNewBlockTemplateRequestMessage { - command: command.into(), - }) + kashd_request::Payload::NotifyNewBlockTemplateRequest(NotifyNewBlockTemplateRequestMessage { command: command.into() }) } Scope::VirtualChainChanged(ref scope) => { @@ -36,14 +33,10 @@ impl kashd_request::Payload { }) } Scope::FinalityConflict(_) => { - kashd_request::Payload::NotifyFinalityConflictRequest(NotifyFinalityConflictRequestMessage { - command: command.into(), - }) + kashd_request::Payload::NotifyFinalityConflictRequest(NotifyFinalityConflictRequestMessage { command: command.into() }) } Scope::FinalityConflictResolved(_) => { - kashd_request::Payload::NotifyFinalityConflictRequest(NotifyFinalityConflictRequestMessage { - command: command.into(), - }) + kashd_request::Payload::NotifyFinalityConflictRequest(NotifyFinalityConflictRequestMessage { command: command.into() }) } Scope::UtxosChanged(ref scope) => kashd_request::Payload::NotifyUtxosChangedRequest(NotifyUtxosChangedRequestMessage { addresses: scope.addresses.iter().map(|x| x.into()).collect::>(), diff --git a/rpc/grpc/server/src/request_handler/interface.rs b/rpc/grpc/server/src/request_handler/interface.rs index d9f68849ea..e0acdfb2f6 100644 --- a/rpc/grpc/server/src/request_handler/interface.rs +++ b/rpc/grpc/server/src/request_handler/interface.rs @@ -58,13 +58,7 @@ impl Interface { let _ = self.methods.insert(op, method); } - pub fn set_method_properties( - &mut self, - op: KashdPayloadOps, - tasks: usize, - queue_size: usize, - routing_policy: KashdRoutingPolicy, - ) { + pub fn set_method_properties(&mut self, op: KashdPayloadOps, tasks: usize, queue_size: usize, routing_policy: KashdRoutingPolicy) { self.methods.entry(op).and_modify(|x| { let method: Method = Method::with_properties(x.method_fn(), tasks, queue_size, routing_policy); @@ -73,12 +67,7 @@ impl Interface { }); } - pub async fn call( - &self, - op: &KashdPayloadOps, - connection: Connection, - request: KashdRequest, - ) -> GrpcServerResult { + pub async fn call(&self, op: &KashdPayloadOps, connection: Connection, request: KashdRequest) -> GrpcServerResult { self.methods.get(op).unwrap_or(&self.method_not_implemented).call(self.server_ctx.clone(), connection, request).await } diff --git a/rpc/service/src/converter/consensus.rs b/rpc/service/src/converter/consensus.rs index 4544c973bb..fbba1b8da1 100644 --- a/rpc/service/src/converter/consensus.rs +++ b/rpc/service/src/converter/consensus.rs @@ -134,6 +134,7 @@ impl ConsensusConverter { version: transaction.version, inputs: transaction.inputs.iter().map(|x| self.get_transaction_input(x)).collect(), outputs: transaction.outputs.iter().map(|x| self.get_transaction_output(x)).collect(), + kind: transaction.kind, lock_time: transaction.lock_time, subnetwork_id: transaction.subnetwork_id.clone(), gas: transaction.gas, @@ -154,7 +155,12 @@ impl ConsensusConverter { let address = extract_script_pub_key_address(&output.script_public_key, self.config.prefix()).ok(); let verbose_data = address.map(|address| RpcTransactionOutputVerboseData { script_public_key_type, script_public_key_address: address }); - RpcTransactionOutput { value: output.value, script_public_key: output.script_public_key.clone(), verbose_data } + RpcTransactionOutput { + value: output.value, + script_public_key: output.script_public_key.clone(), + verbose_data, + asset_type: output.asset_type, + } } pub async fn get_virtual_chain_accepted_transaction_ids( diff --git a/simpa/src/simulator/miner.rs b/simpa/src/simulator/miner.rs index cd4d809885..fb52aa872a 100644 --- a/simpa/src/simulator/miner.rs +++ b/simpa/src/simulator/miner.rs @@ -4,12 +4,14 @@ use kash_consensus::consensus::Consensus; use kash_consensus::model::stores::virtual_state::VirtualStateStoreReader; use kash_consensus::params::Params; use kash_consensus_core::api::ConsensusApi; +use kash_consensus_core::asset_type::AssetType; use kash_consensus_core::block::{Block, TemplateBuildMode, TemplateTransactionSelector}; use kash_consensus_core::coinbase::MinerData; use kash_consensus_core::sign::sign; use kash_consensus_core::subnets::SUBNETWORK_ID_NATIVE; use kash_consensus_core::tx::{ - MutableTransaction, ScriptPublicKey, ScriptVec, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, + MutableTransaction, ScriptPublicKey, ScriptVec, Transaction, TransactionInput, TransactionKind, TransactionOutpoint, + TransactionOutput, UtxoEntry, }; use kash_consensus_core::utxo::utxo_view::UtxoView; use kash_core::trace; @@ -167,18 +169,20 @@ impl Miner { Some(entry) } + // Creates an unsigned transaction for simulation purposes. fn create_unsigned_tx(&self, outpoint: TransactionOutpoint, input_amount: u64, multiple_outputs: bool) -> Transaction { Transaction::new( 0, vec![TransactionInput::new(outpoint, vec![], 0, 0)], if multiple_outputs && input_amount > 4 { vec![ - TransactionOutput::new(input_amount / 2, self.miner_data.script_public_key.clone()), - TransactionOutput::new(input_amount / 2 - 1, self.miner_data.script_public_key.clone()), + TransactionOutput::new(input_amount / 2, self.miner_data.script_public_key.clone(), AssetType::KSH), + TransactionOutput::new(input_amount / 2 - 1, self.miner_data.script_public_key.clone(), AssetType::KSH), ] } else { - vec![TransactionOutput::new(input_amount - 1, self.miner_data.script_public_key.clone())] + vec![TransactionOutput::new(input_amount - 1, self.miner_data.script_public_key.clone(), AssetType::KSH)] }, + TransactionKind::TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, diff --git a/testing/integration/src/common/utils.rs b/testing/integration/src/common/utils.rs index 9538b8c11d..760893f963 100644 --- a/testing/integration/src/common/utils.rs +++ b/testing/integration/src/common/utils.rs @@ -1,4 +1,6 @@ use itertools::Itertools; +use kash_consensus_core::asset_type::AssetType::KSH; +use kash_consensus_core::tx::TransactionKind::TransferKSH; use kash_consensus_core::{ constants::TX_VERSION, sign::sign, @@ -67,9 +69,9 @@ pub fn generate_tx_dag( let total_in = entries.iter().map(|e| e.amount).sum::(); let total_out = total_in - required_fee(num_inputs, num_outputs); let outputs = (0..num_outputs) - .map(|_| TransactionOutput { value: total_out / num_outputs, script_public_key: spk.clone() }) + .map(|_| TransactionOutput { value: total_out / num_outputs, script_public_key: spk.clone(), asset_type: KSH }) .collect_vec(); - let unsigned_tx = Transaction::new(TX_VERSION, inputs, outputs, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + let unsigned_tx = Transaction::new(TX_VERSION, inputs, outputs, TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); sign(SignableTransaction::with_entries(unsigned_tx, entries), schnorr_key) }) .collect::>() diff --git a/testing/integration/src/consensus_integration_tests.rs b/testing/integration/src/consensus_integration_tests.rs index f874158c14..62d837ded0 100644 --- a/testing/integration/src/consensus_integration_tests.rs +++ b/testing/integration/src/consensus_integration_tests.rs @@ -30,7 +30,9 @@ use kash_consensus_core::header::Header; use kash_consensus_core::network::{NetworkId, NetworkType::Mainnet}; use kash_consensus_core::subnets::SubnetworkId; use kash_consensus_core::trusted::{ExternalGhostdagData, TrustedBlock}; -use kash_consensus_core::tx::{ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry}; +use kash_consensus_core::tx::{ + ScriptPublicKey, Transaction, TransactionInput, TransactionKind, TransactionOutpoint, TransactionOutput, UtxoEntry, +}; use kash_consensus_core::{blockhash, hashing, BlockHashMap, BlueWorkType}; use kash_consensus_notify::root::ConsensusNotificationRoot; use kash_consensus_notify::service::NotifyService; @@ -43,6 +45,7 @@ use kash_hashes::Hash; use flate2::read::GzDecoder; use futures_util::future::try_join_all; use itertools::Itertools; +use kash_consensus_core::asset_type::AssetType; use kash_core::core::Core; use kash_core::signals::Shutdown; use kash_core::task::runtime::AsyncRuntime; @@ -662,6 +665,7 @@ struct RPCTransaction { struct RPCTransactionOutput { Amount: u64, ScriptPublicKey: RPCScriptPublicKey, + AssetType: AssetType, } #[allow(non_snake_case)] @@ -755,6 +759,7 @@ struct RPCUTXOEntry { ScriptPublicKey: RPCScriptPublicKey, BlockDAAScore: u64, IsCoinbase: bool, + AssetType: AssetType, } #[allow(non_snake_case)] @@ -1158,6 +1163,7 @@ fn json_line_to_utxo_pairs(line: String) -> Vec<(TransactionOutpoint, UtxoEntry) ), block_daa_score: json_pair.UTXOEntry.BlockDAAScore, is_coinbase: json_pair.UTXOEntry.IsCoinbase, + asset_type: json_pair.UTXOEntry.AssetType, }, ) }) @@ -1200,8 +1206,10 @@ fn rpc_block_to_block(rpc_block: RPCBlock) -> Block { output.ScriptPublicKey.Version, hex_decode(&output.ScriptPublicKey.Script), ), + asset_type: output.AssetType, }) .collect(), + TransactionKind::TransferKSH, tx.LockTime, SubnetworkId::from_str(&tx.SubnetworkID).unwrap(), tx.Gas, diff --git a/testing/integration/src/rpc_tests.rs b/testing/integration/src/rpc_tests.rs index 87526da59a..fabe980dac 100644 --- a/testing/integration/src/rpc_tests.rs +++ b/testing/integration/src/rpc_tests.rs @@ -4,6 +4,7 @@ use crate::common::{client_notify::ChannelNotify, daemon::Daemon}; use futures_util::future::try_join_all; use kash_addresses::{Address, Prefix, Version}; use kash_consensus::params::SIMNET_GENESIS; +use kash_consensus_core::tx::TransactionKind; use kash_consensus_core::{constants::MAX_SOMPI, subnets::SubnetworkId, tx::Transaction}; use kash_core::info; use kash_grpc_core::ops::KashdPayloadOps; @@ -292,7 +293,8 @@ async fn sanity_test() { let rpc_client = client.clone(); tst!(op, { // Build an erroneous transaction... - let transaction = Transaction::new(0, vec![], vec![], 0, SubnetworkId::default(), 0, vec![]); + let transaction = + Transaction::new(0, vec![], vec![], TransactionKind::TransferKSH, 0, SubnetworkId::default(), 0, vec![]); let result = rpc_client.submit_transaction((&transaction).into(), false).await; // ...that gets rejected by the consensus assert!(result.is_err()); diff --git a/wallet/core/src/derivation/mod.rs b/wallet/core/src/derivation/mod.rs index f9df45b2a7..97ad0890e3 100644 --- a/wallet/core/src/derivation/mod.rs +++ b/wallet/core/src/derivation/mod.rs @@ -14,9 +14,7 @@ use crate::runtime::AccountKind; use crate::Result; use kash_bip32::{AddressType, DerivationPath, ExtendedPrivateKey, ExtendedPublicKey, Language, Mnemonic, SecretKeyExt}; use kash_consensus_core::network::NetworkType; -use kash_txscript::{ - extract_script_pub_key_address, multisig_redeem_script, multisig_redeem_script_ecdsa, pay_to_script_hash_script, -}; +use kash_txscript::{extract_script_pub_key_address, multisig_redeem_script, multisig_redeem_script_ecdsa, pay_to_script_hash_script}; use std::collections::HashMap; use std::sync::{Arc, Mutex, MutexGuard}; use wasm_bindgen::prelude::*; diff --git a/wallet/core/src/error.rs b/wallet/core/src/error.rs index 228c9c75f4..2b185601d7 100644 --- a/wallet/core/src/error.rs +++ b/wallet/core/src/error.rs @@ -12,6 +12,7 @@ use workflow_rpc::client::error::Error as RpcError; use workflow_wasm::jserror::*; use workflow_wasm::printable::*; +use kash_consensus_core::asset_type::AssetType; use thiserror::Error; #[derive(Debug, Error)] @@ -222,6 +223,15 @@ pub enum Error { #[error(transparent)] TxScriptError(#[from] kash_txscript_errors::TxScriptError), + + #[error("The transaction kind is not supported")] + UnsupportedTransactionKind, + + #[error("Invalid asset type")] + InvalidAssetType, + + #[error("UTXOs asset type mismatch: expected {expected}, found {found}")] + MismatchedAssetType { expected: AssetType, found: AssetType }, } impl From for Error { diff --git a/wallet/core/src/message.rs b/wallet/core/src/message.rs index 692962cfd3..a7d16e8383 100644 --- a/wallet/core/src/message.rs +++ b/wallet/core/src/message.rs @@ -126,7 +126,7 @@ Ut omnis magnam et accusamus earum rem impedit provident eum commodi repellat qu #[test] fn test_sign_and_verify_test_case_0() { - let pm = PersonalMessage("Hello Kash!"); + let pm = PersonalMessage("Hello Kaspa!"); let privkey: [u8; 32] = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, @@ -156,7 +156,7 @@ Ut omnis magnam et accusamus earum rem impedit provident eum commodi repellat qu #[test] fn test_sign_and_verify_test_case_1() { - let pm = PersonalMessage("Hello Kash!"); + let pm = PersonalMessage("Hello Kaspa!"); let privkey: [u8; 32] = [ 0xB7, 0xE1, 0x51, 0x62, 0x8A, 0xED, 0x2A, 0x6A, 0xBF, 0x71, 0x58, 0x80, 0x9C, 0xF4, 0xF3, 0xC7, 0x62, 0xE7, 0x16, 0x0F, 0x38, 0xB4, 0xDA, 0x56, 0xA7, 0x84, 0xD9, 0x04, 0x51, 0x90, 0xCF, 0xEF, diff --git a/wallet/core/src/runtime/account/mod.rs b/wallet/core/src/runtime/account/mod.rs index 3aa46f45db..a03fe08ef3 100644 --- a/wallet/core/src/runtime/account/mod.rs +++ b/wallet/core/src/runtime/account/mod.rs @@ -3,8 +3,8 @@ pub mod kind; pub mod variants; pub use id::*; -use kash_bip32::ChildNumber; use kash_bip32::ExtendedPrivateKey; +use kash_bip32::{ChildNumber, PrivateKeyBytes}; pub use kind::*; pub use variants::*; @@ -23,6 +23,8 @@ use crate::storage::{self, AccessContextT, AccountData, PrvKeyData, PrvKeyDataId use crate::tx::{Fees, Generator, GeneratorSettings, GeneratorSummary, PaymentDestination, PendingTransaction, Signer}; use crate::utxo::{UtxoContext, UtxoContextBinding}; use kash_bip32::PrivateKey; +use kash_consensus_core::asset_type::AssetType; +use kash_consensus_core::tx::TransactionKind; use kash_consensus_wasm::UtxoEntryReference; use kash_notify::listener::ListenerId; use separator::Separatable; @@ -202,7 +204,9 @@ pub trait Account: AnySync + Send + Sync + 'static { self.utxo_context().clear().await?; let current_daa_score = self.wallet().current_daa_score().ok_or(Error::NotConnected)?; - let balance = Arc::new(AtomicBalance::default()); + let ksh_balance = Arc::new(AtomicBalance::default()); + let kusd_balance = Arc::new(AtomicBalance::default()); + let krv_balance = Arc::new(AtomicBalance::default()); match self.clone().as_derivation_capable() { Ok(account) => { @@ -216,14 +220,18 @@ pub trait Account: AnySync + Send + Sync + 'static { let scans = vec![ Scan::new_with_address_manager( derivation.receive_address_manager(), - &balance, + &ksh_balance, + &kusd_balance, + &krv_balance, current_daa_score, window_size, Some(extent), ), Scan::new_with_address_manager( derivation.change_address_manager(), - &balance, + &ksh_balance, + &kusd_balance, + &krv_balance, current_daa_score, window_size, Some(extent), @@ -239,7 +247,7 @@ pub trait Account: AnySync + Send + Sync + 'static { address_set.insert(self.receive_address()?); address_set.insert(self.change_address()?); - let scan = Scan::new_with_address_set(address_set, &balance, current_daa_score); + let scan = Scan::new_with_address_set(address_set, &ksh_balance, &kusd_balance, &krv_balance, current_daa_score); scan.scan(self.utxo_context()).await?; } } @@ -297,32 +305,57 @@ pub trait Account: AnySync + Send + Sync + 'static { payment_secret: Option, abortable: &Abortable, notifier: Option, - ) -> Result<(GeneratorSummary, Vec)> { + ) -> Result<(Vec, Vec)> { let keydata = self.prv_key_data(wallet_secret).await?; let signer = Arc::new(Signer::new(self.clone().as_dyn_arc(), keydata, payment_secret)); - let settings = - GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), PaymentDestination::Change, Fees::None, None)?; - let generator = Generator::try_new(settings, Some(signer), Some(abortable))?; - let mut stream = generator.stream(); - let mut ids = vec![]; - while let Some(transaction) = stream.try_next().await? { - if let Some(notifier) = notifier.as_ref() { - notifier(&transaction); + let mut all_summaries = Vec::new(); + let mut all_ids = Vec::new(); + + let asset_types = [AssetType::KSH, AssetType::KUSD, AssetType::KRV]; + + for asset_type in asset_types.iter() { + let tx_kind = match asset_type { + AssetType::KSH => TransactionKind::TransferKSH, + AssetType::KUSD => TransactionKind::TransferKUSD, + AssetType::KRV => TransactionKind::TransferKRV, + }; + + let settings = GeneratorSettings::try_new_with_account( + self.clone().as_dyn_arc(), + tx_kind, + PaymentDestination::Change, + Fees::None, + None, + )?; + + let generator = Generator::try_new(settings, Some(signer.clone()), Some(abortable))?; + + let mut stream = generator.stream(); + let mut ids = Vec::new(); + + while let Some(transaction) = stream.try_next().await? { + if let Some(notifier) = notifier.as_ref() { + notifier(&transaction); + } + + transaction.try_sign()?; + transaction.log().await?; + let id = transaction.try_submit(&self.wallet().rpc_api()).await?; + ids.push(id); + yield_executor().await; } - transaction.try_sign()?; - transaction.log().await?; - let id = transaction.try_submit(&self.wallet().rpc_api()).await?; - ids.push(id); - yield_executor().await; + all_summaries.push(generator.summary()); + all_ids.extend(ids); } - Ok((generator.summary(), ids)) + Ok((all_summaries, all_ids)) } async fn send( self: Arc, + asset_type: AssetType, destination: PaymentDestination, priority_fee_sompi: Fees, payload: Option>, @@ -334,7 +367,14 @@ pub trait Account: AnySync + Send + Sync + 'static { let keydata = self.prv_key_data(wallet_secret).await?; let signer = Arc::new(Signer::new(self.clone().as_dyn_arc(), keydata, payment_secret)); - let settings = GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), destination, priority_fee_sompi, payload)?; + let tx_kind = match asset_type { + AssetType::KSH => TransactionKind::TransferKSH, + AssetType::KUSD => TransactionKind::TransferKUSD, + AssetType::KRV => TransactionKind::TransferKRV, + }; + + let settings = + GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), tx_kind, destination, priority_fee_sompi, payload)?; let generator = Generator::try_new(settings, Some(signer), Some(abortable))?; @@ -357,12 +397,14 @@ pub trait Account: AnySync + Send + Sync + 'static { async fn estimate( self: Arc, + transaction_kind: TransactionKind, destination: PaymentDestination, priority_fee_sompi: Fees, payload: Option>, abortable: &Abortable, ) -> Result { - let settings = GeneratorSettings::try_new_with_account(self.as_dyn_arc(), destination, priority_fee_sompi, payload)?; + let settings = + GeneratorSettings::try_new_with_account(self.as_dyn_arc(), transaction_kind, destination, priority_fee_sompi, payload)?; let generator = Generator::try_new(settings, None, Some(abortable))?; @@ -470,29 +512,27 @@ pub trait DerivationCapableAccount: Account { aggregate_balance += balance; if sweep { - let utxos = utxos.into_iter().map(UtxoEntryReference::from).collect::>(); - - let settings = GeneratorSettings::try_new_with_iterator( - Box::new(utxos.into_iter()), - change_address.clone(), - 1, - 1, - PaymentDestination::Change, - Fees::None, - None, - None, - )?; - - let generator = Generator::try_new(settings, None, Some(abortable))?; - - let mut stream = generator.stream(); - while let Some(transaction) = stream.try_next().await? { - transaction.try_sign_with_keys(keys.clone())?; - let id = transaction.try_submit(&rpc).await?; - if let Some(notifier) = notifier { - notifier(index, balance, Some(id)); - } - yield_executor().await; + let asset_types = [AssetType::KSH, AssetType::KUSD, AssetType::KRV]; + for asset_type in asset_types.iter() { + let filtered_utxos = utxos + .clone() + .into_iter() + .filter(|utxo| utxo.utxo_entry.asset_type == *asset_type) + .map(UtxoEntryReference::from) + .collect::>(); + + process_asset_utxos( + *asset_type, + filtered_utxos, + &change_address, + &keys, + &rpc, + abortable, + notifier, + index, + balance, + ) + .await?; } } else { if let Some(notifier) = notifier { @@ -601,6 +641,73 @@ pub fn create_private_keys<'l>( Ok(private_keys) } +/// Processes UTXOs for a specified asset type, consolidating them into the `change_address`. +/// This involves signing transactions with provided keys, and submitting them. +/// +/// # Arguments +/// * `asset_type`: Type of the asset (KSH, KUSD, KRV). +/// * `utxos`: Pre-filtered UTXOs to be consolidated for the given asset type. +/// * `change_address`: Destination address for the consolidated UTXOs. +/// * `keys`: Keys for transaction signing. +/// * `rpc`: Interface for blockchain interactions. +/// * `abortable`: Control for aborting the process. +/// * `notifier`: Optional notifier for progress updates. +/// * `index`: Current index in the scanning process. +/// * `balance`: Aggregate balance of processed UTXOs. +/// +/// # Returns +/// Result indicating success or failure of the consolidation process. +async fn process_asset_utxos( + asset_type: AssetType, + utxos: Vec, + change_address: &Address, + keys: &[PrivateKeyBytes], + rpc: &Arc, + abortable: &Abortable, + notifier: Option<&ScanNotifier>, + index: usize, + balance: u64, +) -> Result<()> { + // Verify that all UTXOs are of the correct asset type + if let Some(mismatched_utxo_ref) = utxos.iter().find(|utxo_ref| utxo_ref.utxo.entry.asset_type != asset_type) { + return Err(Error::MismatchedAssetType { expected: asset_type, found: mismatched_utxo_ref.utxo.entry.asset_type }); + } + + let tx_type = match asset_type { + AssetType::KSH => TransactionKind::TransferKSH, + AssetType::KUSD => TransactionKind::TransferKUSD, + AssetType::KRV => TransactionKind::TransferKRV, + }; + + // Check if there are any UTXOs to process + if !utxos.is_empty() { + let settings = GeneratorSettings::try_new_with_iterator( + Box::new(utxos.into_iter()), + change_address.clone(), + 1, + 1, + tx_type, + PaymentDestination::Change, + Fees::None, + None, + None, + )?; + + let generator = Generator::try_new(settings, None, Some(abortable))?; + + let mut stream = generator.stream(); + while let Some(transaction) = stream.try_next().await? { + transaction.try_sign_with_keys(keys.to_vec())?; + let id = transaction.try_submit(rpc).await?; + if let Some(notifier) = notifier { + notifier(index, balance, Some(id)); + } + yield_executor().await; + } + } + Ok(()) +} + #[cfg(not(target_arch = "wasm32"))] #[cfg(test)] mod tests { @@ -616,26 +723,26 @@ mod tests { fn gen0_receive_addresses() -> Vec<&'static str> { vec![ - "kashtest:qqnapngv3zxp305qf06w6hpzmyxtx2r99jjhs04lu980xdyd2ulwwmx9evrfz", - "kashtest:qqfwmv2jm7dsuju9wz27ptdm4e28qh6evfsm66uf2vf4fxmpxfqgym4m2fcyp", - "kashtest:qpcerqk4ltxtyprv9096wrlzjx5mnrlw4fqce6hnl3axy7tkvyjxypjc5dyqs", - "kashtest:qr9m4h44ghmyz4wagktx8kgmh9zj8h8q0f6tc87wuad5xvzkdlwd6uu9plg2c", - "kashtest:qrkxylqkyjtkjr5zs4z5wjmhmj756e84pa05amcw3zn8wdqjvn4tcc2gcqhrw", - "kashtest:qp3w5h9hp9ude4vjpllsm4qpe8rcc5dmeealkl0cnxlgtj4ly7rczqxcdamvr", - "kashtest:qpqen78dezzj4w7rae4n6kvahlr6wft7jy3lcul78709asxksgxc2kr9fgv6j", - "kashtest:qq7upgj3g8klaylc4etwhlmr70t24wu4n4qrlayuw44yd8wx40seje27ah2x7", - "kashtest:qqt2jzgzwy04j8np6ne4g0akmq4gj3fha0gqupr2mjj95u5utzxqvv33mzpcu", - "kashtest:qpcnt3vscphae5q8h576xkufhtuqvntg0ves8jnthgfaxy8ajek8zz3jcg4de", - "kashtest:qz7wzgzvnadgp6v4u6ua9f3hltaa3cv8635mvzlepa63ttt72c6m208g48q0p", - "kashtest:qpqtsd4flc0n4g720mjwk67tnc46xv9ns5xs2khyvlvszy584ej4xq9adw9h9", - "kashtest:qq4uy92hzh9eauypps060g2k7zv2xv9fsgc5gxkwgsvlhc7tw4a3gk5rnpc0k", - "kashtest:qqgfhd3ur2v2xcf35jggre97ar3awl0h62qlmmaaq28dfrhwzgjnxntdugycr", - "kashtest:qzuflj6tgzwjujsym9ap6dvqz9zfwnmkta68fjulax09clh8l4rfslj9j9nnt", - "kashtest:qz6645a8rrf0hmrdvyr9uj673lrr9zwhjvvrytqpjsjdet23czvc784e84lfe", - "kashtest:qz2fvhmk996rmmg44ht0s79gnw647ehu8ncmpf3sf6txhkfmuzuxssceg9sw0", - "kashtest:qr9aflwylzdu99z2z25lzljyeszhs7j02zhfdazydgahq2vg6x8w7nfp3juqq", - "kashtest:qzen7nh0lmzvujlye5sv3nwgwdyew2zp9nz5we7pay65wrt6kfxd6khwja56q", - "kashtest:qq74jrja2mh3wn6853g8ywpfy9nlg0uuzchvpa0cmnvds4tfnpjj5tqgnqm4f", + "kashtest:qqnapngv3zxp305qf06w6hpzmyxtx2r99jjhs04lu980xdyd2ulwwgzjc0rzh", + "kashtest:qqfwmv2jm7dsuju9wz27ptdm4e28qh6evfsm66uf2vf4fxmpxfqgyg3vt2c05", + "kashtest:qpcerqk4ltxtyprv9096wrlzjx5mnrlw4fqce6hnl3axy7tkvyjxyjk04wyt9", + "kashtest:qr9m4h44ghmyz4wagktx8kgmh9zj8h8q0f6tc87wuad5xvzkdlwd60cjqugpd", + "kashtest:qrkxylqkyjtkjr5zs4z5wjmhmj756e84pa05amcw3zn8wdqjvn4tctwlerhgm", + "kashtest:qp3w5h9hp9ude4vjpllsm4qpe8rcc5dmeealkl0cnxlgtj4ly7rcznz0v7m8k", + "kashtest:qpqen78dezzj4w7rae4n6kvahlr6wft7jy3lcul78709asxksgxc298jgtv38", + "kashtest:qq7upgj3g8klaylc4etwhlmr70t24wu4n4qrlayuw44yd8wx40sej2wfu52dt", + "kashtest:qqt2jzgzwy04j8np6ne4g0akmq4gj3fha0gqupr2mjj95u5utzxqvl4x6ppnf", + "kashtest:qpcnt3vscphae5q8h576xkufhtuqvntg0ves8jnthgfaxy8ajek8z349et4xv", + "kashtest:qz7wzgzvnadgp6v4u6ua9f3hltaa3cv8635mvzlepa63ttt72c6m2url5yqy5", + "kashtest:qpqtsd4flc0n4g720mjwk67tnc46xv9ns5xs2khyvlvszy584ej4xnp2vd9us", + "kashtest:qq4uy92hzh9eauypps060g2k7zv2xv9fsgc5gxkwgsvlhc7tw4a3g9s5jzcyr", + "kashtest:qqgfhd3ur2v2xcf35jggre97ar3awl0h62qlmmaaq28dfrhwzgjnxq06atynk", + "kashtest:qzuflj6tgzwjujsym9ap6dvqz9zfwnmkta68fjulax09clh8l4rfsvkjnxnc7", + "kashtest:qz6645a8rrf0hmrdvyr9uj673lrr9zwhjvvrytqpjsjdet23czvc753wxklzv", + "kashtest:qz2fvhmk996rmmg44ht0s79gnw647ehu8ncmpf3sf6txhkfmuzuxsruwfxs96", + "kashtest:qr9aflwylzdu99z2z25lzljyeszhs7j02zhfdazydgahq2vg6x8w7qdks3ut4", + "kashtest:qzen7nh0lmzvujlye5sv3nwgwdyew2zp9nz5we7pay65wrt6kfxd69nen7534", + "kashtest:qq74jrja2mh3wn6853g8ywpfy9nlg0uuzchvpa0cmnvds4tfnpjj5cyljrm7u", ] } @@ -666,26 +773,26 @@ mod tests { fn gen0_change_addresses() -> Vec<&'static str> { vec![ - "kashtest:qrc0xjaq00fq8qzvrudfuk9msag7whnd72nefwq5d07ks4j4d97kzm0x3ertv", - "kashtest:qpf00utzmaa2u8w9353ssuazsv7fzs605eg00l9luyvcwzwj9cx0z4m8n9p5j", - "kashtest:qrkxek2q6eze7lhg8tq0qw9h890lujvjhtnn5vllrkgj2rgudl6xv3ut9j5mu", - "kashtest:qrn0ga4lddypp9w8eygt9vwk92lagr55e2eqjgkfr09az90632jc6namw09ll", - "kashtest:qzga696vavxtrg0heunvlta5ghjucptll9cfs5x0m2j05s55vtl36uhpauwuk", - "kashtest:qq8ernhu26fgt3ap73jalhzl5u5zuergm9f0dcsa8uy7lmcx875hwl3r894fp", - "kashtest:qrauma73jdn0yfwspr7yf39recvjkk3uy5e4309vjc82qq7sxtskjphgwu0sx", - "kashtest:qzk7yd3ep4def7sv7yhl8m0mr7p75zclycrv0x0jfm0gmwte23k0u5f9dclzy", - "kashtest:qzvm7mnhpkrw52c4p85xd5scrpddxnagzmhmz4v8yt6nawwzgjtavu84ft88x", - "kashtest:qq4feppacdug6p6zk2xf4rw400ps92c9h78gctfcdlucvzzjwzyz7j650nw52", - "kashtest:qryepg9agerq4wdzpv39xxjdytktga53dphvs6r4fdjc0gfyndhk7ytpnl5tv", - "kashtest:qpywh5galz3dd3ndkx96ckpvvf5g8t4adaf0k58y4kgf8w06jt5myjrpluvk6", - "kashtest:qq32grys34737mfe5ud5j2v03cjefynuym27q7jsdt28qy72ucv3sv0teqwvm", - "kashtest:qper47ahktzf9lv67a5e9rmfk35pq4xneufhu97px6tlzd0d4qkaklx7m3f7w", - "kashtest:qqal0t8w2y65a4lm5j5y4maxyy4nuwxj6u364eppj5qpxz9s4l7tknfw0u6r3", - "kashtest:qr7p66q7lmdqcf2vnyus38efx3l4apvqvv5sff66n808mtclef2w7vxh3afnn", - "kashtest:qqx4xydd58qe5csedz3l3q7v02e49rwqnydc425d6jchv02el2gdv4055vh0y", - "kashtest:qzyc9l5azcae7y3yltgnl5k2dzzvngp90a0glsepq0dnz8dvp4jyveezpqse8", - "kashtest:qq705x6hl9qdvr03n0t65esevpvzkkt2xj0faxp6luvd2hk2gr76chxw8xhy5", - "kashtest:qzufchm3cy2ej6f4cjpxpnt3g7c2gn77c320qhrnrjqqskpn7vnzsaxg6z0kd", + "kashtest:qrc0xjaq00fq8qzvrudfuk9msag7whnd72nefwq5d07ks4j4d97kzgt3s6rqe", + "kashtest:qpf00utzmaa2u8w9353ssuazsv7fzs605eg00l9luyvcwzwj9cx0zxlsjxpl8", + "kashtest:qrkxek2q6eze7lhg8tq0qw9h890lujvjhtnn5vllrkgj2rgudl6xvzcuy35sf", + "kashtest:qrn0ga4lddypp9w8eygt9vwk92lagr55e2eqjgkfr09az90632jc6qev0v952", + "kashtest:qzga696vavxtrg0heunvlta5ghjucptll9cfs5x0m2j05s55vtl360nkulwhr", + "kashtest:qq8ernhu26fgt3ap73jalhzl5u5zuergm9f0dcsa8uy7lmcx875hwv45xx4z5", + "kashtest:qrauma73jdn0yfwspr7yf39recvjkk3uy5e4309vjc82qq7sxtskjjnl0l0mn", + "kashtest:qzk7yd3ep4def7sv7yhl8m0mr7p75zclycrv0x0jfm0gmwte23k0u8djvmlf3", + "kashtest:qzvm7mnhpkrw52c4p85xd5scrpddxnagzmhmz4v8yt6nawwzgjtav0rzgg8vn", + "kashtest:qq4feppacdug6p6zk2xf4rw400ps92c9h78gctfcdlucvzzjwzyz7p7rwswll", + "kashtest:qryepg9agerq4wdzpv39xxjdytktga53dphvs6r4fdjc0gfyndhk7h0kju5qe", + "kashtest:qpywh5galz3dd3ndkx96ckpvvf5g8t4adaf0k58y4kgf8w06jt5myp8k7lva0", + "kashtest:qq32grys34737mfe5ud5j2v03cjefynuym27q7jsdt28qy72ucv3sltucrw8w", + "kashtest:qper47ahktzf9lv67a5e9rmfk35pq4xneufhu97px6tlzd0d4qkakvzf6jf4m", + "kashtest:qqal0t8w2y65a4lm5j5y4maxyy4nuwxj6u364eppj5qpxz9s4l7tkqdewl6gy", + "kashtest:qr7p66q7lmdqcf2vnyus38efx3l4apvqvv5sff66n808mtclef2w7lzqs7fcx", + "kashtest:qqx4xydd58qe5csedz3l3q7v02e49rwqnydc425d6jchv02el2gdvxtr40hy3", + "kashtest:qzyc9l5azcae7y3yltgnl5k2dzzvngp90a0glsepq0dnz8dvp4jyv2a4qrsjj", + "kashtest:qq705x6hl9qdvr03n0t65esevpvzkkt2xj0faxp6luvd2hk2gr76cyzex9h0p", + "kashtest:qzufchm3cy2ej6f4cjpxpnt3g7c2gn77c320qhrnrjqqskpn7vnzswzlmp0ac", ] } diff --git a/wallet/core/src/runtime/wallet.rs b/wallet/core/src/runtime/wallet.rs index 4c91de0b8d..40b9e0c130 100644 --- a/wallet/core/src/runtime/wallet.rs +++ b/wallet/core/src/runtime/wallet.rs @@ -1024,7 +1024,9 @@ mod test { use crate::utxo::{UtxoContext, UtxoContextBinding, UtxoIterator}; use kash_addresses::{Address, Prefix, Version}; use kash_bip32::{ChildNumber, ExtendedPrivateKey, SecretKey}; + use kash_consensus_core::asset_type::AssetType; use kash_consensus_core::subnets::SUBNETWORK_ID_NATIVE; + use kash_consensus_core::tx::TransactionKind; use kash_consensus_wasm::{sign_transaction, SignableTransaction, Transaction, TransactionInput, TransactionOutput}; use kash_txscript::pay_to_address_script; use workflow_rpc::client::ConnectOptions; @@ -1093,7 +1095,8 @@ mod test { let tx = Transaction::new( 0, inputs, - vec![TransactionOutput::new(1000, &pay_to_address_script(&to_address))], + vec![TransactionOutput::new(1000, &pay_to_address_script(&to_address), AssetType::KSH)], + TransactionKind::TransferKSH, 0, SUBNETWORK_ID_NATIVE, 0, diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index 5aca75a267..ef3c078fbe 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -60,10 +60,12 @@ use crate::tx::{ PendingTransactionStream, }; use crate::utxo::{UtxoContext, UtxoEntryReference}; +use kash_consensus_core::asset_type::AssetType; +use kash_consensus_core::asset_type::AssetType::KSH; use kash_consensus_core::constants::UNACCEPTED_DAA_SCORE; use kash_consensus_core::subnets::SUBNETWORK_ID_NATIVE; use kash_consensus_core::tx as cctx; -use kash_consensus_core::tx::{Transaction, TransactionInput, TransactionOutpoint, TransactionOutput}; +use kash_consensus_core::tx::{Transaction, TransactionInput, TransactionKind, TransactionOutpoint, TransactionOutput}; use kash_consensus_wasm::UtxoEntry; use kash_txscript::pay_to_address_script; use std::collections::VecDeque; @@ -222,6 +224,8 @@ struct Inner { standard_change_output_mass: u64, // signature mass per input signature_mass_per_input: u64, + // final transaction kind + final_transaction_kind: TransactionKind, // transaction amount (`None` results in consumption of all available UTXOs) // `None` is used for sweep transactions final_transaction_amount: Option, @@ -257,6 +261,7 @@ impl Generator { sig_op_count, minimum_signatures, change_address, + final_transaction_kind, final_transaction_priority_fee, final_transaction_destination, final_transaction_payload, @@ -283,7 +288,7 @@ impl Generator { ( outputs .iter() - .map(|output| TransactionOutput::new(output.amount, pay_to_address_script(&output.address))) + .map(|output| TransactionOutput::new(output.amount, pay_to_address_script(&output.address), output.asset_type)) .collect(), Some(outputs.iter().map(|output| output.amount).sum()), ) @@ -313,8 +318,8 @@ impl Generator { is_done: false, }); - let standard_change_output_mass = - mass_calculator.calc_mass_for_output(&TransactionOutput::new(0, pay_to_address_script(&change_address))); + let standard_change_output_mass = // TODO: This TransactionOutput is only used for mass calculation + mass_calculator.calc_mass_for_output(&TransactionOutput::new(0, pay_to_address_script(&change_address), KSH)); let signature_mass_per_input = mass_calculator.calc_signature_mass(minimum_signatures); let final_transaction_outputs_mass = mass_calculator.calc_mass_for_outputs(&final_transaction_outputs); let final_transaction_payload = final_transaction_payload.unwrap_or_default(); @@ -339,6 +344,7 @@ impl Generator { change_address, standard_change_output_mass, signature_mass_per_input, + final_transaction_kind, final_transaction_amount, final_transaction_priority_fee, final_transaction_outputs, @@ -624,6 +630,9 @@ impl Generator { let (kind, data) = self.generate_transaction_data(&mut context, &mut stage)?; context.stage.replace(stage); + let transaction_kind = self.inner.final_transaction_kind; + let output_asset_type = transaction_kind.asset_transfer_types().1; + match (kind, data) { (DataKind::NoOp, _) => { context.is_done = true; @@ -655,7 +664,11 @@ impl Generator { } if change_output_value > 0 { - let output = TransactionOutput::new(change_output_value, pay_to_address_script(&self.inner.change_address)); + let output = TransactionOutput::new( + change_output_value, + pay_to_address_script(&self.inner.change_address), + output_asset_type, + ); final_outputs.push(output); } @@ -669,6 +682,7 @@ impl Generator { 0, inputs, final_outputs, + transaction_kind, 0, SUBNETWORK_ID_NATIVE, 0, @@ -708,12 +722,17 @@ impl Generator { let output_value = aggregate_input_value - transaction_fees; let script_public_key = pay_to_address_script(&self.inner.change_address); - let output = TransactionOutput::new(output_value, script_public_key.clone()); - let tx = Transaction::new(0, inputs, vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![]); + let output = TransactionOutput::new(output_value, script_public_key.clone(), output_asset_type); + let tx = Transaction::new(0, inputs, vec![output], transaction_kind, 0, SUBNETWORK_ID_NATIVE, 0, vec![]); context.number_of_transactions += 1; - let utxo_entry_reference = - Self::create_batch_utxo_entry_reference(tx.id(), output_value, script_public_key, &self.inner.change_address); + let utxo_entry_reference = Self::create_batch_utxo_entry_reference( + tx.id(), + output_value, + script_public_key, + &self.inner.change_address, + output_asset_type, + ); match kind { DataKind::Node => { @@ -754,8 +773,10 @@ impl Generator { amount: u64, script_public_key: ScriptPublicKey, address: &Address, + asset_type: AssetType, ) -> UtxoEntryReference { - let entry = cctx::UtxoEntry { amount, script_public_key, block_daa_score: UNACCEPTED_DAA_SCORE, is_coinbase: false }; + let entry = + cctx::UtxoEntry { amount, script_public_key, block_daa_score: UNACCEPTED_DAA_SCORE, is_coinbase: false, asset_type }; let outpoint = TransactionOutpoint::new(txid, 0); let utxo = UtxoEntry { address: Some(address.clone()), outpoint: outpoint.into(), entry }; UtxoEntryReference { utxo: Arc::new(utxo) } diff --git a/wallet/core/src/tx/generator/settings.rs b/wallet/core/src/tx/generator/settings.rs index 3d3fc3cae5..919f32e4cf 100644 --- a/wallet/core/src/tx/generator/settings.rs +++ b/wallet/core/src/tx/generator/settings.rs @@ -5,6 +5,7 @@ use crate::utxo::{UtxoContext, UtxoEntryReference, UtxoIterator}; use crate::Events; use kash_addresses::Address; use kash_consensus_core::network::NetworkType; +use kash_consensus_core::tx::TransactionKind; use std::sync::Arc; use workflow_core::channel::Multiplexer; @@ -23,6 +24,8 @@ pub struct GeneratorSettings { pub minimum_signatures: u16, // change address pub change_address: Address, + // kind of the final transaction + pub final_transaction_kind: TransactionKind, // applies only to the final transaction pub final_transaction_priority_fee: Fees, // final transaction outputs @@ -34,6 +37,7 @@ pub struct GeneratorSettings { impl GeneratorSettings { pub fn try_new_with_account( account: Arc, + final_transaction_kind: TransactionKind, final_transaction_destination: PaymentDestination, final_priority_fee: Fees, final_transaction_payload: Option>, @@ -54,7 +58,7 @@ impl GeneratorSettings { change_address, utxo_iterator: Box::new(utxo_iterator), utxo_context: Some(account.utxo_context().clone()), - + final_transaction_kind, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, @@ -68,6 +72,7 @@ impl GeneratorSettings { change_address: Address, sig_op_count: u8, minimum_signatures: u16, + final_transaction_kind: TransactionKind, final_transaction_destination: PaymentDestination, final_priority_fee: Fees, final_transaction_payload: Option>, @@ -84,7 +89,7 @@ impl GeneratorSettings { change_address, utxo_iterator: Box::new(utxo_iterator), utxo_context: Some(utxo_context), - + final_transaction_kind, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, @@ -98,6 +103,7 @@ impl GeneratorSettings { change_address: Address, sig_op_count: u8, minimum_signatures: u16, + final_transaction_kind: TransactionKind, final_transaction_destination: PaymentDestination, final_priority_fee: Fees, final_transaction_payload: Option>, @@ -113,7 +119,7 @@ impl GeneratorSettings { change_address, utxo_iterator: Box::new(utxo_iterator), utxo_context: None, - + final_transaction_kind, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index de2f9ddefc..c9f7e8bd88 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -6,8 +6,9 @@ use crate::tx::{is_standard_output_amount_dust, Fees, MassCalculator, PaymentDes use crate::utxo::UtxoEntryReference; use crate::{tx::PaymentOutputs, utils::kash_to_sompi}; use kash_addresses::Address; +use kash_consensus_core::asset_type::AssetType::KSH; use kash_consensus_core::network::NetworkType; -use kash_consensus_core::tx::Transaction; +use kash_consensus_core::tx::{Transaction, TransactionKind}; use std::cell::RefCell; use std::rc::Rc; use workflow_log::style; @@ -260,10 +261,18 @@ where .iter() .map(|(address, amount)| { let sompi: Sompi = (*amount).clone().into(); - (address.clone()(network_type), sompi.0) + (address.clone()(network_type), sompi.0, KSH) }) .collect::>(); - make_generator(network_type, head, tail, fees, change_address, PaymentOutputs::from(outputs.as_slice()).into()) + make_generator( + network_type, + head, + tail, + fees, + change_address, + TransactionKind::TransferKSH, + PaymentOutputs::from(outputs.as_slice()).into(), + ) } fn make_generator( @@ -272,6 +281,7 @@ fn make_generator( tail: &[f64], fees: Fees, change_address: F, + final_transaction_kind: TransactionKind, final_transaction_destination: PaymentDestination, ) -> Result where @@ -296,6 +306,7 @@ where sig_op_count, minimum_signatures, change_address, + final_transaction_kind, utxo_iterator, utxo_context, final_transaction_priority_fee: final_priority_fee, @@ -325,7 +336,9 @@ fn output_address(network_type: NetworkType) -> Address { #[test] fn test_generator_empty_utxo_noop() -> Result<()> { let network_type = NetworkType::Testnet; - let generator = make_generator(network_type, &[], &[], Fees::None, change_address, PaymentDestination::Change).unwrap(); + let generator = + make_generator(network_type, &[], &[], Fees::None, change_address, TransactionKind::TransferKSH, PaymentDestination::Change) + .unwrap(); let tx = generator.generate_transaction().unwrap(); assert!(tx.is_none()); Ok(()) @@ -334,8 +347,16 @@ fn test_generator_empty_utxo_noop() -> Result<()> { #[test] fn test_generator_sweep_single_utxo_noop() -> Result<()> { let network_type = NetworkType::Testnet; - let generator = make_generator(network_type, &[10.0], &[], Fees::None, change_address, PaymentDestination::Change) - .expect("single UTXO input: generator"); + let generator = make_generator( + network_type, + &[10.0], + &[], + Fees::None, + change_address, + TransactionKind::TransferKSH, + PaymentDestination::Change, + ) + .expect("single UTXO input: generator"); let tx = generator.generate_transaction().unwrap(); assert!(tx.is_none()); Ok(()) @@ -344,17 +365,25 @@ fn test_generator_sweep_single_utxo_noop() -> Result<()> { #[test] fn test_generator_sweep_two_utxos() -> Result<()> { let network_type = NetworkType::Testnet; - make_generator(network_type, &[10.0, 10.0], &[], Fees::None, change_address, PaymentDestination::Change) - .expect("merge 2 UTXOs without fees: generator") - .harness() - .fetch(&Expected { - is_final: true, - input_count: 2, - aggregate_input_value: Kash(20.0).into(), - output_count: 1, - priority_fees: FeesExpected::None, - }) - .finalize(); + make_generator( + network_type, + &[10.0, 10.0], + &[], + Fees::None, + change_address, + TransactionKind::TransferKSH, + PaymentDestination::Change, + ) + .expect("merge 2 UTXOs without fees: generator") + .harness() + .fetch(&Expected { + is_final: true, + input_count: 2, + aggregate_input_value: Kash(20.0).into(), + output_count: 1, + priority_fees: FeesExpected::None, + }) + .finalize(); Ok(()) } @@ -367,6 +396,7 @@ fn test_generator_sweep_two_utxos_with_priority_fees_rejection() -> Result<()> { &[], Fees::sender_pays_all(Kash(5.0)), change_address, + TransactionKind::TransferKSH, PaymentDestination::Change, ); match generator { @@ -470,7 +500,7 @@ fn test_generator_inputs_903_outputs_2_fees_exclude() -> Result<()> { .fetch(&Expected { is_final: true, input_count: 2, - aggregate_input_value: Sompi(9_009_99892258), + aggregate_input_value: Sompi(9_009_99892246), output_count: 2, priority_fees: FeesExpected::sender_pays(Kash(5.0)), }) @@ -514,21 +544,21 @@ fn test_generator_1m_utxos_w_1kas_to_990k_sender_pays_fees() -> Result<()> { .fetch(&Expected { is_final: false, input_count: 843, - aggregate_input_value: Sompi(710_648_15369544), + aggregate_input_value: Sompi(710_648_15362800), output_count: 1, priority_fees: FeesExpected::None, }) .fetch(&Expected { is_final: false, input_count: 332, - aggregate_input_value: Sompi(279_357_66731392), + aggregate_input_value: Sompi(279_357_66728740), output_count: 1, priority_fees: FeesExpected::None, }) .fetch(&Expected { is_final: true, input_count: 2, - aggregate_input_value: Sompi(990_005_81960862), + aggregate_input_value: Sompi(990_005_81951454), output_count: 2, priority_fees: FeesExpected::sender_pays(Kash(5.0)), }) diff --git a/wallet/core/src/tx/mass.rs b/wallet/core/src/tx/mass.rs index c54a968ef4..57b8849cea 100644 --- a/wallet/core/src/tx/mass.rs +++ b/wallet/core/src/tx/mass.rs @@ -175,6 +175,7 @@ const fn outpoint_estimated_serialized_size() -> u64 { pub fn transaction_output_serialized_byte_size(output_inner: &TransactionOutput) -> u64 { let mut size: u64 = 0; + size += 4; // value (u32) size += 8; // value (u64) size += 2; // output.ScriptPublicKey.Version (u16) size += 8; // length of script public key (u64) diff --git a/wallet/core/src/tx/payment.rs b/wallet/core/src/tx/payment.rs index 6851664aa2..6517ea4235 100644 --- a/wallet/core/src/tx/payment.rs +++ b/wallet/core/src/tx/payment.rs @@ -1,4 +1,5 @@ use crate::imports::*; +use kash_consensus_core::asset_type::AssetType; use kash_consensus_wasm::{TransactionOutput, TransactionOutputInner}; use kash_txscript::pay_to_address_script; @@ -22,6 +23,7 @@ pub struct PaymentOutput { #[wasm_bindgen(getter_with_clone)] pub address: Address, pub amount: u64, + pub asset_type: AssetType, } impl TryFrom for PaymentOutput { @@ -34,12 +36,14 @@ impl TryFrom for PaymentOutput { } else { let address = Address::try_from(array.get(0))?; let amount = array.get(1).try_as_u64()?; - Ok(Self { address, amount }) + let asset_type = AssetType::try_from(js_value)?; + Ok(Self { address, amount, asset_type }) } } else if let Some(object) = Object::try_from(&js_value) { let address = object.get::
("address")?; let amount = object.get_u64("amount")?; - Ok(Self { address, amount }) + let asset_type = AssetType::try_from(object.get_value("assetType")?)?; + Ok(Self { address, amount, asset_type }) } else { Err(Error::Custom("Invalid payment output".to_string())) } @@ -49,14 +53,18 @@ impl TryFrom for PaymentOutput { #[wasm_bindgen] impl PaymentOutput { #[wasm_bindgen(constructor)] - pub fn new(address: Address, amount: u64) -> Self { - Self { address, amount } + pub fn new(address: Address, amount: u64, asset_type: AssetType) -> Self { + Self { address, amount, asset_type } } } impl From for TransactionOutput { fn from(value: PaymentOutput) -> Self { - Self::new_with_inner(TransactionOutputInner { script_public_key: pay_to_address_script(&value.address), value: value.amount }) + Self::new_with_inner(TransactionOutputInner { + script_public_key: pay_to_address_script(&value.address), + value: value.amount, + asset_type: value.asset_type, + }) } } @@ -89,6 +97,12 @@ impl From for PaymentDestination { } } +impl From for Vec { + fn from(payment_outputs: PaymentOutputs) -> Self { + payment_outputs.outputs.into_iter().map(Into::into).collect() + } +} + #[wasm_bindgen] impl PaymentOutputs { #[wasm_bindgen(constructor)] @@ -121,34 +135,30 @@ impl TryFrom for PaymentOutputs { } } -impl From for Vec { - fn from(value: PaymentOutputs) -> Self { - value.outputs.into_iter().map(TransactionOutput::from).collect() - } -} - -impl From<(Address, u64)> for PaymentOutputs { - fn from((address, amount): (Address, u64)) -> Self { - PaymentOutputs { outputs: vec![PaymentOutput::new(address, amount)] } +impl From<(Address, u64, AssetType)> for PaymentOutputs { + fn from((address, amount, asset_type): (Address, u64, AssetType)) -> Self { + PaymentOutputs { outputs: vec![PaymentOutput::new(address, amount, asset_type)] } } } -impl From<(&Address, u64)> for PaymentOutputs { - fn from((address, amount): (&Address, u64)) -> Self { - PaymentOutputs { outputs: vec![PaymentOutput::new(address.clone(), amount)] } +impl From<(&Address, u64, AssetType)> for PaymentOutputs { + fn from((address, amount, asset_type): (&Address, u64, AssetType)) -> Self { + PaymentOutputs { outputs: vec![PaymentOutput::new(address.clone(), amount, asset_type)] } } } -impl From<&[(Address, u64)]> for PaymentOutputs { - fn from(outputs: &[(Address, u64)]) -> Self { - let outputs = outputs.iter().map(|(address, amount)| PaymentOutput::new(address.clone(), *amount)).collect(); +impl From<&[(Address, u64, AssetType)]> for PaymentOutputs { + fn from(outputs: &[(Address, u64, AssetType)]) -> Self { + let outputs = + outputs.iter().map(|(address, amount, asset_type)| PaymentOutput::new(address.clone(), *amount, *asset_type)).collect(); PaymentOutputs { outputs } } } -impl From<&[(&Address, u64)]> for PaymentOutputs { - fn from(outputs: &[(&Address, u64)]) -> Self { - let outputs = outputs.iter().map(|(address, amount)| PaymentOutput::new((*address).clone(), *amount)).collect(); +impl From<&[(&Address, u64, AssetType)]> for PaymentOutputs { + fn from(outputs: &[(&Address, u64, AssetType)]) -> Self { + let outputs = + outputs.iter().map(|(address, amount, asset_type)| PaymentOutput::new((*address).clone(), *amount, *asset_type)).collect(); PaymentOutputs { outputs } } } diff --git a/wallet/core/src/utxo/scan.rs b/wallet/core/src/utxo/scan.rs index 0db88a1555..d816f2abb1 100644 --- a/wallet/core/src/utxo/scan.rs +++ b/wallet/core/src/utxo/scan.rs @@ -3,6 +3,7 @@ use crate::imports::*; use crate::result::Result; use crate::runtime::{AtomicBalance, Balance}; use crate::utxo::{UtxoContext, UtxoEntryReference, UtxoEntryReferenceExtension}; +use kash_consensus_core::asset_type::AssetType; use std::cmp::max; pub const DEFAULT_WINDOW_SIZE: usize = 8; @@ -22,17 +23,20 @@ enum Provider { } pub struct Scan { - provider: Provider, //Arc, + provider: Provider, window_size: Option, extent: Option, - balance: Arc, + ksh_balance: Arc, + kusd_balance: Arc, + krv_balance: Arc, current_daa_score: u64, } - impl Scan { pub fn new_with_address_manager( address_manager: Arc, - balance: &Arc, + ksh_balance: &Arc, + kusd_balance: &Arc, + krv_balance: &Arc, current_daa_score: u64, window_size: Option, extent: Option, @@ -41,16 +45,26 @@ impl Scan { provider: Provider::AddressManager(address_manager), window_size, //: Some(DEFAULT_WINDOW_SIZE), extent, //: Some(ScanExtent::EmptyWindow), - balance: balance.clone(), + ksh_balance: ksh_balance.clone(), + kusd_balance: kusd_balance.clone(), + krv_balance: krv_balance.clone(), current_daa_score, } } - pub fn new_with_address_set(addresses: HashSet
, balance: &Arc, current_daa_score: u64) -> Scan { + pub fn new_with_address_set( + addresses: HashSet
, + ksh_balance: &Arc, + kusd_balance: &Arc, + krv_balance: &Arc, + current_daa_score: u64, + ) -> Scan { Scan { provider: Provider::AddressSet(addresses), window_size: None, extent: None, - balance: balance.clone(), + ksh_balance: ksh_balance.clone(), + kusd_balance: kusd_balance.clone(), + krv_balance: krv_balance.clone(), current_daa_score, } } @@ -70,17 +84,16 @@ impl Scan { let mut last_address_index = address_manager.index(); 'scan: loop { - // scan first up to address index, then in window chunks + // Initialize separate balances for each currency + let mut ksh_balance = Balance::default(); + let mut kusd_balance = Balance::default(); + let mut krv_balance = Balance::default(); + let first = cursor; let last = if cursor == 0 { max(last_address_index + 1, window_size) } else { cursor + window_size }; cursor = last; - // generate address derivations let addresses = address_manager.get_range(first..last)?; - // register address in the utxo context; NOTE: during the scan, - // before `get_utxos_by_addresses()` is complete we may receive - // new transactions as such utxo context should be aware of the - // addresses used before we start interacting with them. utxo_context.register_addresses(&addresses).await?; let ts = Instant::now(); @@ -102,22 +115,33 @@ impl Scan { panic!("Account::scan_address_manager() has received an unknown address: `{address}`"); } } - } - yield_executor().await; - let balance: Balance = refs.iter().fold(Balance::default(), |mut balance, r| { - // let entry_balance = r.as_ref().balance(self.current_daa_score); - let entry_balance = r.balance(self.current_daa_score); - balance.mature += entry_balance.mature; - balance.pending += entry_balance.pending; - balance - }); + // Update balance based on asset type + let entry_balance = utxo_ref.balance(self.current_daa_score); + match utxo_ref.utxo.entry.asset_type { + AssetType::KSH => { + ksh_balance.mature += entry_balance.mature; + ksh_balance.pending += entry_balance.pending; + } + AssetType::KUSD => { + kusd_balance.mature += entry_balance.mature; + kusd_balance.pending += entry_balance.pending; + } + AssetType::KRV => { + krv_balance.mature += entry_balance.mature; + krv_balance.pending += entry_balance.pending; + } + } + } yield_executor().await; utxo_context.extend(refs, self.current_daa_score).await?; - if !balance.is_empty() { - self.balance.add(balance); + // Check if any balance is not empty and update accordingly + if !ksh_balance.is_empty() || !kusd_balance.is_empty() || !krv_balance.is_empty() { + self.ksh_balance.add(ksh_balance); + self.kusd_balance.add(kusd_balance); + self.krv_balance.add(krv_balance); } else { match &extent { ScanExtent::EmptyWindow => { @@ -135,7 +159,6 @@ impl Scan { yield_executor().await; } - // update address manager with the last used index address_manager.set_index(last_address_index)?; Ok(()) @@ -148,18 +171,39 @@ impl Scan { let resp = utxo_context.processor().rpc_api().get_utxos_by_addresses(address_vec).await?; let refs: Vec = resp.into_iter().map(UtxoEntryReference::from).collect(); - let balance: Balance = refs.iter().fold(Balance::default(), |mut balance, r| { + // Initialize separate balances for each currency + let mut ksh_balance = Balance::default(); + let mut kusd_balance = Balance::default(); + let mut krv_balance = Balance::default(); + + for r in refs.iter() { + // Update balance based on asset type let entry_balance = r.balance(self.current_daa_score); - balance.mature += entry_balance.mature; - balance.pending += entry_balance.pending; - balance - }); + match r.utxo.entry.asset_type { + AssetType::KSH => { + ksh_balance.mature += entry_balance.mature; + ksh_balance.pending += entry_balance.pending; + } + AssetType::KUSD => { + kusd_balance.mature += entry_balance.mature; + kusd_balance.pending += entry_balance.pending; + } + AssetType::KRV => { + krv_balance.mature += entry_balance.mature; + krv_balance.pending += entry_balance.pending; + } + } + } yield_executor().await; utxo_context.extend(refs, self.current_daa_score).await?; - if !balance.is_empty() { - self.balance.add(balance); + // Check if any balance is not empty and update accordingly + if !ksh_balance.is_empty() || !kusd_balance.is_empty() || !krv_balance.is_empty() { + // Here you need to update the respective balances for KSH, KUSD, KRV + self.ksh_balance.add(ksh_balance); + self.kusd_balance.add(kusd_balance); + self.krv_balance.add(krv_balance); } Ok(()) diff --git a/wallet/core/src/wasm/tx/generator/generator.rs b/wallet/core/src/wasm/tx/generator/generator.rs index dce86fb74b..f1d278242d 100644 --- a/wallet/core/src/wasm/tx/generator/generator.rs +++ b/wallet/core/src/wasm/tx/generator/generator.rs @@ -6,6 +6,7 @@ use crate::utxo::{TryIntoUtxoEntryReferences, UtxoEntryReference}; use crate::wasm::tx::generator::*; use crate::wasm::wallet::Account; use crate::wasm::UtxoContext; +use kash_consensus_core::tx::TransactionKind; #[wasm_bindgen] extern "C" { @@ -65,6 +66,7 @@ impl Generator { let GeneratorSettings { source, multiplexer, + final_transaction_kind, final_transaction_destination, change_address, final_priority_fee, @@ -83,6 +85,7 @@ impl Generator { change_address, sig_op_count, minimum_signatures, + final_transaction_kind, final_transaction_destination, final_priority_fee, payload, @@ -98,6 +101,7 @@ impl Generator { change_address, sig_op_count, minimum_signatures, + final_transaction_kind, final_transaction_destination, final_priority_fee, payload, @@ -106,7 +110,13 @@ impl Generator { } GeneratorSource::Account(account) => { let account: Arc = account.into(); - native::GeneratorSettings::try_new_with_account(account, final_transaction_destination, final_priority_fee, None)? + native::GeneratorSettings::try_new_with_account( + account, + final_transaction_kind, + final_transaction_destination, + final_priority_fee, + None, + )? } }; @@ -157,6 +167,7 @@ enum GeneratorSource { struct GeneratorSettings { pub source: GeneratorSource, pub multiplexer: Option>>, + pub final_transaction_kind: TransactionKind, pub final_transaction_destination: PaymentDestination, pub change_address: Option
, pub final_priority_fee: Fees, @@ -168,6 +179,13 @@ struct GeneratorSettings { impl TryFrom for GeneratorSettings { type Error = Error; fn try_from(args: GeneratorSettingsObject) -> std::result::Result { + // Parsing the final_transaction_input_asset_type + let final_transaction_kind = match TransactionKind::try_from(args.get_value("transactionKind")?) { + Ok(transaction_kind) => transaction_kind, + Err(_err) => { + return Err(Error::custom("Invalid transactionKind value")); + } + }; // lack of outputs results in a sweep transaction compounding utxos into the change address let outputs = args.get_value("outputs")?; @@ -204,6 +222,7 @@ impl TryFrom for GeneratorSettings { let settings = GeneratorSettings { source: generator_source, multiplexer: None, + final_transaction_kind, final_transaction_destination, change_address, final_priority_fee, diff --git a/wallet/core/src/wasm/tx/utils.rs b/wallet/core/src/wasm/tx/utils.rs index 694893e16f..a750d9d400 100644 --- a/wallet/core/src/wasm/tx/utils.rs +++ b/wallet/core/src/wasm/tx/utils.rs @@ -6,12 +6,14 @@ use crate::wasm::tx::generator::*; use crate::wasm::tx::mass::MassCalculator; use kash_addresses::Address; use kash_consensus_core::subnets::SUBNETWORK_ID_NATIVE; +use kash_consensus_core::tx::TransactionKind; use kash_consensus_wasm::*; use workflow_core::runtime::is_web; /// Create a basic transaction without any mass limit checks. #[wasm_bindgen(js_name=createTransaction)] pub fn create_transaction_js( + transaction_kind: TransactionKind, utxo_entry_source: JsValue, outputs: JsValue, change_address: JsValue, @@ -64,7 +66,7 @@ pub fn create_transaction_js( // TODO - Calculate mass and fees let outputs: Vec = outputs.into(); - let transaction = Transaction::new(0, inputs, outputs, 0, SUBNETWORK_ID_NATIVE, 0, payload)?; + let transaction = Transaction::new(0, inputs, outputs, transaction_kind, 0, SUBNETWORK_ID_NATIVE, 0, payload)?; let _fee = mc.calc_minimum_transaction_relay_fee(&transaction, minimum_signatures); let mtx = SignableTransaction::new(transaction, entries.into()); diff --git a/wasm/nodejs/demo.js b/wasm/nodejs/demo.js index a8fe37af9b..d6f47a466d 100644 --- a/wasm/nodejs/demo.js +++ b/wasm/nodejs/demo.js @@ -66,7 +66,7 @@ const { networkId, encoding } = require("./utils").parseArgs(); const changeAddress = address; console.info(changeAddress); - const tx = createTransaction(utxos, outputs, changeAddress, 0n, 0, 1, 1); + const tx = createTransaction("TransferKSH", utxos, outputs, changeAddress, 0n, 0, 1, 1); console.info(tx); diff --git a/wasm/nodejs/refactoring/tx-script-sign.js b/wasm/nodejs/refactoring/tx-script-sign.js index bb83361c6d..506292983d 100644 --- a/wasm/nodejs/refactoring/tx-script-sign.js +++ b/wasm/nodejs/refactoring/tx-script-sign.js @@ -66,7 +66,7 @@ kash.init_console_panic_hook(); const changeAddress = addr; const priorityFee = 1500; - const tx = createTransaction(utxoSelection, outputs, changeAddress, priorityFee); + const tx = createTransaction("TransferKSH", utxoSelection, outputs, changeAddress, priorityFee); const scriptHashes = tx.getScriptHashes(); console.log("scriptHashes", scriptHashes)