diff --git a/consensus/core/src/header.rs b/consensus/core/src/header.rs index b6c2b9bc7..de5f79c68 100644 --- a/consensus/core/src/header.rs +++ b/consensus/core/src/header.rs @@ -92,6 +92,12 @@ impl Header { } } +impl AsRef
for Header { + fn as_ref(&self) -> &Header { + self + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rothschild/src/main.rs b/rothschild/src/main.rs index bbabca71b..2154cec5c 100644 --- a/rothschild/src/main.rs +++ b/rothschild/src/main.rs @@ -13,7 +13,7 @@ use kaspa_consensus_core::{ use kaspa_core::{info, kaspad_env::version, time::unix_now, warn}; use kaspa_grpc_client::{ClientPool, GrpcClient}; use kaspa_notify::subscription::context::SubscriptionContext; -use kaspa_rpc_core::{api::rpc::RpcApi, notify::mode::NotificationMode}; +use kaspa_rpc_core::{api::rpc::RpcApi, notify::mode::NotificationMode, RpcUtxoEntry}; use kaspa_txscript::pay_to_address_script; use parking_lot::Mutex; use rayon::prelude::*; @@ -323,7 +323,7 @@ async fn populate_pending_outpoints_from_mempool( for entry in entries { for entry in entry.sending { for input in entry.transaction.inputs { - pending_outpoints.insert(input.previous_outpoint, now); + pending_outpoints.insert(input.previous_outpoint.into(), now); } } } @@ -337,20 +337,20 @@ async fn fetch_spendable_utxos( ) -> Vec<(TransactionOutpoint, UtxoEntry)> { let resp = rpc_client.get_utxos_by_addresses(vec![kaspa_addr]).await.unwrap(); let dag_info = rpc_client.get_block_dag_info().await.unwrap(); - let mut utxos = Vec::with_capacity(resp.len()); - for resp_entry in resp - .into_iter() - .filter(|resp_entry| is_utxo_spendable(&resp_entry.utxo_entry, dag_info.virtual_daa_score, coinbase_maturity)) + + let mut utxos = resp.into_iter() + .filter(|entry| { + is_utxo_spendable(&entry.utxo_entry, dag_info.virtual_daa_score, coinbase_maturity) + }) + .map(|entry| (TransactionOutpoint::from(entry.outpoint), UtxoEntry::from(entry.utxo_entry))) // Eliminates UTXOs we already tried to spend so we don't try to spend them again in this period - .filter(|utxo| !pending.contains_key(&utxo.outpoint)) - { - utxos.push((resp_entry.outpoint, resp_entry.utxo_entry)); - } + .filter(|(outpoint,_)| !pending.contains_key(outpoint)) + .collect::>(); utxos.sort_by(|a, b| b.1.amount.cmp(&a.1.amount)); utxos } -fn is_utxo_spendable(entry: &UtxoEntry, virtual_daa_score: u64, coinbase_maturity: u64) -> bool { +fn is_utxo_spendable(entry: &RpcUtxoEntry, virtual_daa_score: u64, coinbase_maturity: u64) -> bool { let needed_confs = if !entry.is_coinbase { 10 } else { diff --git a/rpc/core/Cargo.toml b/rpc/core/Cargo.toml index ef010a020..ac02f7513 100644 --- a/rpc/core/Cargo.toml +++ b/rpc/core/Cargo.toml @@ -42,6 +42,7 @@ hex.workspace = true js-sys.workspace = true log.workspace = true paste.workspace = true +rand.workspace = true serde-wasm-bindgen.workspace = true serde.workspace = true smallvec.workspace = true @@ -51,7 +52,6 @@ wasm-bindgen.workspace = true workflow-core.workspace = true workflow-serializer.workspace = true workflow-wasm.workspace = true -rand.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/rpc/core/src/convert/block.rs b/rpc/core/src/convert/block.rs index 3f66b617f..6d1d165c1 100644 --- a/rpc/core/src/convert/block.rs +++ b/rpc/core/src/convert/block.rs @@ -10,7 +10,7 @@ use kaspa_consensus_core::block::{Block, MutableBlock}; impl From<&Block> for RpcBlock { fn from(item: &Block) -> Self { Self { - header: (*item.header).clone(), + header: item.header.as_ref().into(), transactions: item.transactions.iter().map(RpcTransaction::from).collect(), // TODO: Implement a populating process inspired from kaspad\app\rpc\rpccontext\verbosedata.go verbose_data: None, @@ -21,7 +21,7 @@ impl From<&Block> for RpcBlock { impl From<&MutableBlock> for RpcBlock { fn from(item: &MutableBlock) -> Self { Self { - header: item.header.clone(), + header: item.header.as_ref().into(), transactions: item.transactions.iter().map(RpcTransaction::from).collect(), verbose_data: None, } @@ -36,7 +36,7 @@ impl TryFrom<&RpcBlock> for Block { type Error = RpcError; fn try_from(item: &RpcBlock) -> RpcResult { Ok(Self { - header: Arc::new(item.header.clone()), + header: Arc::new(item.header.as_ref().into()), transactions: Arc::new( item.transactions .iter() diff --git a/rpc/core/src/convert/tx.rs b/rpc/core/src/convert/tx.rs index 44a9389a4..78fe6c77c 100644 --- a/rpc/core/src/convert/tx.rs +++ b/rpc/core/src/convert/tx.rs @@ -36,7 +36,7 @@ impl From<&TransactionOutput> for RpcTransactionOutput { impl From<&TransactionInput> for RpcTransactionInput { fn from(item: &TransactionInput) -> Self { Self { - previous_outpoint: item.previous_outpoint, + previous_outpoint: item.previous_outpoint.into(), signature_script: item.signature_script.clone(), sequence: item.sequence, sig_op_count: item.sig_op_count, @@ -83,6 +83,6 @@ impl TryFrom<&RpcTransactionOutput> for TransactionOutput { impl TryFrom<&RpcTransactionInput> for TransactionInput { type Error = RpcError; fn try_from(item: &RpcTransactionInput) -> RpcResult { - Ok(Self::new(item.previous_outpoint, item.signature_script.clone(), item.sequence, item.sig_op_count)) + Ok(Self::new(item.previous_outpoint.into(), item.signature_script.clone(), item.sequence, item.sig_op_count)) } } diff --git a/rpc/core/src/convert/utxo.rs b/rpc/core/src/convert/utxo.rs index 305fb0931..a0376580d 100644 --- a/rpc/core/src/convert/utxo.rs +++ b/rpc/core/src/convert/utxo.rs @@ -1,6 +1,6 @@ +use crate::RpcUtxoEntry; use crate::RpcUtxosByAddressesEntry; use kaspa_addresses::Prefix; -use kaspa_consensus_core::tx::UtxoEntry; use kaspa_index_core::indexed_utxos::UtxoSetByScriptPublicKey; use kaspa_txscript::extract_script_pub_key_address; @@ -16,8 +16,8 @@ pub fn utxo_set_into_rpc(item: &UtxoSetByScriptPublicKey, prefix: Option .iter() .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), + outpoint: (*outpoint).into(), + utxo_entry: RpcUtxoEntry::new(entry.amount, script_public_key.clone(), entry.block_daa_score, entry.is_coinbase), }) .collect::>() }) diff --git a/rpc/core/src/model/address.rs b/rpc/core/src/model/address.rs index 720cb4f86..69bece8ff 100644 --- a/rpc/core/src/model/address.rs +++ b/rpc/core/src/model/address.rs @@ -1,11 +1,11 @@ use crate::{RpcTransactionOutpoint, RpcUtxoEntry}; -use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use workflow_serializer::prelude::*; pub type RpcAddress = kaspa_addresses::Address; /// Represents a UTXO entry of an address returned by the `GetUtxosByAddresses` RPC. -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcUtxosByAddressesEntry { pub address: Option, @@ -13,8 +13,25 @@ pub struct RpcUtxosByAddressesEntry { pub utxo_entry: RpcUtxoEntry, } +impl Serializer for RpcUtxosByAddressesEntry { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; // version + store!(Option, &self.address, writer)?; + serialize!(RpcTransactionOutpoint, &self.outpoint, writer)?; + serialize!(RpcUtxoEntry, &self.utxo_entry, writer) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version: u8 = load!(u8, reader)?; + let address = load!(Option, reader)?; + let outpoint = deserialize!(RpcTransactionOutpoint, reader)?; + let utxo_entry = deserialize!(RpcUtxoEntry, reader)?; + Ok(Self { address, outpoint, utxo_entry }) + } +} + /// Represents a balance of an address returned by the `GetBalancesByAddresses` RPC. -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcBalancesByAddressesEntry { pub address: RpcAddress, @@ -22,3 +39,18 @@ pub struct RpcBalancesByAddressesEntry { /// Balance of `address` if available pub balance: Option, } + +impl Serializer for RpcBalancesByAddressesEntry { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; // version + store!(RpcAddress, &self.address, writer)?; + store!(Option, &self.balance, writer) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version: u8 = load!(u8, reader)?; + let address = load!(RpcAddress, reader)?; + let balance = load!(Option, reader)?; + Ok(Self { address, balance }) + } +} diff --git a/rpc/core/src/model/block.rs b/rpc/core/src/model/block.rs index c4c501afb..9efe6a8e8 100644 --- a/rpc/core/src/model/block.rs +++ b/rpc/core/src/model/block.rs @@ -1,8 +1,8 @@ use crate::prelude::{RpcHash, RpcHeader, RpcTransaction}; -use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use workflow_serializer::prelude::*; -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcBlock { pub header: RpcHeader, @@ -10,7 +10,27 @@ pub struct RpcBlock { pub verbose_data: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +impl Serializer for RpcBlock { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u32, &1, writer)?; + serialize!(RpcHeader, &self.header, writer)?; + serialize!(Vec, &self.transactions, writer)?; + serialize!(Option, &self.verbose_data, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u32, reader)?; + let header = deserialize!(RpcHeader, reader)?; + let transactions = deserialize!(Vec, reader)?; + let verbose_data = deserialize!(Option, reader)?; + + Ok(Self { header, transactions, verbose_data }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcBlockVerboseData { pub hash: RpcHash, @@ -25,6 +45,51 @@ pub struct RpcBlockVerboseData { pub is_chain_block: bool, } +impl Serializer for RpcBlockVerboseData { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + store!(RpcHash, &self.hash, writer)?; + store!(f64, &self.difficulty, writer)?; + store!(RpcHash, &self.selected_parent_hash, writer)?; + store!(Vec, &self.transaction_ids, writer)?; + store!(bool, &self.is_header_only, writer)?; + store!(u64, &self.blue_score, writer)?; + store!(Vec, &self.children_hashes, writer)?; + store!(Vec, &self.merge_set_blues_hashes, writer)?; + store!(Vec, &self.merge_set_reds_hashes, writer)?; + store!(bool, &self.is_chain_block, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let hash = load!(RpcHash, reader)?; + let difficulty = load!(f64, reader)?; + let selected_parent_hash = load!(RpcHash, reader)?; + let transaction_ids = load!(Vec, reader)?; + let is_header_only = load!(bool, reader)?; + let blue_score = load!(u64, reader)?; + let children_hashes = load!(Vec, reader)?; + let merge_set_blues_hashes = load!(Vec, reader)?; + let merge_set_reds_hashes = load!(Vec, reader)?; + let is_chain_block = load!(bool, reader)?; + + Ok(Self { + hash, + difficulty, + selected_parent_hash, + transaction_ids, + is_header_only, + blue_score, + children_hashes, + merge_set_blues_hashes, + merge_set_reds_hashes, + is_chain_block, + }) + } +} + cfg_if::cfg_if! { if #[cfg(feature = "wasm32-sdk")] { use wasm_bindgen::prelude::*; diff --git a/rpc/core/src/model/header.rs b/rpc/core/src/model/header.rs index e116f87ea..28d582479 100644 --- a/rpc/core/src/model/header.rs +++ b/rpc/core/src/model/header.rs @@ -1 +1,179 @@ -pub type RpcHeader = kaspa_consensus_core::header::Header; +// pub type RpcHeader = kaspa_consensus_core::header::Header; +use borsh::{BorshDeserialize, BorshSerialize}; +use kaspa_consensus_core::header::Header; +use kaspa_consensus_core::BlueWorkType; +use kaspa_hashes::Hash; +use serde::{Deserialize, Serialize}; +use workflow_serializer::prelude::*; + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcHeader { + /// Cached hash + pub hash: Hash, + pub version: u16, + pub parents_by_level: Vec>, + pub hash_merkle_root: Hash, + pub accepted_id_merkle_root: Hash, + pub utxo_commitment: Hash, + /// Timestamp is in milliseconds + pub timestamp: u64, + pub bits: u32, + pub nonce: u64, + pub daa_score: u64, + pub blue_work: BlueWorkType, + pub blue_score: u64, + pub pruning_point: Hash, +} + +impl RpcHeader { + pub fn direct_parents(&self) -> &[Hash] { + if self.parents_by_level.is_empty() { + &[] + } else { + &self.parents_by_level[0] + } + } +} + +impl AsRef for RpcHeader { + fn as_ref(&self) -> &RpcHeader { + self + } +} + +impl From
for RpcHeader { + fn from(header: Header) -> Self { + Self { + hash: header.hash, + version: header.version, + parents_by_level: header.parents_by_level, + hash_merkle_root: header.hash_merkle_root, + accepted_id_merkle_root: header.accepted_id_merkle_root, + utxo_commitment: header.utxo_commitment, + timestamp: header.timestamp, + bits: header.bits, + nonce: header.nonce, + daa_score: header.daa_score, + blue_work: header.blue_work, + blue_score: header.blue_score, + pruning_point: header.pruning_point, + } + } +} + +impl From<&Header> for RpcHeader { + fn from(header: &Header) -> Self { + Self { + hash: header.hash, + version: header.version, + parents_by_level: header.parents_by_level.clone(), + hash_merkle_root: header.hash_merkle_root, + accepted_id_merkle_root: header.accepted_id_merkle_root, + utxo_commitment: header.utxo_commitment, + timestamp: header.timestamp, + bits: header.bits, + nonce: header.nonce, + daa_score: header.daa_score, + blue_work: header.blue_work, + blue_score: header.blue_score, + pruning_point: header.pruning_point, + } + } +} + +impl From for Header { + fn from(header: RpcHeader) -> Self { + Self { + hash: header.hash, + version: header.version, + parents_by_level: header.parents_by_level, + hash_merkle_root: header.hash_merkle_root, + accepted_id_merkle_root: header.accepted_id_merkle_root, + utxo_commitment: header.utxo_commitment, + timestamp: header.timestamp, + bits: header.bits, + nonce: header.nonce, + daa_score: header.daa_score, + blue_work: header.blue_work, + blue_score: header.blue_score, + pruning_point: header.pruning_point, + } + } +} + +impl From<&RpcHeader> for Header { + fn from(header: &RpcHeader) -> Self { + Self { + hash: header.hash, + version: header.version, + parents_by_level: header.parents_by_level.clone(), + hash_merkle_root: header.hash_merkle_root, + accepted_id_merkle_root: header.accepted_id_merkle_root, + utxo_commitment: header.utxo_commitment, + timestamp: header.timestamp, + bits: header.bits, + nonce: header.nonce, + daa_score: header.daa_score, + blue_work: header.blue_work, + blue_score: header.blue_score, + pruning_point: header.pruning_point, + } + } +} + +impl Serializer for RpcHeader { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u32, &1, writer)?; + + store!(Hash, &self.hash, writer)?; + store!(u16, &self.version, writer)?; + store!(Vec>, &self.parents_by_level, writer)?; + store!(Hash, &self.hash_merkle_root, writer)?; + store!(Hash, &self.accepted_id_merkle_root, writer)?; + store!(Hash, &self.utxo_commitment, writer)?; + store!(u64, &self.timestamp, writer)?; + store!(u32, &self.bits, writer)?; + store!(u64, &self.nonce, writer)?; + store!(u64, &self.daa_score, writer)?; + store!(BlueWorkType, &self.blue_work, writer)?; + store!(u64, &self.blue_score, writer)?; + store!(Hash, &self.pruning_point, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u32, reader)?; + + let hash = load!(Hash, reader)?; + let version = load!(u16, reader)?; + let parents_by_level = load!(Vec>, reader)?; + let hash_merkle_root = load!(Hash, reader)?; + let accepted_id_merkle_root = load!(Hash, reader)?; + let utxo_commitment = load!(Hash, reader)?; + let timestamp = load!(u64, reader)?; + let bits = load!(u32, reader)?; + let nonce = load!(u64, reader)?; + let daa_score = load!(u64, reader)?; + let blue_work = load!(BlueWorkType, reader)?; + let blue_score = load!(u64, reader)?; + let pruning_point = load!(Hash, reader)?; + + Ok(Self { + hash, + version, + parents_by_level, + hash_merkle_root, + accepted_id_merkle_root, + utxo_commitment, + timestamp, + bits, + nonce, + daa_score, + blue_work, + blue_score, + pruning_point, + }) + } +} diff --git a/rpc/core/src/model/mempool.rs b/rpc/core/src/model/mempool.rs index bd08b745a..81ddc8f92 100644 --- a/rpc/core/src/model/mempool.rs +++ b/rpc/core/src/model/mempool.rs @@ -1,9 +1,9 @@ use super::RpcAddress; use super::RpcTransaction; -use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use workflow_serializer::prelude::*; -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct RpcMempoolEntry { pub fee: u64, pub transaction: RpcTransaction, @@ -16,7 +16,22 @@ impl RpcMempoolEntry { } } -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +impl Serializer for RpcMempoolEntry { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u64, &self.fee, writer)?; + serialize!(RpcTransaction, &self.transaction, writer)?; + store!(bool, &self.is_orphan, writer) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let fee = load!(u64, reader)?; + let transaction = deserialize!(RpcTransaction, reader)?; + let is_orphan = load!(bool, reader)?; + Ok(Self { fee, transaction, is_orphan }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct RpcMempoolEntryByAddress { pub address: RpcAddress, pub sending: Vec, @@ -29,6 +44,21 @@ impl RpcMempoolEntryByAddress { } } +impl Serializer for RpcMempoolEntryByAddress { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(RpcAddress, &self.address, writer)?; + serialize!(Vec, &self.sending, writer)?; + serialize!(Vec, &self.receiving, writer) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let address = load!(RpcAddress, reader)?; + let sending = deserialize!(Vec, reader)?; + let receiving = deserialize!(Vec, reader)?; + Ok(Self { address, sending, receiving }) + } +} + cfg_if::cfg_if! { if #[cfg(feature = "wasm32-sdk")] { use wasm_bindgen::prelude::*; diff --git a/rpc/core/src/model/message.rs b/rpc/core/src/model/message.rs index c33565195..f86656b98 100644 --- a/rpc/core/src/model/message.rs +++ b/rpc/core/src/model/message.rs @@ -32,7 +32,7 @@ impl SubmitBlockRequest { impl Serializer for SubmitBlockRequest { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(RpcBlock, &self.block, writer)?; + serialize!(RpcBlock, &self.block, writer)?; store!(bool, &self.allow_non_daa_blocks, writer)?; Ok(()) @@ -40,7 +40,7 @@ impl Serializer for SubmitBlockRequest { fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let block = load!(RpcBlock, reader)?; + let block = deserialize!(RpcBlock, reader)?; let allow_non_daa_blocks = load!(bool, reader)?; Ok(Self { block, allow_non_daa_blocks }) @@ -157,7 +157,7 @@ pub struct GetBlockTemplateResponse { impl Serializer for GetBlockTemplateResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(RpcBlock, &self.block, writer)?; + serialize!(RpcBlock, &self.block, writer)?; store!(bool, &self.is_synced, writer)?; Ok(()) @@ -165,7 +165,7 @@ impl Serializer for GetBlockTemplateResponse { fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let block = load!(RpcBlock, reader)?; + let block = deserialize!(RpcBlock, reader)?; let is_synced = load!(bool, reader)?; Ok(Self { block, is_synced }) @@ -215,14 +215,14 @@ pub struct GetBlockResponse { impl Serializer for GetBlockResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(RpcBlock, &self.block, writer)?; + serialize!(RpcBlock, &self.block, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let block = load!(RpcBlock, reader)?; + let block = deserialize!(RpcBlock, reader)?; Ok(Self { block }) } @@ -464,13 +464,13 @@ impl GetMempoolEntryResponse { impl Serializer for GetMempoolEntryResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(RpcMempoolEntry, &self.mempool_entry, writer)?; + serialize!(RpcMempoolEntry, &self.mempool_entry, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let mempool_entry = load!(RpcMempoolEntry, reader)?; + let mempool_entry = deserialize!(RpcMempoolEntry, reader)?; Ok(Self { mempool_entry }) } } @@ -522,13 +522,13 @@ impl GetMempoolEntriesResponse { impl Serializer for GetMempoolEntriesResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(Vec, &self.mempool_entries, writer)?; + serialize!(Vec, &self.mempool_entries, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let mempool_entries = load!(Vec, reader)?; + let mempool_entries = deserialize!(Vec, reader)?; Ok(Self { mempool_entries }) } } @@ -638,8 +638,7 @@ impl SubmitTransactionRequest { impl Serializer for SubmitTransactionRequest { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - // TODO - store!(RpcTransaction, &self.transaction, writer)?; + serialize!(RpcTransaction, &self.transaction, writer)?; store!(bool, &self.allow_orphan, writer)?; Ok(()) @@ -647,7 +646,7 @@ impl Serializer for SubmitTransactionRequest { fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let transaction = load!(RpcTransaction, reader)?; + let transaction = deserialize!(RpcTransaction, reader)?; let allow_orphan = load!(bool, reader)?; Ok(Self { transaction, allow_orphan }) @@ -858,7 +857,7 @@ impl Serializer for GetBlocksResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; store!(Vec, &self.block_hashes, writer)?; - store!(Vec, &self.blocks, writer)?; + serialize!(Vec, &self.blocks, writer)?; Ok(()) } @@ -866,7 +865,7 @@ impl Serializer for GetBlocksResponse { fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; let block_hashes = load!(Vec, reader)?; - let blocks = load!(Vec, reader)?; + let blocks = deserialize!(Vec, reader)?; Ok(Self { block_hashes, blocks }) } @@ -1231,14 +1230,14 @@ impl GetBalancesByAddressesResponse { impl Serializer for GetBalancesByAddressesResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(Vec, &self.entries, writer)?; + serialize!(Vec, &self.entries, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let entries = load!(Vec, reader)?; + let entries = deserialize!(Vec, reader)?; Ok(Self { entries }) } @@ -1331,14 +1330,14 @@ impl GetUtxosByAddressesResponse { impl Serializer for GetUtxosByAddressesResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(Vec, &self.entries, writer)?; + serialize!(Vec, &self.entries, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let entries = load!(Vec, reader)?; + let entries = deserialize!(Vec, reader)?; Ok(Self { entries }) } @@ -1541,14 +1540,14 @@ impl GetMempoolEntriesByAddressesResponse { impl Serializer for GetMempoolEntriesByAddressesResponse { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(Vec, &self.entries, writer)?; + serialize!(Vec, &self.entries, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let entries = load!(Vec, reader)?; + let entries = deserialize!(Vec, reader)?; Ok(Self { entries }) } @@ -2182,13 +2181,13 @@ pub struct BlockAddedNotification { impl Serializer for BlockAddedNotification { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(RpcBlock, &self.block, writer)?; + serialize!(RpcBlock, &self.block, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let block = load!(RpcBlock, reader)?; + let block = deserialize!(RpcBlock, reader)?; Ok(Self { block: block.into() }) } } @@ -2509,15 +2508,15 @@ impl UtxosChangedNotification { impl Serializer for UtxosChangedNotification { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { store!(u16, &1, writer)?; - store!(Vec, &self.added, writer)?; - store!(Vec, &self.removed, writer)?; + serialize!(Vec, &self.added, writer)?; + serialize!(Vec, &self.removed, writer)?; Ok(()) } fn deserialize(reader: &mut R) -> std::io::Result { let _version = load!(u16, reader)?; - let added = load!(Vec, reader)?; - let removed = load!(Vec, reader)?; + let added = deserialize!(Vec, reader)?; + let removed = deserialize!(Vec, reader)?; Ok(Self { added: added.into(), removed: removed.into() }) } } diff --git a/rpc/core/src/model/tests.rs b/rpc/core/src/model/tests.rs index f9a06032d..ec2715f70 100644 --- a/rpc/core/src/model/tests.rs +++ b/rpc/core/src/model/tests.rs @@ -4,10 +4,9 @@ mod mockery { use crate::{model::*, RpcScriptClass}; use kaspa_addresses::{Prefix, Version}; use kaspa_consensus_core::api::BlockCount; - use kaspa_consensus_core::header::Header; use kaspa_consensus_core::network::NetworkType; use kaspa_consensus_core::subnets::SubnetworkId; - use kaspa_consensus_core::tx::{ScriptPublicKey, TransactionOutpoint, UtxoEntry}; + use kaspa_consensus_core::tx::ScriptPublicKey; use kaspa_hashes::Hash; use kaspa_math::Uint192; use kaspa_notify::subscription::Command; @@ -136,9 +135,9 @@ mod mockery { } } - impl Mock for Header { + impl Mock for RpcHeader { fn mock() -> Self { - Header { + RpcHeader { version: mock(), timestamp: mock(), bits: mock(), @@ -297,15 +296,15 @@ mod mockery { } } - impl Mock for UtxoEntry { + impl Mock for RpcUtxoEntry { fn mock() -> Self { - UtxoEntry { amount: mock(), script_public_key: mock(), block_daa_score: mock(), is_coinbase: true } + RpcUtxoEntry { amount: mock(), script_public_key: mock(), block_daa_score: mock(), is_coinbase: true } } } - impl Mock for TransactionOutpoint { + impl Mock for RpcTransactionOutpoint { fn mock() -> Self { - TransactionOutpoint { transaction_id: mock(), index: mock() } + RpcTransactionOutpoint { transaction_id: mock(), index: mock() } } } diff --git a/rpc/core/src/model/tx.rs b/rpc/core/src/model/tx.rs index bb13f797d..4b3e25f50 100644 --- a/rpc/core/src/model/tx.rs +++ b/rpc/core/src/model/tx.rs @@ -1,9 +1,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; use kaspa_addresses::Address; use kaspa_consensus_core::tx::{ - ScriptPublicKey, ScriptVec, TransactionId, TransactionInput, TransactionOutpoint, TransactionOutput, UtxoEntry, + ScriptPublicKey, ScriptVec, TransactionId, TransactionIndexType, TransactionInput, TransactionOutpoint, TransactionOutput, + UtxoEntry, }; +use kaspa_utils::serde_bytes_fixed_ref; use serde::{Deserialize, Serialize}; +use workflow_serializer::prelude::*; use crate::prelude::{RpcHash, RpcScriptClass, RpcSubnetworkId}; @@ -12,13 +15,120 @@ pub type RpcTransactionId = TransactionId; pub type RpcScriptVec = ScriptVec; pub type RpcScriptPublicKey = ScriptPublicKey; -pub type RpcUtxoEntry = UtxoEntry; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcUtxoEntry { + pub amount: u64, + pub script_public_key: ScriptPublicKey, + pub block_daa_score: u64, + pub is_coinbase: bool, +} + +impl RpcUtxoEntry { + 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 } + } +} + +impl From for RpcUtxoEntry { + fn from(entry: UtxoEntry) -> Self { + Self { + amount: entry.amount, + script_public_key: entry.script_public_key, + block_daa_score: entry.block_daa_score, + is_coinbase: entry.is_coinbase, + } + } +} + +impl From for UtxoEntry { + fn from(entry: RpcUtxoEntry) -> Self { + Self { + amount: entry.amount, + script_public_key: entry.script_public_key, + block_daa_score: entry.block_daa_score, + is_coinbase: entry.is_coinbase, + } + } +} + +impl Serializer for RpcUtxoEntry { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + store!(u64, &self.amount, writer)?; + store!(ScriptPublicKey, &self.script_public_key, writer)?; + store!(u64, &self.block_daa_score, writer)?; + store!(bool, &self.is_coinbase, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let amount = load!(u64, reader)?; + let script_public_key = load!(ScriptPublicKey, reader)?; + let block_daa_score = load!(u64, reader)?; + let is_coinbase = load!(bool, reader)?; + + Ok(Self { amount, script_public_key, block_daa_score, is_coinbase }) + } +} /// Represents a Kaspa transaction outpoint -pub type RpcTransactionOutpoint = TransactionOutpoint; +#[derive(Eq, Hash, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)] +pub struct RpcTransactionOutpoint { + #[serde(with = "serde_bytes_fixed_ref")] + pub transaction_id: TransactionId, + pub index: TransactionIndexType, +} + +impl From for RpcTransactionOutpoint { + fn from(outpoint: TransactionOutpoint) -> Self { + Self { transaction_id: outpoint.transaction_id, index: outpoint.index } + } +} + +impl From for TransactionOutpoint { + fn from(outpoint: RpcTransactionOutpoint) -> Self { + Self { transaction_id: outpoint.transaction_id, index: outpoint.index } + } +} + +impl From for RpcTransactionOutpoint { + fn from(outpoint: kaspa_consensus_client::TransactionOutpoint) -> Self { + TransactionOutpoint::from(outpoint).into() + // Self { transaction_id: outpoint.transaction_id, index: outpoint.index } + } +} + +impl From for kaspa_consensus_client::TransactionOutpoint { + fn from(outpoint: RpcTransactionOutpoint) -> Self { + // Self { transaction_id: outpoint.transaction_id, index: outpoint.index } + TransactionOutpoint::from(outpoint).into() + } +} + +impl Serializer for RpcTransactionOutpoint { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + store!(TransactionId, &self.transaction_id, writer)?; + store!(TransactionIndexType, &self.index, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let transaction_id = load!(TransactionId, reader)?; + let index = load!(TransactionIndexType, reader)?; + + Ok(Self { transaction_id, index }) + } +} /// Represents a Kaspa transaction input -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransactionInput { pub previous_outpoint: RpcTransactionOutpoint, @@ -32,7 +142,7 @@ pub struct RpcTransactionInput { impl From for RpcTransactionInput { fn from(input: TransactionInput) -> Self { Self { - previous_outpoint: input.previous_outpoint, + previous_outpoint: input.previous_outpoint.into(), signature_script: input.signature_script, sequence: input.sequence, sig_op_count: input.sig_op_count, @@ -47,13 +157,49 @@ impl RpcTransactionInput { } } +impl Serializer for RpcTransactionInput { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + serialize!(RpcTransactionOutpoint, &self.previous_outpoint, writer)?; + store!(Vec, &self.signature_script, writer)?; + store!(u64, &self.sequence, writer)?; + store!(u8, &self.sig_op_count, writer)?; + serialize!(Option, &self.verbose_data, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let previous_outpoint = deserialize!(RpcTransactionOutpoint, reader)?; + let signature_script = load!(Vec, reader)?; + let sequence = load!(u64, reader)?; + let sig_op_count = load!(u8, reader)?; + let verbose_data = deserialize!(Option, reader)?; + + Ok(Self { previous_outpoint, signature_script, sequence, sig_op_count, verbose_data }) + } +} + /// Represent Kaspa transaction input verbose data -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransactionInputVerboseData {} +impl Serializer for RpcTransactionInputVerboseData { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + Ok(Self {}) + } +} + /// Represents a Kaspad transaction output -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransactionOutput { pub value: u64, @@ -73,16 +219,54 @@ impl From for RpcTransactionOutput { } } +impl Serializer for RpcTransactionOutput { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + store!(u64, &self.value, writer)?; + store!(RpcScriptPublicKey, &self.script_public_key, writer)?; + serialize!(Option, &self.verbose_data, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let value = load!(u64, reader)?; + let script_public_key = load!(RpcScriptPublicKey, reader)?; + let verbose_data = deserialize!(Option, reader)?; + + Ok(Self { value, script_public_key, verbose_data }) + } +} + /// Represent Kaspa transaction output verbose data -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransactionOutputVerboseData { pub script_public_key_type: RpcScriptClass, pub script_public_key_address: Address, } +impl Serializer for RpcTransactionOutputVerboseData { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + store!(RpcScriptClass, &self.script_public_key_type, writer)?; + store!(Address, &self.script_public_key_address, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let script_public_key_type = load!(RpcScriptClass, reader)?; + let script_public_key_address = load!(Address, reader)?; + + Ok(Self { script_public_key_type, script_public_key_address }) + } +} + /// Represents a Kaspa transaction -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransaction { pub version: u16, @@ -97,8 +281,40 @@ pub struct RpcTransaction { pub verbose_data: Option, } +impl Serializer for RpcTransaction { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u16, &1, writer)?; + store!(u16, &self.version, writer)?; + serialize!(Vec, &self.inputs, writer)?; + serialize!(Vec, &self.outputs, writer)?; + store!(u64, &self.lock_time, writer)?; + store!(RpcSubnetworkId, &self.subnetwork_id, writer)?; + store!(u64, &self.gas, writer)?; + store!(Vec, &self.payload, writer)?; + store!(u64, &self.mass, writer)?; + serialize!(Option, &self.verbose_data, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _struct_version = load!(u16, reader)?; + let version = load!(u16, reader)?; + let inputs = deserialize!(Vec, reader)?; + let outputs = deserialize!(Vec, reader)?; + let lock_time = load!(u64, reader)?; + let subnetwork_id = load!(RpcSubnetworkId, reader)?; + let gas = load!(u64, reader)?; + let payload = load!(Vec, reader)?; + let mass = load!(u64, reader)?; + let verbose_data = deserialize!(Option, reader)?; + + Ok(Self { version, inputs, outputs, lock_time, subnetwork_id, gas, payload, mass, verbose_data }) + } +} + /// Represent Kaspa transaction verbose data -#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RpcTransactionVerboseData { pub transaction_id: RpcTransactionId, @@ -108,6 +324,30 @@ pub struct RpcTransactionVerboseData { pub block_time: u64, } +impl Serializer for RpcTransactionVerboseData { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + store!(u8, &1, writer)?; + store!(RpcTransactionId, &self.transaction_id, writer)?; + store!(RpcHash, &self.hash, writer)?; + store!(u64, &self.mass, writer)?; + store!(RpcHash, &self.block_hash, writer)?; + store!(u64, &self.block_time, writer)?; + + Ok(()) + } + + fn deserialize(reader: &mut R) -> std::io::Result { + let _version = load!(u8, reader)?; + let transaction_id = load!(RpcTransactionId, reader)?; + let hash = load!(RpcHash, reader)?; + let mass = load!(u64, reader)?; + let block_hash = load!(RpcHash, reader)?; + let block_time = load!(u64, reader)?; + + Ok(Self { transaction_id, hash, mass, block_hash, block_time }) + } +} + /// Represents accepted transaction ids #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] diff --git a/rpc/core/src/wasm/convert.rs b/rpc/core/src/wasm/convert.rs index 0c33cf0ec..ce38e32c4 100644 --- a/rpc/core/src/wasm/convert.rs +++ b/rpc/core/src/wasm/convert.rs @@ -1,12 +1,11 @@ use crate::model::*; use kaspa_consensus_client::*; -use kaspa_consensus_core::tx as cctx; use std::sync::Arc; impl From for UtxoEntry { fn from(entry: RpcUtxosByAddressesEntry) -> UtxoEntry { let RpcUtxosByAddressesEntry { address, outpoint, utxo_entry } = entry; - let cctx::UtxoEntry { amount, script_public_key, block_daa_score, is_coinbase } = utxo_entry; + let RpcUtxoEntry { amount, script_public_key, block_daa_score, is_coinbase } = utxo_entry; UtxoEntry { address, outpoint: outpoint.into(), amount, script_public_key, block_daa_score, is_coinbase } } } diff --git a/rpc/grpc/core/src/convert/header.rs b/rpc/grpc/core/src/convert/header.rs index f4d78b7c1..66296a169 100644 --- a/rpc/grpc/core/src/convert/header.rs +++ b/rpc/grpc/core/src/convert/header.rs @@ -1,5 +1,6 @@ use crate::protowire; use crate::{from, try_from}; +use kaspa_consensus_core::header::Header; use kaspa_rpc_core::{FromRpcHex, RpcError, RpcHash, RpcResult, ToRpcHex}; use std::str::FromStr; @@ -31,8 +32,11 @@ from!(item: &Vec, protowire::RpcBlockLevelParents, { Self { parent_hash // ---------------------------------------------------------------------------- try_from!(item: &protowire::RpcBlockHeader, kaspa_rpc_core::RpcHeader, { + + // TODO - restructure using dual structs (with and without hash) + // We re-hash the block to remain as most trustless as possible - Self::new_finalized( + let header = Header::new_finalized( item.version.try_into()?, item.parents.iter().map(Vec::::try_from).collect::>>>()?, RpcHash::from_str(&item.hash_merkle_root)?, @@ -45,7 +49,9 @@ try_from!(item: &protowire::RpcBlockHeader, kaspa_rpc_core::RpcHeader, { kaspa_rpc_core::RpcBlueWorkType::from_rpc_hex(&item.blue_work)?, item.blue_score, RpcHash::from_str(&item.pruning_point)?, - ) + ); + + header.into() }); try_from!(item: &protowire::RpcBlockLevelParents, Vec, { @@ -55,6 +61,7 @@ try_from!(item: &protowire::RpcBlockLevelParents, Vec, { #[cfg(test)] mod tests { use crate::protowire; + use kaspa_consensus_core::header::Header; use kaspa_rpc_core::{RpcHash, RpcHeader}; fn new_unique() -> RpcHash { @@ -106,7 +113,7 @@ mod tests { #[test] fn test_rpc_header() { - let r = RpcHeader::new_finalized( + let r = Header::new_finalized( 0, vec![vec![new_unique(), new_unique(), new_unique()], vec![new_unique()], vec![new_unique(), new_unique()]], new_unique(), @@ -120,6 +127,7 @@ mod tests { 1928374, new_unique(), ); + let r = RpcHeader::from(r); let p: protowire::RpcBlockHeader = (&r).into(); let r2: RpcHeader = (&p).try_into().unwrap(); let p2: protowire::RpcBlockHeader = (&r2).into(); diff --git a/rpc/service/src/converter/consensus.rs b/rpc/service/src/converter/consensus.rs index 9f3f5b661..dda04899f 100644 --- a/rpc/service/src/converter/consensus.rs +++ b/rpc/service/src/converter/consensus.rs @@ -81,7 +81,7 @@ impl ConsensusConverter { vec![] }; - Ok(RpcBlock { header: (*block.header).clone(), transactions, verbose_data }) + Ok(RpcBlock { header: block.header.as_ref().into(), transactions, verbose_data }) } pub fn get_mempool_entry(&self, consensus: &ConsensusProxy, transaction: &MutableTransaction) -> RpcMempoolEntry { diff --git a/testing/integration/src/common/utils.rs b/testing/integration/src/common/utils.rs index 824bda388..f8c547766 100644 --- a/testing/integration/src/common/utils.rs +++ b/testing/integration/src/common/utils.rs @@ -16,7 +16,7 @@ use kaspa_consensus_core::{ }; use kaspa_core::info; use kaspa_grpc_client::GrpcClient; -use kaspa_rpc_core::{api::rpc::RpcApi, BlockAddedNotification, Notification, VirtualDaaScoreChangedNotification}; +use kaspa_rpc_core::{api::rpc::RpcApi, BlockAddedNotification, Notification, RpcUtxoEntry, VirtualDaaScoreChangedNotification}; use kaspa_txscript::pay_to_address_script; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use secp256k1::Keypair; @@ -170,13 +170,13 @@ pub async fn fetch_spendable_utxos( { assert!(resp_entry.address.is_some()); assert_eq!(*resp_entry.address.as_ref().unwrap(), address); - utxos.push((resp_entry.outpoint, resp_entry.utxo_entry)); + utxos.push((TransactionOutpoint::from(resp_entry.outpoint), UtxoEntry::from(resp_entry.utxo_entry))); } utxos.sort_by(|a, b| b.1.amount.cmp(&a.1.amount)); utxos } -pub fn is_utxo_spendable(entry: &UtxoEntry, virtual_daa_score: u64, coinbase_maturity: u64) -> bool { +pub fn is_utxo_spendable(entry: &RpcUtxoEntry, virtual_daa_score: u64, coinbase_maturity: u64) -> bool { let needed_confirmations = if !entry.is_coinbase { 10 } else { coinbase_maturity }; entry.block_daa_score + needed_confirmations <= virtual_daa_score }