diff --git a/Cargo.lock b/Cargo.lock index a730d805f..ab4b74e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3054,6 +3054,7 @@ dependencies = [ "dhat", "dirs 4.0.0", "futures-util", + "kaspa-addresses", "kaspa-addressmanager", "kaspa-consensus", "kaspa-consensus-core", @@ -3069,6 +3070,7 @@ dependencies = [ "kaspa-perf-monitor", "kaspa-rpc-core", "kaspa-rpc-service", + "kaspa-txscript", "kaspa-utils", "kaspa-utxoindex", "kaspa-wrpc-server", diff --git a/consensus/core/src/config/mod.rs b/consensus/core/src/config/mod.rs index d46d5f1c3..3e80ed80c 100644 --- a/consensus/core/src/config/mod.rs +++ b/consensus/core/src/config/mod.rs @@ -7,6 +7,8 @@ use std::ops::Deref; use kaspa_utils::networking::{ContextualNetAddress, IpAddress}; +use crate::utxo::utxo_collection::UtxoCollection; + use { constants::perf::{PerfParams, PERF_PARAMS}, params::Params, @@ -53,6 +55,8 @@ pub struct Config { pub p2p_listen_address: ContextualNetAddress, pub externalip: Option, + + pub initial_utxo_set: UtxoCollection, } impl Config { @@ -74,6 +78,7 @@ impl Config { user_agent_comments: Default::default(), externalip: None, p2p_listen_address: ContextualNetAddress::unspecified(), + initial_utxo_set: Default::default(), } } diff --git a/consensus/core/src/errors/config.rs b/consensus/core/src/errors/config.rs index 319d97296..d5e9c8d97 100644 --- a/consensus/core/src/errors/config.rs +++ b/consensus/core/src/errors/config.rs @@ -7,6 +7,9 @@ pub enum ConfigError { #[error("Configuration: --logdir and --nologfiles cannot be used together")] MixedLogDirAndNoLogFiles, + + #[error("Cannot set fake UTXOs on any network except devnet")] + FakeUTXOsOnNonDevnet, } pub type ConfigResult = std::result::Result; diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index cab615e8f..4eea6900c 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -243,7 +243,7 @@ impl Consensus { if config.process_genesis { header_processor.process_genesis(); body_processor.process_genesis(); - virtual_processor.process_genesis(); + virtual_processor.process_genesis(&config.initial_utxo_set); } Self { diff --git a/consensus/src/model/stores/virtual_state.rs b/consensus/src/model/stores/virtual_state.rs index 79dcfad17..cbd03c477 100644 --- a/consensus/src/model/stores/virtual_state.rs +++ b/consensus/src/model/stores/virtual_state.rs @@ -57,7 +57,7 @@ impl VirtualState { } } - pub fn from_genesis(genesis: &GenesisBlock, ghostdag_data: GhostdagData) -> Self { + pub fn from_genesis(genesis: &GenesisBlock, ghostdag_data: GhostdagData, utxo_diff: UtxoDiff) -> Self { Self { parents: vec![genesis.hash], ghostdag_data, @@ -65,7 +65,7 @@ impl VirtualState { bits: genesis.bits, past_median_time: genesis.timestamp, multiset: MuHash::new(), - utxo_diff: UtxoDiff::default(), // Virtual diff is initially empty since genesis receives no reward + utxo_diff, accepted_tx_ids: genesis.build_genesis_transactions().into_iter().map(|tx| tx.id()).collect(), mergeset_rewards: BlockHashMap::new(), mergeset_non_daa: BlockHashSet::from_iter(std::iter::once(genesis.hash)), diff --git a/consensus/src/pipeline/virtual_processor/processor.rs b/consensus/src/pipeline/virtual_processor/processor.rs index a34a8e92b..d5963aa01 100644 --- a/consensus/src/pipeline/virtual_processor/processor.rs +++ b/consensus/src/pipeline/virtual_processor/processor.rs @@ -54,8 +54,10 @@ use kaspa_consensus_core::{ config::genesis::GenesisBlock, header::Header, merkle::calc_hash_merkle_root, + muhash::MuHashExtensions, tx::{MutableTransaction, Transaction}, utxo::{ + utxo_collection::UtxoCollection, utxo_diff::UtxoDiff, utxo_view::{UtxoView, UtxoViewComposition}, }, @@ -840,14 +842,22 @@ impl VirtualStateProcessor { /// Initializes UTXO state of genesis and points virtual at genesis. /// Note that pruning point-related stores are initialized by `init` - pub fn process_genesis(self: &Arc) { + pub fn process_genesis(self: &Arc, initial_utxo_set: &UtxoCollection) { // Write the UTXO state of genesis - self.commit_utxo_state(self.genesis.hash, UtxoDiff::default(), MuHash::new(), AcceptanceData::default()); + let muhash = { + let mut muhash = MuHash::new(); + for (outpoint, entry) in initial_utxo_set { + muhash.add_utxo(&outpoint, &entry); + } + muhash + }; + let utxo_diff = UtxoDiff::new(initial_utxo_set.clone(), UtxoCollection::default()); + self.commit_utxo_state(self.genesis.hash, utxo_diff.clone(), muhash, AcceptanceData::default()); // Init virtual stores self.virtual_stores .write() .state - .set(Arc::new(VirtualState::from_genesis(&self.genesis, self.ghostdag_manager.ghostdag(&[self.genesis.hash])))) + .set(Arc::new(VirtualState::from_genesis(&self.genesis, self.ghostdag_manager.ghostdag(&[self.genesis.hash]), utxo_diff))) .unwrap(); // Init the virtual selected chain store let mut batch = WriteBatch::default(); diff --git a/kaspad/Cargo.toml b/kaspad/Cargo.toml index 422fbc919..d266e4aff 100644 --- a/kaspad/Cargo.toml +++ b/kaspad/Cargo.toml @@ -29,6 +29,8 @@ kaspa-mining.workspace = true kaspa-addressmanager.workspace = true kaspa-consensusmanager.workspace = true kaspa-perf-monitor.workspace = true +kaspa-addresses.workspace = true +kaspa-txscript.workspace = true async-channel.workspace = true thiserror.workspace = true diff --git a/kaspad/src/args.rs b/kaspad/src/args.rs index 506e08561..26465b82c 100644 --- a/kaspad/src/args.rs +++ b/kaspad/src/args.rs @@ -2,11 +2,16 @@ use clap::ArgAction; #[allow(unused)] use clap::{arg, command, Arg, Command}; +use kaspa_addresses::Address; use kaspa_consensus_core::{ config::Config, networktype::{NetworkId, NetworkType}, + tx::{TransactionId, TransactionOutpoint, UtxoEntry}, + utxo::utxo_collection::UtxoCollection, }; use kaspa_core::kaspad_env::version; +use kaspa_hashes::TransactionID; +use kaspa_txscript::pay_to_address_script; use kaspa_utils::networking::{ContextualNetAddress, IpAddress}; use kaspa_wrpc_server::address::WrpcNetAddress; @@ -44,6 +49,9 @@ pub struct Args { pub externalip: Option, pub perf_metrics: bool, pub perf_metrics_interval_sec: u64, + pub num_fake_utxos: Option, + pub fake_utxos_address: Option, + pub fake_utxos_amount: u64, } impl Default for Args { @@ -80,6 +88,9 @@ impl Default for Args { perf_metrics: false, perf_metrics_interval_sec: 1, externalip: None, + num_fake_utxos: None, + fake_utxos_address: None, + fake_utxos_amount: 1_000_000, } } } @@ -93,6 +104,24 @@ impl Args { // TODO: change to `config.enable_sanity_checks = self.sanity` when we reach stable versions config.enable_sanity_checks = true; config.user_agent_comments = self.user_agent_comments.clone(); + + if let Some(num_fake_utxos) = self.num_fake_utxos { + let addr = Address::try_from(&self.fake_utxos_address.as_ref().unwrap()[..]).unwrap(); + let spk = pay_to_address_script(&addr); + config.initial_utxo_set = (1..=num_fake_utxos) + .map(|i| { + ( + TransactionOutpoint { transaction_id: i.into(), index: 0 }, + UtxoEntry { + amount: self.fake_utxos_amount, + script_public_key: spk.clone(), + block_daa_score: 0, + is_coinbase: false, + }, + ) + }) + .collect(); + } } pub fn network(&self) -> NetworkId { @@ -261,6 +290,27 @@ pub fn cli() -> Command { .value_parser(clap::value_parser!(u64)) .help("Interval in seconds for performance metrics collection."), ) + .arg( + Arg::new("num-fake-utxos") + .long("num-fake-utxos") + .require_equals(true) + .value_parser(clap::value_parser!(u64)) + .hide(true), + ) + .arg( + Arg::new("fake-utxos-address") + .long("fake-utxos-address") + .require_equals(true) + .value_parser(clap::value_parser!(String)) + .hide(true), + ) + .arg( + Arg::new("fake-utxos-amount") + .long("fake-utxos-amount") + .require_equals(true) + .value_parser(clap::value_parser!(u64)) + .hide(true), + ) } pub fn parse_args() -> Args { @@ -302,6 +352,9 @@ pub fn parse_args() -> Args { .get_one::("perf-metrics-interval-sec") .cloned() .unwrap_or(defaults.perf_metrics_interval_sec), + num_fake_utxos: m.get_one::("num-fake-utxos").cloned(), + fake_utxos_address: m.get_one::("fake-utxos-address").cloned(), + fake_utxos_amount: m.get_one::("fake-utxos-amount").cloned().unwrap_or(defaults.fake_utxos_amount), } } diff --git a/kaspad/src/daemon.rs b/kaspad/src/daemon.rs index 6e51af673..4b713c6df 100644 --- a/kaspad/src/daemon.rs +++ b/kaspad/src/daemon.rs @@ -48,6 +48,10 @@ fn get_app_dir() -> PathBuf { } fn validate_config_and_args(_config: &Arc, args: &Args) -> ConfigResult<()> { + if args.num_fake_utxos.is_some() && !args.devnet { + return Err(ConfigError::MixedConnectAndAddPeers); + } + if !args.connect_peers.is_empty() && !args.add_peers.is_empty() { return Err(ConfigError::MixedConnectAndAddPeers); } diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index 6631b423a..435c4382a 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -2,6 +2,7 @@ use super::collector::{CollectorFromConsensus, CollectorFromIndex}; use crate::converter::{consensus::ConsensusConverter, index::IndexConverter, protocol::ProtocolConverter}; +use crate::service::NetworkType::{Mainnet, Testnet}; use async_trait::async_trait; use kaspa_consensus::pipeline::ProcessingCounters; use kaspa_consensus_core::{ @@ -353,7 +354,8 @@ impl RpcApi for RpcCoreService { mempool_size: self.mining_manager.clone().transaction_count(true, false).await as u64, server_version: version().to_string(), is_utxo_indexed: self.config.utxoindex, - is_synced: self.flow_context.hub().has_peers() && is_nearly_synced, + is_synced: (![Mainnet, Testnet].contains(self.flow_context.config.net.as_type()) || self.flow_context.hub().has_peers()) + && is_nearly_synced, has_notify_command: true, has_message_id: true, }) diff --git a/wallet/core/src/network.rs b/wallet/core/src/network.rs index 9e2c913c4..71e7138c2 100644 --- a/wallet/core/src/network.rs +++ b/wallet/core/src/network.rs @@ -82,7 +82,7 @@ impl FromStr for NetworkId { // diallow network types without suffix (other than mainnet) // lack of suffix makes it impossible to distinguish between // multiple testnet networks - if !matches!(network_type, NetworkType::Mainnet) && suffix.is_none() { + if matches!(network_type, NetworkType::Testnet) && suffix.is_none() { return Err(Error::MissingNetworkSuffix(network_name.to_string())); } match parts.next() {