diff --git a/applications/tari_app_grpc/proto/base_node.proto b/applications/tari_app_grpc/proto/base_node.proto index 013fe970d4..94139c6b15 100644 --- a/applications/tari_app_grpc/proto/base_node.proto +++ b/applications/tari_app_grpc/proto/base_node.proto @@ -25,6 +25,7 @@ import "types.proto"; import "transaction.proto"; import "block.proto"; import "network.proto"; +import "sidechain_types.proto"; package tari.rpc; @@ -39,7 +40,7 @@ service BaseNode { // Returns the block timing for the chain heights rpc GetBlockTiming(HeightRequest) returns (BlockTimingResponse); // Returns the network Constants - rpc GetConstants(Empty) returns (ConsensusConstants); + rpc GetConstants(BlockHeight) returns (ConsensusConstants); // Returns Block Sizes rpc GetBlockSize (BlockGroupRequest) returns (BlockGroupResponse); // Returns Block Fees @@ -88,7 +89,12 @@ service BaseNode { rpc ListConnectedPeers(Empty) returns (ListConnectedPeersResponse); // Get mempool stats rpc GetMempoolStats(Empty) returns (MempoolStatsResponse); - + // Get VNs + rpc GetActiveValidatorNodes(GetActiveValidatorNodesRequest) returns (stream GetActiveValidatorNodesResponse); + rpc GetShardKey(GetShardKeyRequest) returns (GetShardKeyResponse); + // Get templates + rpc GetTemplateRegistrations(GetTemplateRegistrationsRequest) returns (stream GetTemplateRegistrationResponse); + rpc GetSideChainUtxos(GetSideChainUtxosRequest) returns (stream GetSideChainUtxosResponse); } message GetAssetMetadataRequest { @@ -435,3 +441,48 @@ message MempoolStatsResponse { uint64 unconfirmed_weight = 4; } +message GetActiveValidatorNodesRequest { + uint64 height = 1; +} + +message GetActiveValidatorNodesResponse { + bytes shard_key = 1; + bytes public_key = 2; +} + +message GetShardKeyRequest { + uint64 height = 1; + bytes public_key = 2; +} + +message GetShardKeyResponse { + bytes shard_key = 1; + bool found = 2; +} + +message GetTemplateRegistrationsRequest { + bytes start_hash = 1; + uint64 count = 2; +} + +message GetTemplateRegistrationResponse { + bytes utxo_hash = 1; + TemplateRegistration registration = 2; +} + +message BlockInfo { + uint64 height = 1; + bytes hash = 2; + bytes next_block_hash = 3; +} + +message GetSideChainUtxosRequest { + bytes start_hash = 1; + uint64 count = 2; +} + +message GetSideChainUtxosResponse { + BlockInfo block_info = 1; + repeated TransactionOutput outputs = 2; +} + diff --git a/applications/tari_app_grpc/proto/block.proto b/applications/tari_app_grpc/proto/block.proto index 821487e224..223ef6d815 100644 --- a/applications/tari_app_grpc/proto/block.proto +++ b/applications/tari_app_grpc/proto/block.proto @@ -61,6 +61,8 @@ message BlockHeader { uint64 output_mmr_size = 14; // Sum of script offsets for all kernels in this block. bytes total_script_offset = 15; + // Merkle root of validator nodes + bytes validator_node_mr = 16; } // Metadata required for validating the Proof of Work calculation diff --git a/applications/tari_app_grpc/proto/sidechain_types.proto b/applications/tari_app_grpc/proto/sidechain_types.proto index d03b7b187b..421a1bd8f0 100644 --- a/applications/tari_app_grpc/proto/sidechain_types.proto +++ b/applications/tari_app_grpc/proto/sidechain_types.proto @@ -23,5 +23,42 @@ syntax = "proto3"; package tari.rpc; -message SideChainFeatures { +import "types.proto"; + +message SideChainFeature { + oneof side_chain_feature { + ValidatorNodeRegistration validator_node_registration = 1; + TemplateRegistration template_registration = 2; + } +} + +message ValidatorNodeRegistration { + bytes public_key = 1; + Signature signature = 2; +} + +message TemplateRegistration { + bytes author_public_key = 1; + Signature author_signature = 2; + string template_name = 3; + uint32 template_version = 4; + TemplateType template_type = 5; + BuildInfo build_info = 6; + bytes binary_sha = 7; + string binary_url = 8; +} + +message TemplateType { + oneof template_type { + WasmInfo wasm = 1; + } +} + +message WasmInfo { + uint32 abi_version = 1; +} + +message BuildInfo { + string repo_url = 1; + bytes commit_hash = 2; } diff --git a/applications/tari_app_grpc/proto/transaction.proto b/applications/tari_app_grpc/proto/transaction.proto index a713f7f7f8..8d21963fb9 100644 --- a/applications/tari_app_grpc/proto/transaction.proto +++ b/applications/tari_app_grpc/proto/transaction.proto @@ -123,7 +123,7 @@ message OutputFeatures { // require a min maturity of the Coinbase_lock_height, this should be checked on receiving new blocks. uint64 maturity = 3; bytes metadata = 4; - SideChainFeatures sidechain_features = 5; + SideChainFeature sidechain_feature = 5; } diff --git a/applications/tari_app_grpc/proto/types.proto b/applications/tari_app_grpc/proto/types.proto index a353891e41..4afef72706 100644 --- a/applications/tari_app_grpc/proto/types.proto +++ b/applications/tari_app_grpc/proto/types.proto @@ -23,9 +23,20 @@ syntax = "proto3"; package tari.rpc; +/// An unsigned range interface to more accurately represent Rust native Range's +message Range { + uint64 min = 1; + uint64 max = 2; +} + /// An Empty placeholder for endpoints without request parameters message Empty {} +/// Define an interface for block height +message BlockHeight { + uint64 block_height = 1; +} + // Define the explicit Signature implementation for the Tari base layer. A different signature scheme can be // employed by redefining this type. message Signature { @@ -41,6 +52,36 @@ message ComSignature { bytes signature_v = 3; } +/// PoW Algorithm constants +message PowAlgorithmConstants { + uint64 max_target_time = 1; + uint64 min_difficulty = 2; + uint64 max_difficulty = 3; + uint64 target_time = 4; +} + +/// Weight params +message WeightParams { + uint64 kernel_weight = 1; + uint64 input_weight = 2; + uint64 output_weight = 3; + uint64 metadata_bytes_per_gram = 4; +} + +/// Output version +message OutputsVersion { + Range outputs = 1; + Range features = 2; +} + +/// Output types +enum OutputType { + STANDARD = 0; + COINBASE = 1; + BURN = 2; + VALIDATOR_NODE_REGISTRATION = 3; + CODE_TEMPLATE_REGISTRATION = 4; +} /// Consensus Constants response message ConsensusConstants { @@ -76,4 +117,28 @@ message ConsensusConstants { uint64 block_weight_outputs = 15; /// Block weight for kernels uint64 block_weight_kernels = 16; + /// This is to keep track of the value inside of the genesis block + uint64 faucet_value = 17; + /// Maximum byte size of TariScript + uint64 max_script_byte_size = 18; + /// How long does it take to timeout validator node registration + uint64 validator_node_timeout = 19; + /// The height at which these constants become effective + uint64 effective_from_height = 20; + /// Current version of the blockchain + Range valid_blockchain_version_range = 21; + /// This is the maximum age a monero merge mined seed can be reused + uint64 max_randomx_seed_height = 22; + /// This keeps track of the block split targets and which algo is accepted + map proof_of_work = 23; + /// Transaction Weight params + WeightParams transaction_weight = 24; + /// Range of valid transaction input versions + Range input_version_range = 26; + /// Range of valid transaction output (and features) versions + OutputsVersion output_version_range = 27; + /// Range of valid transaction kernel versions + Range kernel_version_range = 28; + /// An allowlist of output types + repeated OutputType permitted_output_types = 29; } diff --git a/applications/tari_app_grpc/proto/validator_node.proto b/applications/tari_app_grpc/proto/validator_node.proto index a549134607..9b8d73ebb1 100644 --- a/applications/tari_app_grpc/proto/validator_node.proto +++ b/applications/tari_app_grpc/proto/validator_node.proto @@ -118,7 +118,7 @@ message Authority { bytes proxied_by = 3; } -message InvokeMethodRequest{ +message InvokeMethodRequest { bytes contract_id = 1; uint32 template_id = 2; string method = 3; diff --git a/applications/tari_app_grpc/proto/wallet.proto b/applications/tari_app_grpc/proto/wallet.proto index 3675c3f11d..417b9dc439 100644 --- a/applications/tari_app_grpc/proto/wallet.proto +++ b/applications/tari_app_grpc/proto/wallet.proto @@ -71,9 +71,13 @@ service Wallet { rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse); // This will claim a HTLC refund transaction rpc ClaimHtlcRefundTransaction(ClaimHtlcRefundRequest) returns (ClaimHtlcRefundResponse); + // Creates a transaction with a template registration output + rpc CreateTemplateRegistration(CreateTemplateRegistrationRequest) returns (CreateTemplateRegistrationResponse); rpc SetBaseNode(SetBaseNodeRequest) returns (SetBaseNodeResponse); rpc StreamTransactionEvents(TransactionEventRequest) returns (stream TransactionEventResponse); + + rpc RegisterValidatorNode(RegisterValidatorNodeRequest) returns (RegisterValidatorNodeResponse); } message GetVersionRequest { } @@ -97,6 +101,7 @@ message CreateBurnTransactionRequest{ string message = 3; } + message PaymentRecipient { string address = 1; uint64 amount = 2; @@ -257,6 +262,16 @@ message ImportUtxosResponse { repeated uint64 tx_ids = 1; } +message CreateTemplateRegistrationRequest { + TemplateRegistration template_registration = 1; + uint64 fee_per_gram = 2; +} + +message CreateTemplateRegistrationResponse { + uint64 tx_id = 1; + bytes template_address = 2; +} + message CancelTransactionRequest { uint64 tx_id = 1; } @@ -307,3 +322,16 @@ message TransactionEvent { message TransactionEventResponse { TransactionEvent transaction = 1; } + +message RegisterValidatorNodeRequest { + bytes validator_node_public_key = 1; + Signature validator_node_signature = 2; + uint64 fee_per_gram = 3; + string message = 4; +} + +message RegisterValidatorNodeResponse { + uint64 transaction_id = 1; + bool is_success = 2; + string failure_message = 3; +} \ No newline at end of file diff --git a/applications/tari_app_grpc/src/conversions/block_header.rs b/applications/tari_app_grpc/src/conversions/block_header.rs index c921ebe53b..0577701bd2 100644 --- a/applications/tari_app_grpc/src/conversions/block_header.rs +++ b/applications/tari_app_grpc/src/conversions/block_header.rs @@ -53,6 +53,7 @@ impl From for grpc::BlockHeader { pow_algo: pow_algo.as_u64(), pow_data: h.pow.pow_data, }), + validator_node_mr: h.validator_node_mr.to_vec(), } } } @@ -91,6 +92,7 @@ impl TryFrom for BlockHeader { total_script_offset, nonce: header.nonce, pow, + validator_node_mr: FixedHash::try_from(header.validator_node_mr).map_err(|err| err.to_string())?, }) } } diff --git a/applications/tari_app_grpc/src/conversions/consensus_constants.rs b/applications/tari_app_grpc/src/conversions/consensus_constants.rs index be0b45e28a..e35256a08e 100644 --- a/applications/tari_app_grpc/src/conversions/consensus_constants.rs +++ b/applications/tari_app_grpc/src/conversions/consensus_constants.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::convert::TryFrom; +use std::{collections::HashMap, convert::TryFrom, iter::FromIterator}; use tari_core::{consensus::ConsensusConstants, proof_of_work::PowAlgorithm}; @@ -30,6 +30,73 @@ impl From for grpc::ConsensusConstants { fn from(cc: ConsensusConstants) -> Self { let (emission_initial, emission_decay, emission_tail) = cc.emission_amounts(); let weight_params = cc.transaction_weight().params(); + let input_version_range = cc.input_version_range().clone().into_inner(); + let input_version_range = grpc::Range { + min: u64::from(input_version_range.0.as_u8()), + max: u64::from(input_version_range.1.as_u8()), + }; + let kernel_version_range = cc.kernel_version_range().clone().into_inner(); + let kernel_version_range = grpc::Range { + min: u64::from(kernel_version_range.0.as_u8()), + max: u64::from(kernel_version_range.1.as_u8()), + }; + let valid_blockchain_version_range = cc.valid_blockchain_version_range().clone().into_inner(); + let valid_blockchain_version_range = grpc::Range { + min: u64::from(valid_blockchain_version_range.0), + max: u64::from(valid_blockchain_version_range.1), + }; + let transaction_weight = cc.transaction_weight(); + let metadata_bytes_per_gram = if let Some(val) = transaction_weight.params().metadata_bytes_per_gram { + u64::from(val) + } else { + 0u64 + }; + let transaction_weight = grpc::WeightParams { + kernel_weight: cc.transaction_weight().params().kernel_weight, + input_weight: cc.transaction_weight().params().input_weight, + output_weight: cc.transaction_weight().params().output_weight, + metadata_bytes_per_gram, + }; + let output_version_range = cc.output_version_range(); + let outputs = grpc::Range { + min: u64::from(output_version_range.outputs.start().as_u8()), + max: u64::from(output_version_range.outputs.end().as_u8()), + }; + let features = grpc::Range { + min: u64::from(output_version_range.features.start().as_u8()), + max: u64::from(output_version_range.features.end().as_u8()), + }; + + let output_version_range = grpc::OutputsVersion { + outputs: Some(outputs), + features: Some(features), + }; + + let permitted_output_types = cc.permitted_output_types(); + let permitted_output_types = permitted_output_types + .iter() + .map(|ot| i32::from(ot.as_byte())) + .collect::>(); + + let monero_pow = PowAlgorithm::Monero; + let sha3_pow = PowAlgorithm::Sha3; + + let monero_pow = grpc::PowAlgorithmConstants { + max_target_time: cc.get_difficulty_max_block_interval(monero_pow), + max_difficulty: cc.max_pow_difficulty(monero_pow).as_u64(), + min_difficulty: cc.min_pow_difficulty(monero_pow).as_u64(), + target_time: cc.get_diff_target_block_interval(monero_pow), + }; + + let sha3_pow = grpc::PowAlgorithmConstants { + max_target_time: cc.get_difficulty_max_block_interval(sha3_pow), + max_difficulty: cc.max_pow_difficulty(sha3_pow).as_u64(), + min_difficulty: cc.min_pow_difficulty(sha3_pow).as_u64(), + target_time: cc.get_diff_target_block_interval(sha3_pow), + }; + + let proof_of_work = HashMap::from_iter([(0u32, monero_pow), (1u32, sha3_pow)]); + Self { coinbase_lock_height: cc.coinbase_lock_height(), blockchain_version: cc.blockchain_version().into(), @@ -46,6 +113,18 @@ impl From for grpc::ConsensusConstants { block_weight_inputs: weight_params.input_weight, block_weight_outputs: weight_params.output_weight, block_weight_kernels: weight_params.kernel_weight, + validator_node_timeout: cc.validator_node_timeout(), + max_script_byte_size: cc.get_max_script_byte_size() as u64, + faucet_value: cc.faucet_value().as_u64(), + effective_from_height: cc.effective_from_height(), + input_version_range: Some(input_version_range), + kernel_version_range: Some(kernel_version_range), + valid_blockchain_version_range: Some(valid_blockchain_version_range), + proof_of_work, + transaction_weight: Some(transaction_weight), + max_randomx_seed_height: cc.max_randomx_seed_height(), + output_version_range: Some(output_version_range), + permitted_output_types, } } } diff --git a/applications/tari_app_grpc/src/conversions/mod.rs b/applications/tari_app_grpc/src/conversions/mod.rs index c08c3d0cdb..f9b6d30455 100644 --- a/applications/tari_app_grpc/src/conversions/mod.rs +++ b/applications/tari_app_grpc/src/conversions/mod.rs @@ -32,7 +32,7 @@ mod new_block_template; mod output_features; mod peer; mod proof_of_work; -mod sidechain_features; +mod sidechain_feature; mod signature; mod transaction; mod transaction_input; diff --git a/applications/tari_app_grpc/src/conversions/output_features.rs b/applications/tari_app_grpc/src/conversions/output_features.rs index 872188e6b8..a1dc0633de 100644 --- a/applications/tari_app_grpc/src/conversions/output_features.rs +++ b/applications/tari_app_grpc/src/conversions/output_features.rs @@ -26,7 +26,7 @@ use tari_core::transactions::transaction_components::{ OutputFeatures, OutputFeaturesVersion, OutputType, - SideChainFeatures, + SideChainFeature, }; use crate::tari_rpc as grpc; @@ -35,9 +35,10 @@ impl TryFrom for OutputFeatures { type Error = String; fn try_from(features: grpc::OutputFeatures) -> Result { - let sidechain_features = features - .sidechain_features - .map(SideChainFeatures::try_from) + let sidechain_feature = features + .sidechain_feature + .and_then(|f| f.side_chain_feature) + .map(SideChainFeature::try_from) .transpose()?; let output_type = features @@ -52,7 +53,7 @@ impl TryFrom for OutputFeatures { OutputType::from_byte(output_type).ok_or_else(|| "Invalid or unrecognised output type".to_string())?, features.maturity, features.metadata, - sidechain_features, + sidechain_feature, )) } } @@ -64,7 +65,7 @@ impl From for grpc::OutputFeatures { output_type: u32::from(features.output_type.as_byte()), maturity: features.maturity, metadata: features.metadata, - sidechain_features: features.sidechain_features.map(|v| *v).map(Into::into), + sidechain_feature: features.sidechain_feature.map(Into::into), } } } diff --git a/applications/tari_app_grpc/src/conversions/sidechain_feature.rs b/applications/tari_app_grpc/src/conversions/sidechain_feature.rs new file mode 100644 index 0000000000..815f0c5818 --- /dev/null +++ b/applications/tari_app_grpc/src/conversions/sidechain_feature.rs @@ -0,0 +1,192 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::convert::{TryFrom, TryInto}; + +use tari_common_types::types::{PublicKey, Signature}; +use tari_core::{ + consensus::MaxSizeString, + transactions::transaction_components::{ + BuildInfo, + CodeTemplateRegistration, + SideChainFeature, + TemplateType, + ValidatorNodeRegistration, + }, +}; +use tari_utilities::ByteArray; + +use crate::tari_rpc as grpc; + +//---------------------------------- SideChainFeature --------------------------------------------// +impl From for grpc::SideChainFeature { + fn from(value: SideChainFeature) -> Self { + Self { + side_chain_feature: Some(value.into()), + } + } +} + +impl From for grpc::side_chain_feature::SideChainFeature { + fn from(value: SideChainFeature) -> Self { + match value { + SideChainFeature::ValidatorNodeRegistration(template_reg) => { + grpc::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(template_reg.into()) + }, + SideChainFeature::TemplateRegistration(template_reg) => { + grpc::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg.into()) + }, + } + } +} + +impl TryFrom for SideChainFeature { + type Error = String; + + fn try_from(features: grpc::side_chain_feature::SideChainFeature) -> Result { + match features { + grpc::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(vn_reg) => { + Ok(SideChainFeature::ValidatorNodeRegistration(vn_reg.try_into()?)) + }, + grpc::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg) => { + Ok(SideChainFeature::TemplateRegistration(template_reg.try_into()?)) + }, + } + } +} + +// -------------------------------- ValidatorNodeRegistration -------------------------------- // +impl TryFrom for ValidatorNodeRegistration { + type Error = String; + + fn try_from(value: grpc::ValidatorNodeRegistration) -> Result { + Ok(Self { + public_key: PublicKey::from_bytes(&value.public_key).map_err(|e| e.to_string())?, + signature: value + .signature + .map(Signature::try_from) + .ok_or("signature not provided")??, + }) + } +} + +impl From for grpc::ValidatorNodeRegistration { + fn from(value: ValidatorNodeRegistration) -> Self { + Self { + public_key: value.public_key.to_vec(), + signature: Some(value.signature.into()), + } + } +} + +// -------------------------------- TemplateRegistration -------------------------------- // +impl TryFrom for CodeTemplateRegistration { + type Error = String; + + fn try_from(value: grpc::TemplateRegistration) -> Result { + Ok(Self { + author_public_key: PublicKey::from_bytes(&value.author_public_key).map_err(|e| e.to_string())?, + author_signature: value + .author_signature + .map(Signature::try_from) + .ok_or("author_signature not provided")??, + template_name: MaxSizeString::try_from(value.template_name).map_err(|e| e.to_string())?, + template_version: value + .template_version + .try_into() + .map_err(|_| "Invalid template version")?, + template_type: value + .template_type + .map(TryFrom::try_from) + .ok_or("Template type not provided")??, + build_info: value + .build_info + .map(TryFrom::try_from) + .ok_or("Build info not provided")??, + binary_sha: value.binary_sha.try_into().map_err(|_| "Invalid commit sha")?, + binary_url: MaxSizeString::try_from(value.binary_url).map_err(|e| e.to_string())?, + }) + } +} + +impl From for grpc::TemplateRegistration { + fn from(value: CodeTemplateRegistration) -> Self { + Self { + author_public_key: value.author_public_key.to_vec(), + author_signature: Some(value.author_signature.into()), + template_name: value.template_name.to_string(), + template_version: u32::from(value.template_version), + template_type: Some(value.template_type.into()), + build_info: Some(value.build_info.into()), + binary_sha: value.binary_sha.to_vec(), + binary_url: value.binary_url.to_string(), + } + } +} + +// -------------------------------- TemplateType -------------------------------- // +impl TryFrom for TemplateType { + type Error = String; + + fn try_from(value: grpc::TemplateType) -> Result { + let template_type = value.template_type.ok_or("Template type not provided")?; + match template_type { + grpc::template_type::TemplateType::Wasm(wasm) => Ok(TemplateType::Wasm { + abi_version: wasm.abi_version.try_into().map_err(|_| "abi_version overflowed")?, + }), + } + } +} + +impl From for grpc::TemplateType { + fn from(value: TemplateType) -> Self { + match value { + TemplateType::Wasm { abi_version } => Self { + template_type: Some(grpc::template_type::TemplateType::Wasm(grpc::WasmInfo { + abi_version: abi_version.into(), + })), + }, + } + } +} + +// -------------------------------- BuildInfo -------------------------------- // + +impl TryFrom for BuildInfo { + type Error = String; + + fn try_from(value: grpc::BuildInfo) -> Result { + Ok(Self { + repo_url: value.repo_url.try_into().map_err(|_| "Invalid repo url")?, + commit_hash: value.commit_hash.try_into().map_err(|_| "Invalid commit hash")?, + }) + } +} + +impl From for grpc::BuildInfo { + fn from(value: BuildInfo) -> Self { + Self { + repo_url: value.repo_url.into_string(), + commit_hash: value.commit_hash.into_vec(), + } + } +} diff --git a/applications/tari_app_utilities/src/common_cli_args.rs b/applications/tari_app_utilities/src/common_cli_args.rs index 6da5808d48..cc9e2d1508 100644 --- a/applications/tari_app_utilities/src/common_cli_args.rs +++ b/applications/tari_app_utilities/src/common_cli_args.rs @@ -35,10 +35,10 @@ pub struct CommonCliArgs { default_value_t= defaults::base_path(), env = "TARI_BASE_DIR" )] - base_path: String, + pub base_path: String, /// A path to the configuration file to use (config.toml) #[clap(short, long, default_value_t= defaults::config())] - config: String, + pub config: String, /// The path to the log configuration file #[clap(short, long, alias = "log_config")] pub log_config: Option, diff --git a/applications/tari_base_node/src/builder.rs b/applications/tari_base_node/src/builder.rs index 60ba9f2953..0f65f46b81 100644 --- a/applications/tari_base_node/src/builder.rs +++ b/applications/tari_base_node/src/builder.rs @@ -53,7 +53,7 @@ use tari_service_framework::ServiceHandles; use tari_shutdown::ShutdownSignal; use tokio::sync::watch; -use crate::{bootstrap::BaseNodeBootstrapper, config::DatabaseType, ApplicationConfig}; +use crate::{bootstrap::BaseNodeBootstrapper, ApplicationConfig, DatabaseType}; const LOG_TARGET: &str = "c::bn::initialization"; @@ -172,9 +172,11 @@ pub async fn configure_and_initialize_node( ) -> Result { let result = match &app_config.base_node.db_type { DatabaseType::Lmdb => { + let rules = ConsensusManager::builder(app_config.base_node.network).build(); let backend = create_lmdb_database( app_config.base_node.lmdb_path.as_path(), app_config.base_node.lmdb.clone(), + rules, ) .map_err(|e| ExitError::new(ExitCode::DatabaseError, e))?; build_node_context(backend, app_config, node_identity, interrupt_signal).await? diff --git a/applications/tari_base_node/src/cli.rs b/applications/tari_base_node/src/cli.rs index 3c129b274e..f5e7097851 100644 --- a/applications/tari_base_node/src/cli.rs +++ b/applications/tari_base_node/src/cli.rs @@ -28,7 +28,7 @@ use tari_common::configuration::{ConfigOverrideProvider, Network}; #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] #[allow(clippy::struct_excessive_bools)] -pub(crate) struct Cli { +pub struct Cli { #[clap(flatten)] pub common: CommonCliArgs, /// Create a default configuration file if it doesn't exist diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index a17e80722c..626d942f8a 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -33,7 +33,7 @@ use tari_app_grpc::{ tari_rpc::{CalcType, Sorting}, }; use tari_app_utilities::consts; -use tari_common_types::types::{Commitment, Signature}; +use tari_common_types::types::{Commitment, FixedHash, PublicKey, Signature}; use tari_comms::{Bytes, CommsNode}; use tari_core::{ base_node::{ @@ -135,10 +135,13 @@ impl BaseNodeGrpcServer {} #[tonic::async_trait] impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { type FetchMatchingUtxosStream = mpsc::Receiver>; + type GetActiveValidatorNodesStream = mpsc::Receiver>; type GetBlocksStream = mpsc::Receiver>; type GetMempoolTransactionsStream = mpsc::Receiver>; type GetNetworkDifficultyStream = mpsc::Receiver>; type GetPeersStream = mpsc::Receiver>; + type GetSideChainUtxosStream = mpsc::Receiver>; + type GetTemplateRegistrationsStream = mpsc::Receiver>; type GetTokensInCirculationStream = mpsc::Receiver>; type ListHeadersStream = mpsc::Receiver>; type SearchKernelsStream = mpsc::Receiver>; @@ -1120,14 +1123,19 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { async fn get_constants( &self, - _request: Request, + request: Request, ) -> Result, Status> { debug!(target: LOG_TARGET, "Incoming GRPC request for GetConstants",); debug!(target: LOG_TARGET, "Sending GetConstants response to client"); - // TODO: Switch to request height - Ok(Response::new( - self.network.create_consensus_constants().pop().unwrap().into(), - )) + + let block_height = request.into_inner().block_height; + + let consensus_manager = ConsensusManager::builder(self.network.as_network()).build(); + let consensus_constants = consensus_manager.consensus_constants(block_height); + + Ok(Response::new(tari_rpc::ConsensusConstants::from( + consensus_constants.clone(), + ))) } async fn get_block_size( @@ -1428,6 +1436,280 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { Ok(Response::new(response)) } + + async fn get_shard_key( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let report_error_flag = self.report_error_flag(); + let mut handler = self.node_service.clone(); + let public_key = PublicKey::from_bytes(&request.public_key) + .map_err(|e| obscure_error_if_true(report_error_flag, Status::invalid_argument(e.to_string())))?; + + let shard_key = handler.get_shard_key(request.height, public_key).await.map_err(|e| { + error!(target: LOG_TARGET, "Error {}", e); + obscure_error_if_true(report_error_flag, Status::internal(e.to_string())) + })?; + if let Some(shard_key) = shard_key { + Ok(Response::new(tari_rpc::GetShardKeyResponse { + shard_key: shard_key.to_vec(), + found: true, + })) + } else { + Ok(Response::new(tari_rpc::GetShardKeyResponse { + shard_key: vec![], + found: false, + })) + } + } + + async fn get_active_validator_nodes( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + debug!(target: LOG_TARGET, "Incoming GRPC request for GetActiveValidatorNodes"); + + let mut handler = self.node_service.clone(); + let (mut tx, rx) = mpsc::channel(1000); + + task::spawn(async move { + let active_validator_nodes = match handler.get_active_validator_nodes(request.height).await { + Err(err) => { + warn!(target: LOG_TARGET, "Base node service error: {}", err,); + return; + }, + Ok(data) => data, + }; + + for (public_key, shard_key) in active_validator_nodes { + let active_validator_node = tari_rpc::GetActiveValidatorNodesResponse { + public_key: public_key.to_vec(), + shard_key: shard_key.to_vec(), + }; + + if tx.send(Ok(active_validator_node)).await.is_err() { + debug!( + target: LOG_TARGET, + "[get_active_validator_nodes] Client has disconnected before stream completed" + ); + return; + } + } + }); + debug!( + target: LOG_TARGET, + "Sending GetActiveValidatorNodes response stream to client" + ); + Ok(Response::new(rx)) + } + + async fn get_template_registrations( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let report_error_flag = self.report_error_flag(); + debug!(target: LOG_TARGET, "Incoming GRPC request for GetTemplateRegistrations"); + + let (mut tx, rx) = mpsc::channel(10); + + let start_hash = Some(request.start_hash) + .filter(|x| !x.is_empty()) + .map(FixedHash::try_from) + .transpose() + .map_err(|_| Status::invalid_argument("Invalid start_hash"))?; + + let mut node_service = self.node_service.clone(); + + let start_height = match start_hash { + Some(hash) => { + let header = node_service + .get_header_by_hash(hash) + .await + .map_err(|err| obscure_error_if_true(self.report_grpc_error, Status::internal(err.to_string())))?; + header + .map(|h| h.height()) + .ok_or_else(|| Status::not_found("Start hash not found"))? + }, + None => 0, + }; + + if request.count == 0 { + return Ok(Response::new(rx)); + } + + let end_height = start_height + .checked_add(request.count) + .ok_or_else(|| Status::invalid_argument("Request start height + count overflows u64"))?; + + task::spawn(async move { + let template_registrations = match node_service.get_template_registrations(start_height, end_height).await { + Err(err) => { + warn!(target: LOG_TARGET, "Base node service error: {}", err); + return; + }, + Ok(data) => data, + }; + + for template_registration in template_registrations { + let registration = match template_registration.registration_data.try_into() { + Ok(t) => t, + Err(e) => { + warn!( + target: LOG_TARGET, + "Error sending converting template registration for GRPC: {}", e + ); + let _ignore = tx + .send(Err(obscure_error_if_true( + report_error_flag, + Status::internal(format!("Error converting template_registration: {}", e)), + ))) + .await; + return; + }, + }; + + let resp = tari_rpc::GetTemplateRegistrationResponse { + utxo_hash: template_registration.output_hash.to_vec(), + registration: Some(registration), + }; + + if tx.send(Ok(resp)).await.is_err() { + debug!( + target: LOG_TARGET, + "[get_template_registrations] Client has disconnected before stream completed" + ); + return; + } + } + }); + debug!( + target: LOG_TARGET, + "Sending GetTemplateRegistrations response stream to client" + ); + Ok(Response::new(rx)) + } + + async fn get_side_chain_utxos( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let report_error_flag = self.report_error_flag(); + debug!(target: LOG_TARGET, "Incoming GRPC request for GetTemplateRegistrations"); + + let (mut tx, rx) = mpsc::channel(10); + + let start_hash = Some(request.start_hash) + .filter(|x| !x.is_empty()) + .map(FixedHash::try_from) + .transpose() + .map_err(|_| Status::invalid_argument("Invalid start_hash"))?; + + let mut node_service = self.node_service.clone(); + + let start_header = match start_hash { + Some(hash) => node_service + .get_header_by_hash(hash) + .await + .map_err(|err| obscure_error_if_true(self.report_grpc_error, Status::internal(err.to_string())))? + .ok_or_else(|| Status::not_found("Start hash not found"))?, + None => node_service + .get_header(0) + .await + .map_err(|err| obscure_error_if_true(self.report_grpc_error, Status::internal(err.to_string())))? + .ok_or_else(|| Status::unavailable("Genesis block not available"))?, + }; + + if request.count == 0 { + return Ok(Response::new(rx)); + } + + let start_height = start_header.height(); + let end_height = start_height + .checked_add(request.count - 1) + .ok_or_else(|| Status::invalid_argument("Request start height + count overflows u64"))?; + + task::spawn(async move { + let mut current_header = start_header; + + for height in start_height..=end_height { + let header_hash = *current_header.hash(); + let utxos = match node_service.fetch_unspent_utxos_in_block(header_hash).await { + Ok(utxos) => utxos, + Err(e) => { + warn!(target: LOG_TARGET, "Base node service error: {}", e); + return; + }, + }; + + let next_header = match node_service.get_header(height + 1).await { + Ok(h) => h, + Err(e) => { + let _ignore = tx.send(Err(obscure_error_if_true( + report_error_flag, + Status::internal(e.to_string()), + ))); + return; + }, + }; + + let sidechain_outputs = utxos + .into_iter() + .filter(|u| u.features.output_type.is_sidechain_type()) + .map(TryInto::try_into) + .collect::, _>>(); + + match sidechain_outputs { + Ok(outputs) => { + let resp = tari_rpc::GetSideChainUtxosResponse { + block_info: Some(tari_rpc::BlockInfo { + height: current_header.height(), + hash: header_hash.to_vec(), + next_block_hash: next_header.as_ref().map(|h| h.hash().to_vec()).unwrap_or_default(), + }), + outputs, + }; + + if tx.send(Ok(resp)).await.is_err() { + debug!( + target: LOG_TARGET, + "[get_template_registrations] Client has disconnected before stream completed" + ); + return; + } + }, + Err(e) => { + warn!( + target: LOG_TARGET, + "Error sending converting sidechain output for GRPC: {}", e + ); + let _ignore = tx + .send(Err(obscure_error_if_true( + report_error_flag, + Status::internal(format!("Error converting sidechain output: {}", e)), + ))) + .await; + return; + }, + }; + + match next_header { + Some(header) => { + current_header = header; + }, + None => break, + } + } + }); + debug!( + target: LOG_TARGET, + "Sending GetTemplateRegistrations response stream to client" + ); + Ok(Response::new(rx)) + } } enum BlockGroupType { diff --git a/applications/tari_base_node/src/lib.rs b/applications/tari_base_node/src/lib.rs new file mode 100644 index 0000000000..5c62b17cab --- /dev/null +++ b/applications/tari_base_node/src/lib.rs @@ -0,0 +1,176 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[macro_use] +mod table; + +mod bootstrap; +mod builder; +pub mod cli; +mod commands; +pub mod config; +mod grpc; +#[cfg(feature = "metrics")] +mod metrics; +mod recovery; +mod utils; + +use std::{process, sync::Arc}; + +use commands::{cli_loop::CliLoop, command::CommandContext}; +use futures::FutureExt; +use log::*; +use tari_app_utilities::common_cli_args::CommonCliArgs; +use tari_common::{ + configuration::bootstrap::{grpc_default_port, ApplicationType}, + exit_codes::{ExitCode, ExitError}, +}; +use tari_comms::{multiaddr::Multiaddr, utils::multiaddr::multiaddr_to_socketaddr, NodeIdentity}; +use tari_shutdown::{Shutdown, ShutdownSignal}; +use tokio::task; +use tonic::transport::Server; + +use crate::cli::Cli; +pub use crate::{ + config::{ApplicationConfig, BaseNodeConfig, DatabaseType}, + metrics::MetricsConfig, +}; + +const LOG_TARGET: &str = "tari::base_node::app"; + +pub async fn run_base_node(node_identity: Arc, config: Arc) -> Result<(), ExitError> { + let shutdown = Shutdown::new(); + + let data_dir = config.base_node.data_dir.clone(); + let data_dir_str = data_dir.clone().into_os_string().into_string().unwrap(); + + let mut config_path = data_dir.clone(); + config_path.push("config.toml"); + + let cli = Cli { + common: CommonCliArgs { + base_path: data_dir_str, + config: config_path.into_os_string().into_string().unwrap(), + log_config: None, + log_level: None, + config_property_overrides: vec![], + }, + init: true, + rebuild_db: false, + non_interactive_mode: true, + watch: None, + network: None, + }; + + run_base_node_with_cli(node_identity, config, cli, shutdown).await +} + +/// Sets up the base node and runs the cli_loop +pub async fn run_base_node_with_cli( + node_identity: Arc, + config: Arc, + cli: Cli, + shutdown: Shutdown, +) -> Result<(), ExitError> { + #[cfg(feature = "metrics")] + { + metrics::install( + ApplicationType::BaseNode, + &node_identity, + &config.metrics, + shutdown.to_signal(), + ); + } + + log_mdc::insert("node-public-key", node_identity.public_key().to_string()); + log_mdc::insert("node-id", node_identity.node_id().to_string()); + + if cli.rebuild_db { + info!(target: LOG_TARGET, "Node is in recovery mode, entering recovery"); + recovery::initiate_recover_db(&config.base_node)?; + recovery::run_recovery(&config.base_node) + .await + .map_err(|e| ExitError::new(ExitCode::RecoveryError, e))?; + return Ok(()); + }; + + // Build, node, build! + let ctx = builder::configure_and_initialize_node(config.clone(), node_identity, shutdown.to_signal()).await?; + + if config.base_node.grpc_enabled { + let grpc_address = config.base_node.grpc_address.clone().unwrap_or_else(|| { + let port = grpc_default_port(ApplicationType::BaseNode, config.base_node.network); + format!("/ip4/127.0.0.1/tcp/{}", port).parse().unwrap() + }); + // Go, GRPC, go go + let grpc = grpc::base_node_grpc_server::BaseNodeGrpcServer::from_base_node_context(&ctx); + task::spawn(run_grpc(grpc, grpc_address, shutdown.to_signal())); + } + + // Run, node, run! + let context = CommandContext::new(&ctx, shutdown); + let main_loop = CliLoop::new(context, cli.watch, cli.non_interactive_mode); + if cli.non_interactive_mode { + println!("Node started in non-interactive mode (pid = {})", process::id()); + } else { + info!( + target: LOG_TARGET, + "Node has been successfully configured and initialized. Starting CLI loop." + ); + } + if !config.base_node.force_sync_peers.is_empty() { + warn!( + target: LOG_TARGET, + "Force Sync Peers have been set! This node will only sync to the nodes in this set." + ); + } + + info!(target: LOG_TARGET, "Tari base node has STARTED"); + main_loop.cli_loop().await; + + ctx.wait_for_shutdown().await; + + println!("Goodbye!"); + Ok(()) +} + +/// Runs the gRPC server +async fn run_grpc( + grpc: grpc::base_node_grpc_server::BaseNodeGrpcServer, + grpc_address: Multiaddr, + interrupt_signal: ShutdownSignal, +) -> Result<(), anyhow::Error> { + info!(target: LOG_TARGET, "Starting GRPC on {}", grpc_address); + + let grpc_address = multiaddr_to_socketaddr(&grpc_address)?; + Server::builder() + .add_service(tari_app_grpc::tari_rpc::base_node_server::BaseNodeServer::new(grpc)) + .serve_with_shutdown(grpc_address, interrupt_signal.map(|_| ())) + .await + .map_err(|err| { + error!(target: LOG_TARGET, "GRPC encountered an error: {:?}", err); + err + })?; + + info!(target: LOG_TARGET, "Stopping GRPC"); + Ok(()) +} diff --git a/applications/tari_base_node/src/main.rs b/applications/tari_base_node/src/main.rs index 87bb421427..d1b4bbc9b9 100644 --- a/applications/tari_base_node/src/main.rs +++ b/applications/tari_base_node/src/main.rs @@ -53,15 +53,15 @@ /// /// `help` - Displays a list of commands /// `get-balance` - Displays the balance of the wallet (available, pending incoming, pending outgoing) -/// `send-tari` - Sends Tari, the amount needs to be specified, followed by the destination (public key or emoji id) and -/// an optional message `get-chain-metadata` - Lists information about the blockchain of this Base Node +/// `send-tari` - Sends Tari, the amount needs to be specified, followed by the destination (public key or emoji +/// id) and an optional message `get-chain-metadata` - Lists information about the blockchain of this Base Node /// `list-peers` - Lists information about peers known by this base node /// `ban-peer` - Bans a peer /// `unban-peer` - Removes a ban for a peer /// `list-connections` - Lists active connections to this Base Node -/// `list-headers` - Lists header information. Either the first header height and the last header height needs to be -/// specified, or the amount of headers from the top `check-db` - Checks the blockchain database for missing blocks and -/// headers `calc-timing` - Calculates the time average time taken to mine a given range of blocks +/// `list-headers` - Lists header information. Either the first header height and the last header height needs to +/// be specified, or the amount of headers from the top `check-db` - Checks the blockchain database for missing +/// blocks and headers `calc-timing` - Calculates the time average time taken to mine a given range of blocks /// `discover-peer` - Attempts to discover a peer on the network, a public key or emoji id needs to be specified /// `get-block` - Retrieves a block, the height of the block needs to be specified /// `get-mempool-stats` - Displays information about the mempool @@ -69,48 +69,18 @@ /// `whoami` - Displays identity information about this Base Node and it's wallet /// `quit` - Exits the Base Node /// `exit` - Same as quit - -/// Used to display tabulated data -#[macro_use] -mod table; - -mod bootstrap; -mod builder; -mod cli; -mod commands; -mod config; -mod grpc; -#[cfg(feature = "metrics")] -mod metrics; -mod recovery; -mod utils; - use std::{process, sync::Arc}; use clap::Parser; -use commands::{cli_loop::CliLoop, command::CommandContext}; -use futures::FutureExt; use log::*; use tari_app_utilities::{identity_management::setup_node_identity, utilities::setup_runtime}; -use tari_common::{ - configuration::bootstrap::{grpc_default_port, ApplicationType}, - exit_codes::{ExitCode, ExitError}, - initialize_logging, - load_configuration, -}; -use tari_comms::{ - multiaddr::Multiaddr, - peer_manager::PeerFeatures, - utils::multiaddr::multiaddr_to_socketaddr, - NodeIdentity, -}; +use tari_base_node::{cli::Cli, run_base_node_with_cli, ApplicationConfig}; +use tari_common::{exit_codes::ExitError, initialize_logging, load_configuration}; +use tari_comms::peer_manager::PeerFeatures; #[cfg(all(unix, feature = "libtor"))] use tari_libtor::tor::Tor; -use tari_shutdown::{Shutdown, ShutdownSignal}; -use tokio::task; -use tonic::transport::Server; +use tari_shutdown::Shutdown; -use crate::{cli::Cli, config::ApplicationConfig}; const LOG_TARGET: &str = "tari::base_node::app"; /// Application entry point @@ -181,98 +151,7 @@ fn main_inner() -> Result<(), ExitError> { } // Run the base node - runtime.block_on(run_node(node_identity, Arc::new(config), cli, shutdown))?; - - Ok(()) -} - -/// Sets up the base node and runs the cli_loop -async fn run_node( - node_identity: Arc, - config: Arc, - cli: Cli, - shutdown: Shutdown, -) -> Result<(), ExitError> { - #[cfg(feature = "metrics")] - { - metrics::install( - ApplicationType::BaseNode, - &node_identity, - &config.metrics, - shutdown.to_signal(), - ); - } - - log_mdc::insert("node-public-key", node_identity.public_key().to_string()); - log_mdc::insert("node-id", node_identity.node_id().to_string()); - - if cli.rebuild_db { - info!(target: LOG_TARGET, "Node is in recovery mode, entering recovery"); - recovery::initiate_recover_db(&config.base_node)?; - recovery::run_recovery(&config.base_node) - .await - .map_err(|e| ExitError::new(ExitCode::RecoveryError, e))?; - return Ok(()); - }; - - // Build, node, build! - let ctx = builder::configure_and_initialize_node(config.clone(), node_identity, shutdown.to_signal()).await?; - - if config.base_node.grpc_enabled { - let grpc_address = config.base_node.grpc_address.clone().unwrap_or_else(|| { - let port = grpc_default_port(ApplicationType::BaseNode, config.base_node.network); - format!("/ip4/127.0.0.1/tcp/{}", port).parse().unwrap() - }); - // Go, GRPC, go go - let grpc = grpc::base_node_grpc_server::BaseNodeGrpcServer::from_base_node_context(&ctx); - task::spawn(run_grpc(grpc, grpc_address, shutdown.to_signal())); - } - - // Run, node, run! - let context = CommandContext::new(&ctx, shutdown); - let main_loop = CliLoop::new(context, cli.watch, cli.non_interactive_mode); - if cli.non_interactive_mode { - println!("Node started in non-interactive mode (pid = {})", process::id()); - } else { - info!( - target: LOG_TARGET, - "Node has been successfully configured and initialized. Starting CLI loop." - ); - } - if !config.base_node.force_sync_peers.is_empty() { - warn!( - target: LOG_TARGET, - "Force Sync Peers have been set! This node will only sync to the nodes in this set." - ); - } - - info!(target: LOG_TARGET, "Tari base node has STARTED"); - main_loop.cli_loop().await; - - ctx.wait_for_shutdown().await; - - println!("Goodbye!"); - Ok(()) -} - -/// Runs the gRPC server -async fn run_grpc( - grpc: grpc::base_node_grpc_server::BaseNodeGrpcServer, - grpc_address: Multiaddr, - interrupt_signal: ShutdownSignal, -) -> Result<(), anyhow::Error> { - info!(target: LOG_TARGET, "Starting GRPC on {}", grpc_address); - - let grpc_address = multiaddr_to_socketaddr(&grpc_address)?; - Server::builder() - .add_service(tari_app_grpc::tari_rpc::base_node_server::BaseNodeServer::new(grpc)) - .serve_with_shutdown(grpc_address, interrupt_signal.map(|_| ())) - .await - .map_err(|err| { - error!(target: LOG_TARGET, "GRPC encountered an error: {:?}", err); - err - })?; + runtime.block_on(run_base_node_with_cli(node_identity, Arc::new(config), cli, shutdown))?; - info!(target: LOG_TARGET, "Stopping GRPC"); Ok(()) } diff --git a/applications/tari_base_node/src/recovery.rs b/applications/tari_base_node/src/recovery.rs index 3ab23c7705..7563635760 100644 --- a/applications/tari_base_node/src/recovery.rs +++ b/applications/tari_base_node/src/recovery.rs @@ -55,7 +55,7 @@ use tari_core::{ }, }; -use crate::config::{BaseNodeConfig, DatabaseType}; +use crate::{BaseNodeConfig, DatabaseType}; pub const LOG_TARGET: &str = "base_node::app"; @@ -74,22 +74,23 @@ pub fn initiate_recover_db(config: &BaseNodeConfig) -> Result<(), ExitError> { pub async fn run_recovery(node_config: &BaseNodeConfig) -> Result<(), anyhow::Error> { println!("Starting recovery mode"); + let rules = ConsensusManager::builder(node_config.network).build(); let (temp_db, main_db, temp_path) = match &node_config.db_type { DatabaseType::Lmdb => { - let backend = create_lmdb_database(&node_config.lmdb_path, node_config.lmdb.clone()).map_err(|e| { - error!(target: LOG_TARGET, "Error opening db: {}", e); - anyhow!("Could not open DB: {}", e) - })?; + let backend = create_lmdb_database(&node_config.lmdb_path, node_config.lmdb.clone(), rules.clone()) + .map_err(|e| { + error!(target: LOG_TARGET, "Error opening db: {}", e); + anyhow!("Could not open DB: {}", e) + })?; let temp_path = temp_dir().join("temp_recovery"); - let temp = create_lmdb_database(&temp_path, node_config.lmdb.clone()).map_err(|e| { + let temp = create_lmdb_database(&temp_path, node_config.lmdb.clone(), rules.clone()).map_err(|e| { error!(target: LOG_TARGET, "Error opening recovery db: {}", e); anyhow!("Could not open recovery DB: {}", e) })?; (temp, backend, temp_path) }, }; - let rules = ConsensusManager::builder(node_config.network).build(); let factories = CryptoFactories::default(); let randomx_factory = RandomXFactory::new(node_config.max_randomx_vms); let validators = Validators::new( diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index e3461968a6..cab0fe61f2 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -41,7 +41,7 @@ use tari_app_grpc::authentication::salted_password::create_salted_hashed_passwor use tari_common_types::{ emoji::EmojiId, transaction::TxId, - types::{CommitmentFactory, FixedHash, PublicKey}, + types::{CommitmentFactory, FixedHash, PublicKey, Signature}, }; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, @@ -53,6 +53,7 @@ use tari_core::transactions::{ tari_amount::{uT, MicroTari, Tari}, transaction_components::{OutputFeatures, TransactionOutput, UnblindedOutput}, }; +use tari_crypto::ristretto::RistrettoSecretKey; use tari_utilities::{hex::Hex, ByteArray}; use tari_wallet::{ connectivity_service::WalletConnectivityInterface, @@ -190,6 +191,26 @@ pub async fn claim_htlc_refund( Ok(tx_id) } +pub async fn register_validator_node( + mut wallet_transaction_service: TransactionServiceHandle, + validator_node_public_key: PublicKey, + validator_node_signature: Signature, + selection_criteria: UtxoSelectionCriteria, + fee_per_gram: MicroTari, + message: String, +) -> Result { + wallet_transaction_service + .register_validator_node( + validator_node_public_key, + validator_node_signature, + selection_criteria, + fee_per_gram, + message, + ) + .await + .map_err(CommandError::TransactionServiceError) +} + /// Send a one-sided transaction to a recipient pub async fn send_one_sided( mut wallet_transaction_service: TransactionServiceHandle, @@ -947,6 +968,22 @@ pub async fn command_runner( Err(e) => eprintln!("HashGrpcPassword error! {}", e), } }, + RegisterValidatorNode(args) => { + let tx_id = register_validator_node( + transaction_service.clone(), + args.validator_node_public_key.into(), + Signature::new( + args.validator_node_public_nonce.into(), + RistrettoSecretKey::from_vec(&args.validator_node_signature).unwrap(), + ), + UtxoSelectionCriteria::default(), + config.fee_per_gram * uT, + args.message, + ) + .await?; + debug!(target: LOG_TARGET, "Registering VN tx_id {}", tx_id); + tx_ids.push(tx_id); + }, } } diff --git a/applications/tari_console_wallet/src/cli.rs b/applications/tari_console_wallet/src/cli.rs index 9319e2e471..0b1efc2937 100644 --- a/applications/tari_console_wallet/src/cli.rs +++ b/applications/tari_console_wallet/src/cli.rs @@ -41,7 +41,7 @@ use tari_utilities::{ #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] #[allow(clippy::struct_excessive_bools)] -pub(crate) struct Cli { +pub struct Cli { #[clap(flatten)] pub common: CommonCliArgs, /// Supply the password for the console wallet. It's very bad security practice to provide the password on the @@ -131,6 +131,7 @@ pub enum CliCommands { ClaimShaAtomicSwapRefund(ClaimShaAtomicSwapRefundArgs), RevalidateWalletDb, HashGrpcPassword(HashPasswordArgs), + RegisterValidatorNode(RegisterValidatorNodeArgs), } #[derive(Debug, Args, Clone)] @@ -266,3 +267,12 @@ pub struct HashPasswordArgs { /// If true, only output the hashed password and the salted password. Otherwise a usage explanation is output. pub short: bool, } + +#[derive(Debug, Args, Clone)] +pub struct RegisterValidatorNodeArgs { + pub validator_node_public_key: UniPublicKey, + pub validator_node_public_nonce: UniPublicKey, + pub validator_node_signature: Vec, + #[clap(short, long, default_value = "Registering VN")] + pub message: String, +} diff --git a/applications/tari_console_wallet/src/config.rs b/applications/tari_console_wallet/src/config.rs index 9deff44fec..f1ac8d2a9d 100644 --- a/applications/tari_console_wallet/src/config.rs +++ b/applications/tari_console_wallet/src/config.rs @@ -20,12 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#![allow(dead_code, unused)] + use config::Config; use tari_app_utilities::consts; use tari_common::{configuration::CommonConfig, ConfigurationError, DefaultConfigLoader}; use tari_p2p::{auto_update::AutoUpdateConfig, PeerSeedsConfig}; use tari_wallet::WalletConfig; +#[derive(Clone)] pub struct ApplicationConfig { pub common: CommonConfig, pub auto_update: AutoUpdateConfig, diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index 43aa77c19e..80c3b91ddb 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -43,6 +43,8 @@ use tari_app_grpc::{ CoinSplitResponse, CreateBurnTransactionRequest, CreateBurnTransactionResponse, + CreateTemplateRegistrationRequest, + CreateTemplateRegistrationResponse, GetBalanceRequest, GetBalanceResponse, GetCoinbaseRequest, @@ -59,6 +61,8 @@ use tari_app_grpc::{ GetVersionResponse, ImportUtxosRequest, ImportUtxosResponse, + RegisterValidatorNodeRequest, + RegisterValidatorNodeResponse, RevalidateRequest, RevalidateResponse, SendShaAtomicSwapRequest, @@ -82,8 +86,8 @@ use tari_common_types::{ }; use tari_comms::{multiaddr::Multiaddr, types::CommsPublicKey, CommsNode}; use tari_core::transactions::{ - tari_amount::MicroTari, - transaction_components::{OutputFeatures, UnblindedOutput}, + tari_amount::{MicroTari, T}, + transaction_components::{CodeTemplateRegistration, OutputFeatures, OutputType, SideChainFeature, UnblindedOutput}, }; use tari_utilities::{hex::Hex, ByteArray}; use tari_wallet::{ @@ -881,6 +885,102 @@ impl wallet_server::Wallet for WalletGrpcServer { }, } } + + async fn create_template_registration( + &self, + request: Request, + ) -> Result, Status> { + let mut output_manager = self.wallet.output_manager_service.clone(); + let mut transaction_service = self.wallet.transaction_service.clone(); + let message = request.into_inner(); + + let template_registration = CodeTemplateRegistration::try_from( + message + .template_registration + .ok_or_else(|| Status::invalid_argument("template_registration is empty"))?, + ) + .map_err(|e| Status::invalid_argument(format!("template_registration is invalid: {}", e)))?; + let fee_per_gram = message.fee_per_gram; + + let message = format!("Template registration {}", template_registration.template_name); + let output = output_manager + .create_output_with_features(1 * T, OutputFeatures { + output_type: OutputType::CodeTemplateRegistration, + sidechain_feature: Some(SideChainFeature::TemplateRegistration(template_registration)), + ..Default::default() + }) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + let (tx_id, transaction) = output_manager + .create_send_to_self_with_output(vec![output], fee_per_gram.into(), UtxoSelectionCriteria::default()) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + debug!( + target: LOG_TARGET, + "Template registration transaction: {:?}", transaction + ); + + let reg_output = transaction + .body + .outputs() + .iter() + .find(|o| o.features.output_type == OutputType::CodeTemplateRegistration) + .unwrap(); + let template_address = reg_output.hash(); + + transaction_service + .submit_transaction(tx_id, transaction, 0.into(), message) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + Ok(Response::new(CreateTemplateRegistrationResponse { + tx_id: tx_id.as_u64(), + template_address: template_address.to_vec(), + })) + } + + async fn register_validator_node( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let mut transaction_service = self.get_transaction_service(); + let validator_node_public_key = CommsPublicKey::from_bytes(&request.validator_node_public_key) + .map_err(|_| Status::internal("Destination address is malformed".to_string()))?; + let validator_node_signature = request + .validator_node_signature + .ok_or_else(|| Status::invalid_argument("Validator node signature is missing!"))? + .try_into() + .unwrap(); + + let response = match transaction_service + .register_validator_node( + validator_node_public_key, + validator_node_signature, + UtxoSelectionCriteria::default(), + request.fee_per_gram.into(), + request.message, + ) + .await + { + Ok(tx) => RegisterValidatorNodeResponse { + transaction_id: tx.as_u64(), + is_success: true, + failure_message: Default::default(), + }, + Err(e) => { + error!(target: LOG_TARGET, "Transaction service error: {}", e); + RegisterValidatorNodeResponse { + transaction_id: Default::default(), + is_success: false, + failure_message: e.to_string(), + } + }, + }; + Ok(Response::new(response)) + } } async fn handle_completed_tx( diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index f00b788e31..d282bd7134 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#![allow(dead_code, unused)] + use std::{fs, path::PathBuf, str::FromStr, sync::Arc}; use log::*; diff --git a/applications/tari_console_wallet/src/lib.rs b/applications/tari_console_wallet/src/lib.rs new file mode 100644 index 0000000000..9de5b00972 --- /dev/null +++ b/applications/tari_console_wallet/src/lib.rs @@ -0,0 +1,231 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod automation; +mod cli; +mod config; +mod grpc; +mod init; +mod notifier; +mod recovery; +mod ui; +mod utils; +mod wallet_modes; + +pub use cli::Cli; +use init::{ + boot, + change_password, + get_base_node_peer_config, + init_wallet, + start_wallet, + tari_splash_screen, + WalletBoot, +}; +use log::*; +use recovery::{get_seed_from_seed_words, prompt_private_key_from_seed_words}; +use tari_app_utilities::{common_cli_args::CommonCliArgs, consts}; +use tari_common::{ + configuration::bootstrap::ApplicationType, + exit_codes::{ExitCode, ExitError}, +}; +use tari_key_manager::cipher_seed::CipherSeed; +#[cfg(all(unix, feature = "libtor"))] +use tari_libtor::tor::Tor; +use tari_shutdown::Shutdown; +use tari_utilities::SafePassword; +use tokio::runtime::Runtime; +use wallet_modes::{command_mode, grpc_mode, recovery_mode, script_mode, tui_mode, WalletMode}; + +pub use crate::config::ApplicationConfig; +use crate::init::wallet_mode; + +pub const LOG_TARGET: &str = "wallet::console_wallet::main"; + +pub fn run_wallet(runtime: Runtime, config: &mut ApplicationConfig) -> Result<(), ExitError> { + let data_dir = config.wallet.data_dir.clone(); + let data_dir_str = data_dir.clone().into_os_string().into_string().unwrap(); + + let mut config_path = data_dir; + config_path.push("config.toml"); + + let cli = Cli { + common: CommonCliArgs { + base_path: data_dir_str, + config: config_path.into_os_string().into_string().unwrap(), + log_config: None, + log_level: None, + config_property_overrides: vec![], + }, + password: None, + change_password: false, + recovery: false, + seed_words: None, + seed_words_file_name: None, + non_interactive_mode: true, + input_file: None, + command: None, + wallet_notify: None, + command_mode_auto_exit: false, + network: None, + grpc_enabled: true, + grpc_address: None, + command2: None, + }; + + run_wallet_with_cli(runtime, config, cli) +} + +pub fn run_wallet_with_cli(runtime: Runtime, config: &mut ApplicationConfig, cli: Cli) -> Result<(), ExitError> { + info!( + target: LOG_TARGET, + "== {} ({}) ==", + ApplicationType::ConsoleWallet, + consts::APP_VERSION + ); + + let password = get_password(config, &cli); + + if password.is_none() { + tari_splash_screen("Console Wallet"); + } + + // check for recovery based on existence of wallet file + let mut boot_mode = boot(&cli, &config.wallet)?; + + let recovery_seed = get_recovery_seed(boot_mode, &cli)?; + + // get command line password if provided + let seed_words_file_name = cli.seed_words_file_name.clone(); + + let mut shutdown = Shutdown::new(); + let shutdown_signal = shutdown.to_signal(); + + if cli.change_password { + info!(target: LOG_TARGET, "Change password requested."); + return runtime.block_on(change_password( + config, + password, + shutdown_signal, + cli.non_interactive_mode, + )); + } + + // Run our own Tor instance, if configured + // This is currently only possible on linux/macos + #[cfg(all(unix, feature = "libtor"))] + if config.wallet.use_libtor && config.wallet.p2p.transport.is_tor() { + let tor = Tor::initialize()?; + tor.update_comms_transport(&mut config.wallet.p2p.transport)?; + runtime.spawn(tor.run(shutdown.to_signal())); + debug!( + target: LOG_TARGET, + "Updated Tor comms transport: {:?}", config.wallet.p2p.transport + ); + } + + // initialize wallet + let mut wallet = runtime.block_on(init_wallet( + config, + password, + seed_words_file_name, + recovery_seed, + shutdown_signal, + cli.non_interactive_mode, + ))?; + + // Check if there is an in progress recovery in the wallet's database + if wallet.is_recovery_in_progress()? { + println!("A Wallet Recovery was found to be in progress, continuing."); + boot_mode = WalletBoot::Recovery; + } + + // get base node/s + let base_node_config = + runtime.block_on(get_base_node_peer_config(config, &mut wallet, cli.non_interactive_mode))?; + let base_node_selected = base_node_config.get_base_node_peer()?; + + let wallet_mode = wallet_mode(&cli, boot_mode); + + // start wallet + runtime.block_on(start_wallet(&mut wallet, &base_node_selected, &wallet_mode))?; + + debug!(target: LOG_TARGET, "Starting app"); + + let handle = runtime.handle().clone(); + + let result = match wallet_mode { + WalletMode::Tui => tui_mode(handle, &config.wallet, &base_node_config, wallet.clone()), + WalletMode::Grpc => grpc_mode(handle, &config.wallet, wallet.clone()), + WalletMode::Script(path) => script_mode(handle, &cli, &config.wallet, &base_node_config, wallet.clone(), path), + WalletMode::Command(command) => command_mode( + handle, + &cli, + &config.wallet, + &base_node_config, + wallet.clone(), + *command, + ), + + WalletMode::RecoveryDaemon | WalletMode::RecoveryTui => { + recovery_mode(handle, &base_node_config, &config.wallet, wallet_mode, wallet.clone()) + }, + WalletMode::Invalid => Err(ExitError::new( + ExitCode::InputError, + "Invalid wallet mode - are you trying too many command options at once?", + )), + }; + + print!("\nShutting down wallet... "); + shutdown.trigger(); + runtime.block_on(wallet.wait_until_shutdown()); + println!("Done."); + + result +} + +fn get_password(config: &ApplicationConfig, cli: &Cli) -> Option { + cli.password + .as_ref() + .or(config.wallet.password.as_ref()) + .map(|s| s.to_owned()) +} + +fn get_recovery_seed(boot_mode: WalletBoot, cli: &Cli) -> Result, ExitError> { + if matches!(boot_mode, WalletBoot::Recovery) { + let seed = if cli.seed_words.is_some() { + let seed_words: Vec = cli + .seed_words + .clone() + .unwrap() + .split_whitespace() + .map(|v| v.to_string()) + .collect(); + get_seed_from_seed_words(seed_words)? + } else { + prompt_private_key_from_seed_words()? + }; + Ok(Some(seed)) + } else { + Ok(None) + } +} diff --git a/applications/tari_console_wallet/src/main.rs b/applications/tari_console_wallet/src/main.rs index 40a7a8d5a8..d00e8d11d2 100644 --- a/applications/tari_console_wallet/src/main.rs +++ b/applications/tari_console_wallet/src/main.rs @@ -23,33 +23,14 @@ use std::process; use clap::Parser; -use cli::Cli; -use init::{ - boot, - change_password, - get_base_node_peer_config, - init_wallet, - start_wallet, - tari_splash_screen, - WalletBoot, -}; use log::*; -use recovery::prompt_private_key_from_seed_words; -use tari_app_utilities::consts; use tari_common::{ configuration::bootstrap::{grpc_default_port, ApplicationType}, - exit_codes::{ExitCode, ExitError}, + exit_codes::ExitError, initialize_logging, load_configuration, }; -use tari_key_manager::cipher_seed::CipherSeed; -#[cfg(all(unix, feature = "libtor"))] -use tari_libtor::tor::Tor; -use tari_shutdown::Shutdown; -use tari_utilities::SafePassword; -use wallet_modes::{command_mode, grpc_mode, recovery_mode, script_mode, tui_mode, WalletMode}; - -use crate::{config::ApplicationConfig, init::wallet_mode, recovery::get_seed_from_seed_words}; +use tari_console_wallet::{run_wallet_with_cli, ApplicationConfig, Cli}; pub const LOG_TARGET: &str = "wallet::console_wallet::main"; @@ -106,141 +87,7 @@ fn main_inner() -> Result<(), ExitError> { .build() .expect("Failed to build a runtime!"); - info!( - target: LOG_TARGET, - "== {} ({}) ==", - ApplicationType::ConsoleWallet, - consts::APP_VERSION - ); - - let password = get_password(&config, &cli); - - if password.is_none() { - tari_splash_screen("Console Wallet"); - } - - // check for recovery based on existence of wallet file - let mut boot_mode = boot(&cli, &config.wallet)?; - - let recovery_seed = get_recovery_seed(boot_mode, &cli)?; - - // get command line password if provided - let seed_words_file_name = cli.seed_words_file_name.clone(); - - let mut shutdown = Shutdown::new(); - let shutdown_signal = shutdown.to_signal(); - - if cli.change_password { - info!(target: LOG_TARGET, "Change password requested."); - return runtime.block_on(change_password( - &config, - password, - shutdown_signal, - cli.non_interactive_mode, - )); - } - - // Run our own Tor instance, if configured - // This is currently only possible on linux/macos - #[cfg(all(unix, feature = "libtor"))] - if config.wallet.use_libtor && config.wallet.p2p.transport.is_tor() { - let tor = Tor::initialize()?; - tor.update_comms_transport(&mut config.wallet.p2p.transport)?; - runtime.spawn(tor.run(shutdown.to_signal())); - debug!( - target: LOG_TARGET, - "Updated Tor comms transport: {:?}", config.wallet.p2p.transport - ); - } - - // initialize wallet - let mut wallet = runtime.block_on(init_wallet( - &config, - password, - seed_words_file_name, - recovery_seed, - shutdown_signal, - cli.non_interactive_mode, - ))?; - - // Check if there is an in progress recovery in the wallet's database - if wallet.is_recovery_in_progress()? { - println!("A Wallet Recovery was found to be in progress, continuing."); - boot_mode = WalletBoot::Recovery; - } - - // get base node/s - let base_node_config = runtime.block_on(get_base_node_peer_config( - &config, - &mut wallet, - cli.non_interactive_mode, - ))?; - let base_node_selected = base_node_config.get_base_node_peer()?; - - let wallet_mode = wallet_mode(&cli, boot_mode); - - // start wallet - runtime.block_on(start_wallet(&mut wallet, &base_node_selected, &wallet_mode))?; - - debug!(target: LOG_TARGET, "Starting app"); - - let handle = runtime.handle().clone(); - - let result = match wallet_mode { - WalletMode::Tui => tui_mode(handle, &config.wallet, &base_node_config, wallet.clone()), - WalletMode::Grpc => grpc_mode(handle, &config.wallet, wallet.clone()), - WalletMode::Script(path) => script_mode(handle, &cli, &config.wallet, &base_node_config, wallet.clone(), path), - WalletMode::Command(command) => command_mode( - handle, - &cli, - &config.wallet, - &base_node_config, - wallet.clone(), - *command, - ), - - WalletMode::RecoveryDaemon | WalletMode::RecoveryTui => { - recovery_mode(handle, &base_node_config, &config.wallet, wallet_mode, wallet.clone()) - }, - WalletMode::Invalid => Err(ExitError::new( - ExitCode::InputError, - "Invalid wallet mode - are you trying too many command options at once?", - )), - }; - - print!("\nShutting down wallet... "); - shutdown.trigger(); - runtime.block_on(wallet.wait_until_shutdown()); - println!("Done."); - - result -} - -fn get_password(config: &ApplicationConfig, cli: &Cli) -> Option { - cli.password - .as_ref() - .or(config.wallet.password.as_ref()) - .map(|s| s.to_owned()) -} - -fn get_recovery_seed(boot_mode: WalletBoot, cli: &Cli) -> Result, ExitError> { - if matches!(boot_mode, WalletBoot::Recovery) { - let seed = if cli.seed_words.is_some() { - let seed_words: Vec = cli - .seed_words - .clone() - .unwrap() - .split_whitespace() - .map(|v| v.to_string()) - .collect(); - get_seed_from_seed_words(seed_words)? - } else { - prompt_private_key_from_seed_words()? - }; - Ok(Some(seed)) - } else { - Ok(None) - } + run_wallet_with_cli(runtime, &mut config, cli) } fn setup_grpc_config(config: &mut ApplicationConfig) { diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index 6b9a9f3a66..35e2df9ca4 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#![allow(dead_code, unused)] + use chrono::offset::Local; use futures::FutureExt; use log::*; diff --git a/applications/tari_console_wallet/src/utils/crossterm_events.rs b/applications/tari_console_wallet/src/utils/crossterm_events.rs index 98308e8fb3..954a9319f8 100644 --- a/applications/tari_console_wallet/src/utils/crossterm_events.rs +++ b/applications/tari_console_wallet/src/utils/crossterm_events.rs @@ -102,6 +102,12 @@ impl CrosstermEvents { } } +impl Default for CrosstermEvents { + fn default() -> Self { + Self::new() + } +} + impl EventStream for CrosstermEvents { fn next(&self) -> Result, mpsc::RecvError> { self.rx.recv() diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index e92437138b..a95f5c4888 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#![allow(dead_code, unused)] + use std::{fs, io::Stdout, path::PathBuf}; use clap::Parser; @@ -468,6 +470,7 @@ mod test { CliCommands::ClaimShaAtomicSwapRefund(_) => {}, CliCommands::RevalidateWalletDb => {}, CliCommands::HashGrpcPassword(_) => {}, + CliCommands::RegisterValidatorNode(_) => {}, } } assert!(get_balance && send_tari && burn_tari && make_it_rain && coin_split && discover_peer && whois); diff --git a/base_layer/common_types/src/types/fixed_hash.rs b/base_layer/common_types/src/types/fixed_hash.rs index efe9d3e078..43fc39da4a 100644 --- a/base_layer/common_types/src/types/fixed_hash.rs +++ b/base_layer/common_types/src/types/fixed_hash.rs @@ -104,6 +104,11 @@ impl PartialEq> for FixedHash { self == other.as_slice() } } +impl PartialEq for Vec { + fn eq(&self, other: &FixedHash) -> bool { + self == other.as_slice() + } +} impl AsRef<[u8]> for FixedHash { fn as_ref(&self) -> &[u8] { diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 203d0e75e1..e801170a26 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -10,10 +10,10 @@ version = "0.38.8" edition = "2018" [features] -default = ["croaring", "tari_mmr", "transactions", "base_node", "mempool_proto", "base_node_proto", "monero", "randomx-rs"] +default = ["base_node"] transactions = [] mempool_proto = [] -base_node = ["croaring", "tari_mmr", "transactions", "base_node_proto", "monero", "randomx-rs"] +base_node = ["croaring", "tari_mmr", "transactions", "mempool_proto", "base_node_proto", "monero", "randomx-rs"] base_node_proto = [] avx2 = ["tari_crypto/simd_backend"] benches = ["base_node", "criterion"] diff --git a/base_layer/core/src/base_node/comms_interface/comms_request.rs b/base_layer/core/src/base_node/comms_interface/comms_request.rs index 19d6c574ae..b18e34f04e 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_request.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_request.rs @@ -26,7 +26,7 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{Commitment, HashOutput, PrivateKey, Signature}; +use tari_common_types::types::{BlockHash, Commitment, HashOutput, PrivateKey, PublicKey, Signature}; use tari_utilities::hex::Hex; use crate::{blocks::NewBlockTemplate, chain_storage::MmrTree, proof_of_work::PowAlgorithm}; @@ -64,6 +64,20 @@ pub enum NodeCommsRequest { FetchMempoolTransactionsByExcessSigs { excess_sigs: Vec, }, + FetchValidatorNodesKeys { + height: u64, + }, + GetShardKey { + height: u64, + public_key: PublicKey, + }, + FetchTemplateRegistrations { + start_height: u64, + end_height: u64, + }, + FetchUnspentUtxosInBlock { + block_hash: BlockHash, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -104,6 +118,21 @@ impl Display for NodeCommsRequest { FetchMempoolTransactionsByExcessSigs { .. } => { write!(f, "FetchMempoolTransactionsByExcessSigs") }, + FetchValidatorNodesKeys { height } => { + write!(f, "FetchValidatorNodesKeys ({})", height) + }, + GetShardKey { height, public_key } => { + write!(f, "GetShardKey height ({}), public key ({:?})", height, public_key) + }, + FetchTemplateRegistrations { + start_height: start, + end_height: end, + } => { + write!(f, "FetchTemplateRegistrations ({}..={})", start, end) + }, + FetchUnspentUtxosInBlock { block_hash } => { + write!(f, "FetchUnspentUtxosInBlock ({})", block_hash) + }, } } } diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index 81c3173160..c6714a6fe2 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -27,12 +27,12 @@ use std::{ use tari_common_types::{ chain_metadata::ChainMetadata, - types::{HashOutput, PrivateKey}, + types::{HashOutput, PrivateKey, PublicKey}, }; use crate::{ blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate}, - chain_storage::UtxoMinedInfo, + chain_storage::TemplateRegistrationEntry, proof_of_work::Difficulty, transactions::transaction_components::{Transaction, TransactionKernel, TransactionOutput}, }; @@ -55,22 +55,10 @@ pub enum NodeCommsResponse { }, TargetDifficulty(Difficulty), MmrNodes(Vec, Vec), - FetchTokensResponse { - outputs: Vec<(TransactionOutput, u64)>, - }, - FetchAssetRegistrationsResponse { - outputs: Vec, - }, - FetchAssetMetadataResponse { - output: Box>, - }, FetchMempoolTransactionsByExcessSigsResponse(FetchMempoolTransactionsResponse), - FetchOutputsForBlockResponse { - outputs: Vec, - }, - FetchOutputsByContractIdResponse { - outputs: Vec, - }, + FetchValidatorNodesKeysResponse(Vec<(PublicKey, [u8; 32])>), + GetShardKeyResponse(Option<[u8; 32]>), + FetchTemplateRegistrationsResponse(Vec), } impl Display for NodeCommsResponse { @@ -98,17 +86,15 @@ impl Display for NodeCommsResponse { ), TargetDifficulty(_) => write!(f, "TargetDifficulty"), MmrNodes(_, _) => write!(f, "MmrNodes"), - FetchTokensResponse { .. } => write!(f, "FetchTokensResponse"), - FetchAssetRegistrationsResponse { .. } => write!(f, "FetchAssetRegistrationsResponse"), - FetchAssetMetadataResponse { .. } => write!(f, "FetchAssetMetadataResponse"), FetchMempoolTransactionsByExcessSigsResponse(resp) => write!( f, "FetchMempoolTransactionsByExcessSigsResponse({} transaction(s), {} not found)", resp.transactions.len(), resp.not_found.len() ), - FetchOutputsForBlockResponse { .. } => write!(f, "FetchConstitutionsResponse"), - FetchOutputsByContractIdResponse { .. } => write!(f, "FetchOutputsByContractIdResponse"), + FetchValidatorNodesKeysResponse(_) => write!(f, "FetchValidatorNodesKeysResponse"), + GetShardKeyResponse(_) => write!(f, "GetShardKeyResponse"), + FetchTemplateRegistrationsResponse(_) => write!(f, "FetchTemplateRegistrationsResponse"), } } } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index 3bb487b0b5..2dcd1de90f 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -275,7 +275,6 @@ where B: BlockchainBackend + 'static }, NodeCommsRequest::GetNewBlockTemplate(request) => { let best_block_header = self.blockchain_db.fetch_tip_header().await?; - let mut header = BlockHeader::from_previous(best_block_header.header()); let constants = self.consensus_manager.consensus_constants(header.height); header.version = constants.blockchain_version(); @@ -364,6 +363,37 @@ where B: BlockchainBackend + 'static }, )) }, + NodeCommsRequest::FetchValidatorNodesKeys { height } => { + let active_validator_nodes = self.blockchain_db.fetch_active_validator_nodes(height).await?; + Ok(NodeCommsResponse::FetchValidatorNodesKeysResponse( + active_validator_nodes, + )) + }, + NodeCommsRequest::GetShardKey { height, public_key } => { + let shard_key = self.blockchain_db.get_shard_key(height, public_key).await?; + Ok(NodeCommsResponse::GetShardKeyResponse(shard_key)) + }, + NodeCommsRequest::FetchTemplateRegistrations { + start_height, + end_height, + } => { + let template_registrations = self + .blockchain_db + .fetch_template_registrations(start_height..=end_height) + .await?; + Ok(NodeCommsResponse::FetchTemplateRegistrationsResponse( + template_registrations, + )) + }, + NodeCommsRequest::FetchUnspentUtxosInBlock { block_hash } => { + let utxos = self.blockchain_db.fetch_outputs_in_block(block_hash).await?; + Ok(NodeCommsResponse::TransactionOutputs( + utxos + .into_iter() + .filter_map(|utxo| utxo.into_unpruned_output()) + .collect(), + )) + }, } } diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index 54feaf45e7..5e9de71501 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -24,7 +24,7 @@ use std::{ops::RangeInclusive, sync::Arc}; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashOutput, Signature}, + types::{BlockHash, Commitment, HashOutput, PublicKey, Signature}, }; use tari_service_framework::{reply_channel::SenderService, Service}; use tokio::sync::broadcast; @@ -38,6 +38,7 @@ use crate::{ NodeCommsResponse, }, blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate}, + chain_storage::TemplateRegistrationEntry, proof_of_work::PowAlgorithm, transactions::transaction_components::{TransactionKernel, TransactionOutput}, }; @@ -279,4 +280,66 @@ impl LocalNodeCommsInterface { _ => Err(CommsInterfaceError::UnexpectedApiResponse), } } + + pub async fn get_active_validator_nodes( + &mut self, + height: u64, + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::FetchValidatorNodesKeys { height }) + .await?? + { + NodeCommsResponse::FetchValidatorNodesKeysResponse(validator_node) => Ok(validator_node), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } + + pub async fn get_shard_key( + &mut self, + height: u64, + public_key: PublicKey, + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::GetShardKey { height, public_key }) + .await?? + { + NodeCommsResponse::GetShardKeyResponse(shard_key) => Ok(shard_key), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } + + pub async fn get_template_registrations( + &mut self, + start_height: u64, + end_height: u64, + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::FetchTemplateRegistrations { + start_height, + end_height, + }) + .await?? + { + NodeCommsResponse::FetchTemplateRegistrationsResponse(template_registrations) => Ok(template_registrations), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } + + /// Fetches UTXOs that are not spent for the given block hash up to the current chain tip. + pub async fn fetch_unspent_utxos_in_block( + &mut self, + block_hash: BlockHash, + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::FetchUnspentUtxosInBlock { block_hash }) + .await?? + { + NodeCommsResponse::TransactionOutputs(outputs) => Ok(outputs), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } } diff --git a/base_layer/core/src/base_node/sync/header_sync/error.rs b/base_layer/core/src/base_node/sync/header_sync/error.rs index c744c5e4c5..accd093510 100644 --- a/base_layer/core/src/base_node/sync/header_sync/error.rs +++ b/base_layer/core/src/base_node/sync/header_sync/error.rs @@ -92,4 +92,12 @@ pub enum BlockHeaderSyncError { }, #[error("All sync peers exceeded max allowed latency")] AllSyncPeersExceedLatency, + #[error( + "Validator node MMR at height {height} is not correct. Expected {actual} to equal the computed {computed}" + )] + ValidatorNodeMmmr { + height: u64, + actual: String, + computed: String, + }, } diff --git a/base_layer/core/src/blocks/block_header.rs b/base_layer/core/src/blocks/block_header.rs index 74fcf2393a..ad54b1107f 100644 --- a/base_layer/core/src/blocks/block_header.rs +++ b/base_layer/core/src/blocks/block_header.rs @@ -110,6 +110,8 @@ pub struct BlockHeader { pub nonce: u64, /// Proof of work summary pub pow: ProofOfWork, + /// Merkle root of all active validator node. + pub validator_node_mr: FixedHash, } impl BlockHeader { @@ -130,6 +132,7 @@ impl BlockHeader { total_script_offset: BlindingFactor::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), } } @@ -162,6 +165,7 @@ impl BlockHeader { total_script_offset: BlindingFactor::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), } } @@ -263,6 +267,7 @@ impl From for BlockHeader { total_script_offset: header_template.total_script_offset, nonce: 0, pow: header_template.pow, + validator_node_mr: FixedHash::zero(), } } } diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 4ce9a3af19..02c9e5d2bf 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -39,7 +39,6 @@ use crate::{ EncryptedValue, KernelFeatures, OutputFeatures, - OutputFeaturesVersion, OutputType, TransactionKernel, TransactionKernelVersion, @@ -187,6 +186,8 @@ fn get_igor_genesis_block_raw() -> Block { pow_algo: PowAlgorithm::Sha3, pow_data: vec![], }, + validator_node_mr: FixedHash::from_hex("e1d55f91ecc7e435080ac2641280516a355a5ecbe231158987da217b5af30047") + .unwrap(), }, body, } @@ -232,14 +233,27 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // for o in block.body.outputs() { // witness_mmr.push(o.witness_hash().to_vec()).unwrap(); // output_mmr.push(o.hash().to_vec()).unwrap(); + // if matches!(o.features.output_type, OutputType::ValidatorNodeRegistration) { + // let reg = o + // .features + // .sidechain_feature + // .as_ref() + // .and_then(|f| f.validator_node_registration()) + // .unwrap(); + // vn_mmr.push(reg.derive_shard_key(block.hash()).to_vec()).unwrap(); + // } // } + // let vn_mmr = ValidatorNodeMmr::new(Vec::new()); + // block.header.kernel_mr = FixedHash::try_from(kernel_mmr.get_merkle_root().unwrap()).unwrap(); // block.header.witness_mr = FixedHash::try_from(witness_mmr.get_merkle_root().unwrap()).unwrap(); // block.header.output_mr = FixedHash::try_from(output_mmr.get_merkle_root().unwrap()).unwrap(); + // block.header.validator_node_mr = FixedHash::try_from(vn_mmr.get_merkle_root().unwrap()).unwrap(); // println!("kernel mr: {}", block.header.kernel_mr.to_hex()); // println!("witness mr: {}", block.header.witness_mr.to_hex()); // println!("output mr: {}", block.header.output_mr.to_hex()); + // println!("vn mr: {}", block.header.validator_node_mr.to_hex()); // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = @@ -248,6 +262,8 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { FixedHash::from_hex("8e6bb075239bf307e311f497d35c12c77c4563f218c156895e6630a7d9633de3").unwrap(); block.header.output_mr = FixedHash::from_hex("163304b3fe0f9072170db341945854bf88c8e23e23ecaac3ed86b9231b20e16f").unwrap(); + block.header.validator_node_mr = + FixedHash::from_hex("e1d55f91ecc7e435080ac2641280516a355a5ecbe231158987da217b5af30047").unwrap(); let accumulated_data = BlockHeaderAccumulatedData { hash: block.hash(), @@ -275,13 +291,7 @@ fn get_esmeralda_genesis_block_raw() -> Block { ); let coinbase = TransactionOutput::new( TransactionOutputVersion::get_current_version(), - OutputFeatures { - version: OutputFeaturesVersion::get_current_version(), - output_type: OutputType::Coinbase, - maturity: 6, - metadata: Vec::new(), - sidechain_features: None, - }, + OutputFeatures::create_coinbase(6), Commitment::from_hex("46eec110cf173557e149d453734f6707fea9ed27c9a0dd0276bb43eb1f6e3322").unwrap(), BulletRangeProof::from_hex("01b05c72ea976764b8f9a56bb302990829dacae5f9b2d26e028e97c66a7ac3a14c7809ea5da55fb1e88a16195619d67381f28181b1ad7e0c9661c726e1c56ad7770eb75e314b51a89d716a2dd7737b26a40d8e956911ff45d4c47a1164edae5505aaca58ec6f95762daaa02545dc2ce502e9892d98422849352b6dbcc3322b6b1adae4d33461dd8b5b75b4a9bf52b3e3b00ef7579b16e59f17f43c45ea5e82db063c23ce2d214f93a211cd8f7a3cb220071c68ba3a348b082c3eebb8b6d6339d18decd0372b82e762a9f16e5e7ed23b21c1025ba093b676c55cfa603d888bcc315bc95e8e4bebad9ec51124aab0fe4a8abfc9053db1fb1560c5214b9485826e0127448a2aa84c25f17c5833b15bf434903db7a676bfb11ace2ece255b018428457122da112d481c8a742f916cca069b874e6762248fbb00fa6895f7d4b8a9a8829164baf6ad1d3ad5775c679766ead9da782977fdeb5af7e4b2eb6828e87551179f888ed1c598dd1b81c46b335fb4a827fadf7669e007ff4ed6f260d0bde3eb42282983f58bb0f11a44e064a80503154f4cdb76537192411b2755c2b453b90b3754e9253e64837f15c933b7a479fbb9b1ea8d45364fff67b4aa71ecf67f16c497b5846ff50aaae882e71ac5e6f3ba29189d03da3ed91511074747db413a3e8f90fd9b8fa0751e8ecde29324f4fe8d9023405e33e0d07741056941f9593e8931d0c22553af6447d5c38c762e45afaa89cc11c6843e77430cea44b41fcef0ad11d08d3be1f279ee791fd3b4a8b39d2889a51a4cb2a81885ef6cab119e8de29908a0e").unwrap(), // A default script can never be spent, intentionally @@ -337,6 +347,8 @@ fn get_esmeralda_genesis_block_raw() -> Block { pow_algo: PowAlgorithm::Sha3, pow_data: vec![], }, + validator_node_mr: FixedHash::from_hex("e1d55f91ecc7e435080ac2641280516a355a5ecbe231158987da217b5af30047") + .unwrap(), }, body, } @@ -356,6 +368,7 @@ mod test { validation::{ChainBalanceValidator, FinalHorizonStateValidation}, KernelMmr, MutableOutputMmr, + ValidatorNodeMmr, WitnessMmr, }; @@ -364,82 +377,19 @@ mod test { // Note: Generate new data for `pub fn get_esmeralda_genesis_block()` and `fn get_esmeralda_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_esmeralda_genesis_block(); - assert_eq!(block.block().body.outputs().len(), 4001); - - let factories = CryptoFactories::default(); - assert!(block.block().body.outputs().iter().any(|o| o.is_coinbase())); - let outputs = block.block().body.outputs().iter().collect::>(); - batch_verify_range_proofs(&factories.range_proof, &outputs).unwrap(); - // Coinbase and faucet kernel - assert_eq!( - block.block().body.kernels().len() as u64, - block.header().kernel_mmr_size - ); - assert_eq!( - block.block().body.outputs().len() as u64, - block.header().output_mmr_size - ); - - for kernel in block.block().body.kernels() { - kernel.verify_signature().unwrap(); - } - assert!(block - .block() - .body - .kernels() - .iter() - .any(|k| k.features.contains(KernelFeatures::COINBASE_KERNEL))); - - // Check MMR - let mut kernel_mmr = KernelMmr::new(Vec::new()); - for k in block.block().body.kernels() { - kernel_mmr.push(k.hash().to_vec()).unwrap(); - } - - let mut witness_mmr = WitnessMmr::new(Vec::new()); - let mut output_mmr = MutableOutputMmr::new(Vec::new(), Bitmap::create()).unwrap(); - - for o in block.block().body.outputs() { - o.verify_metadata_signature().unwrap(); - - witness_mmr.push(o.witness_hash().to_vec()).unwrap(); - output_mmr.push(o.hash().to_vec()).unwrap(); - } - - assert_eq!( - kernel_mmr.get_merkle_root().unwrap().as_slice(), - block.header().kernel_mr.as_slice() - ); - assert_eq!( - witness_mmr.get_merkle_root().unwrap().as_slice(), - block.header().witness_mr.as_slice() - ); - assert_eq!( - output_mmr.get_merkle_root().unwrap().as_slice(), - block.header().output_mr.as_slice() - ); - - // Check that the faucet UTXOs balance (the faucet_value consensus constant is set correctly and faucet kernel - // is correct) - - let utxo_sum = block.block().body.outputs().iter().map(|o| &o.commitment).sum(); - let kernel_sum = block.block().body.kernels().iter().map(|k| &k.excess).sum(); - - let db = create_new_blockchain_with_network(Network::Esmeralda); - - let lock = db.db_read_access().unwrap(); - ChainBalanceValidator::new( - ConsensusManager::builder(Network::Esmeralda).build(), - Default::default(), - ) - .validate(&*lock, 0, &utxo_sum, &kernel_sum, &Commitment::default()) - .unwrap(); + check_block(Network::Esmeralda, &block, 4001, 2); } #[test] fn igor_genesis_sanity_check() { let block = get_igor_genesis_block(); - assert_eq!(block.block().body.outputs().len(), 1); + check_block(Network::Igor, &block, 1, 1); + } + + fn check_block(network: Network, block: &ChainBlock, expected_outputs: usize, expected_kernels: usize) { + assert!(block.block().body.inputs().is_empty()); + assert_eq!(block.block().body.kernels().len(), expected_kernels); + assert_eq!(block.block().body.outputs().len(), expected_outputs); let factories = CryptoFactories::default(); assert!(block.block().body.outputs().iter().any(|o| o.is_coinbase())); @@ -473,25 +423,25 @@ mod test { let mut witness_mmr = WitnessMmr::new(Vec::new()); let mut output_mmr = MutableOutputMmr::new(Vec::new(), Bitmap::create()).unwrap(); - assert_eq!(block.block().body.kernels().len(), 1); - assert_eq!(block.block().body.outputs().len(), 1); + let mut vn_mmr = ValidatorNodeMmr::new(Vec::new()); for o in block.block().body.outputs() { witness_mmr.push(o.witness_hash().to_vec()).unwrap(); output_mmr.push(o.hash().to_vec()).unwrap(); + if matches!(o.features.output_type, OutputType::ValidatorNodeRegistration) { + let reg = o + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + .unwrap(); + vn_mmr.push(reg.derive_shard_key(block.hash()).to_vec()).unwrap(); + } } - assert_eq!( - kernel_mmr.get_merkle_root().unwrap().as_slice(), - block.header().kernel_mr.as_slice() - ); - assert_eq!( - witness_mmr.get_merkle_root().unwrap().as_slice(), - block.header().witness_mr.as_slice() - ); - assert_eq!( - output_mmr.get_merkle_root().unwrap().as_slice(), - block.header().output_mr.as_slice() - ); + assert_eq!(kernel_mmr.get_merkle_root().unwrap(), block.header().kernel_mr,); + assert_eq!(witness_mmr.get_merkle_root().unwrap(), block.header().witness_mr,); + assert_eq!(output_mmr.get_merkle_root().unwrap(), block.header().output_mr,); + assert_eq!(vn_mmr.get_merkle_root().unwrap(), block.header().validator_node_mr); // Check that the faucet UTXOs balance (the faucet_value consensus constant is set correctly and faucet kernel // is correct) @@ -502,7 +452,7 @@ mod test { let db = create_new_blockchain_with_network(Network::Igor); let lock = db.db_read_access().unwrap(); - ChainBalanceValidator::new(ConsensusManager::builder(Network::Igor).build(), Default::default()) + ChainBalanceValidator::new(ConsensusManager::builder(network).build(), Default::default()) .validate(&*lock, 0, &utxo_sum, &kernel_sum, &Commitment::default()) .unwrap(); } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs b/base_layer/core/src/chain_storage/active_validator_node.rs similarity index 62% rename from base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs rename to base_layer/core/src/chain_storage/active_validator_node.rs index b002614013..374317bc20 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs +++ b/base_layer/core/src/chain_storage/active_validator_node.rs @@ -1,4 +1,4 @@ -// Copyright 2022. The Tari Project +// Copyright 2022, The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -20,39 +20,14 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::io::{Error, Read, Write}; - use serde::{Deserialize, Serialize}; - -use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; - -#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq)] -pub struct SideChainFeatures {} - -impl SideChainFeatures {} - -impl ConsensusEncoding for SideChainFeatures { - fn consensus_encode(&self, _writer: &mut W) -> Result<(), Error> { - Ok(()) - } -} - -impl ConsensusEncodingSized for SideChainFeatures {} - -impl ConsensusDecoding for SideChainFeatures { - fn consensus_decode(_reader: &mut R) -> Result { - Ok(Self {}) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::consensus::check_consensus_encoding_correctness; - - #[test] - fn consensus_encoding() { - let features = SideChainFeatures {}; - check_consensus_encoding_correctness(features).unwrap(); - } +use tari_common_types::types::{HashOutput, PublicKey}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ActiveValidatorNode { + pub shard_key: [u8; 32], + pub from_height: u64, + pub to_height: u64, + pub public_key: PublicKey, + pub output_hash: HashOutput, } diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 7502c31a3c..dff23d0536 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -26,10 +26,11 @@ use log::*; use rand::{rngs::OsRng, RngCore}; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashOutput, Signature}, + types::{BlockHash, Commitment, HashOutput, PublicKey, Signature}, }; use tari_utilities::epoch_time::EpochTime; +use super::TemplateRegistrationEntry; use crate::{ blocks::{ Block, @@ -63,7 +64,6 @@ use crate::{ proof_of_work::{PowAlgorithm, TargetDifficultyWindow}, transactions::transaction_components::{TransactionKernel, TransactionOutput}, }; - const LOG_TARGET: &str = "c::bn::async_db"; fn trace_log(name: &str, f: F) -> R @@ -109,7 +109,7 @@ macro_rules! make_async_fn { $(#[$outer:meta])* $fn:ident$(< $( $lt:tt $( : $clt:path )? ),+ >)?($($param:ident:$ptype:ty),+) -> $rtype:ty, $name:expr) => { $(#[$outer])* - pub async fn $fn$(< $( $lt $( : $clt )? ),+ +Sync+Send + 'static >)?(&self, $($param: $ptype),+) -> Result<$rtype, ChainStorageError> { + pub async fn $fn$(< $( $lt $( : $clt )? ),+ + Sync + Send + 'static >)?(&self, $($param: $ptype),+) -> Result<$rtype, ChainStorageError> { let db = self.db.clone(); let mut mdc = vec![]; log_mdc::iter(|k, v| mdc.push((k.to_owned(), v.to_owned()))); @@ -163,6 +163,8 @@ impl AsyncBlockchainDb { make_async_fn!(fetch_utxos_in_block(hash: HashOutput, deleted: Option>) -> (Vec, Bitmap), "fetch_utxos_in_block"); + make_async_fn!(fetch_outputs_in_block(hash: HashOutput) -> Vec, "fetch_outputs_in_block"); + make_async_fn!(utxo_count() -> usize, "utxo_count"); //---------------------------------- Kernel --------------------------------------------// @@ -264,6 +266,12 @@ impl AsyncBlockchainDb { make_async_fn!(get_stats() -> DbBasicStats, "get_stats"); make_async_fn!(fetch_total_size_stats() -> DbTotalSizeStats, "fetch_total_size_stats"); + + make_async_fn!(fetch_active_validator_nodes(height: u64) -> Vec<(PublicKey, [u8;32])>, "fetch_active_validator_nodes"); + + make_async_fn!(get_shard_key(height:u64, public_key: PublicKey) -> Option<[u8;32]>, "get_shard_key"); + + make_async_fn!(fetch_template_registrations>(range: T) -> Vec, "fetch_template_registrations"); } impl From> for AsyncBlockchainDb { diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index 25291c6765..9bcff45612 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -4,9 +4,10 @@ use croaring::Bitmap; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{Commitment, HashOutput, Signature}, + types::{Commitment, HashOutput, PublicKey, Signature}, }; +use super::TemplateRegistrationEntry; use crate::{ blocks::{ Block, @@ -191,4 +192,12 @@ pub trait BlockchainBackend: Send + Sync { /// Fetches all tracked reorgs fn fetch_all_reorgs(&self) -> Result, ChainStorageError>; + + fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError>; + fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError>; + fn fetch_template_registrations( + &self, + start_height: u64, + end_height: u64, + ) -> Result, ChainStorageError>; } diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 7a44d534a5..8affd89bd6 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -36,11 +36,12 @@ use log::*; use serde::{Deserialize, Serialize}; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, FixedHash, HashOutput, Signature}, + types::{BlockHash, Commitment, FixedHash, HashOutput, PublicKey, Signature}, }; use tari_mmr::pruned_hashset::PrunedHashSet; use tari_utilities::{epoch_time::EpochTime, hex::Hex, ByteArray}; +use super::TemplateRegistrationEntry; use crate::{ blocks::{ Block, @@ -91,6 +92,7 @@ use crate::{ PrunedInputMmr, PrunedKernelMmr, PrunedWitnessMmr, + ValidatorNodeMmr, }; const LOG_TARGET: &str = "c::cs::database"; @@ -437,6 +439,11 @@ where B: BlockchainBackend db.fetch_utxos_in_block(&hash, deleted.as_deref()) } + pub fn fetch_outputs_in_block(&self, hash: HashOutput) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.fetch_outputs_in_block(&hash) + } + /// Returns the number of UTXOs in the current unspent set pub fn utxo_count(&self) -> Result { let db = self.db_read_access()?; @@ -814,6 +821,7 @@ where B: BlockchainBackend block.header.output_mr = roots.output_mr; block.header.witness_mr = roots.witness_mr; block.header.output_mmr_size = roots.output_mmr_size; + block.header.validator_node_mr = roots.validator_node_mr; Ok(block) } @@ -837,6 +845,11 @@ where B: BlockchainBackend db.fetch_mmr_size(tree) } + pub fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.get_shard_key(height, public_key) + } + /// Tries to add a block to the longest chain. /// /// The block is added to the longest chain if and only if @@ -1169,6 +1182,25 @@ where B: BlockchainBackend txn.clear_all_reorgs(); db.write(txn) } + + pub fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.fetch_active_validator_nodes(height) + } + + pub fn fetch_template_registrations>( + &self, + range: T, + ) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + let (start, mut end) = convert_to_option_bounds(range); + if end.is_none() { + // `(n..)` means fetch block headers until this node's tip + end = Some(db.fetch_last_header()?.height); + } + let (start, end) = (start.unwrap_or(0), end.unwrap()); + db.fetch_template_registrations(start, end) + } } fn unexpected_result(request: DbKey, response: DbValue) -> Result { @@ -1189,20 +1221,24 @@ pub struct MmrRoots { pub output_mr: FixedHash, pub witness_mr: FixedHash, pub output_mmr_size: u64, + pub validator_node_mr: FixedHash, } impl std::fmt::Display for MmrRoots { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "MMR Roots")?; - writeln!(f, "Input MR : {}", &self.input_mr.to_hex())?; - writeln!(f, "Witness MR : {}", &self.witness_mr.to_hex())?; - writeln!(f, "Kernel MR : {}", &self.kernel_mr.to_hex())?; - writeln!(f, "Kernel MMR Size : {}", &self.kernel_mmr_size)?; - writeln!(f, "Output MR : {}", &self.output_mr.to_hex())?; - writeln!(f, "Output MMR Size : {}", &self.output_mmr_size) + writeln!(f, "Input MR : {}", self.input_mr)?; + writeln!(f, "Witness MR : {}", self.witness_mr)?; + writeln!(f, "Kernel MR : {}", self.kernel_mr)?; + writeln!(f, "Kernel MMR Size : {}", self.kernel_mmr_size)?; + writeln!(f, "Output MR : {}", self.output_mr)?; + writeln!(f, "Output MMR Size : {}", self.output_mmr_size)?; + writeln!(f, "Validator MR : {}", self.validator_node_mr)?; + Ok(()) } } +#[allow(clippy::too_many_lines)] #[allow(clippy::similar_names)] pub fn calculate_mmr_roots(db: &T, block: &Block) -> Result { let header = &block.header; @@ -1305,6 +1341,10 @@ pub fn calculate_mmr_roots(db: &T, block: &Block) -> Resul output_mmr.compress(); + let mut validator_nodes = db.fetch_active_validator_nodes(metadata.height_of_longest_chain() + 1)?; + validator_nodes.sort(); + let vn_mmr = ValidatorNodeMmr::new(validator_nodes.iter().map(|vn| vn.1.to_vec()).collect()); + let mmr_roots = MmrRoots { kernel_mr: FixedHash::try_from(kernel_mmr.get_merkle_root()?)?, kernel_mmr_size: kernel_mmr.get_leaf_count()? as u64, @@ -1312,6 +1352,7 @@ pub fn calculate_mmr_roots(db: &T, block: &Block) -> Resul output_mr: FixedHash::try_from(output_mmr.get_merkle_root()?)?, output_mmr_size: output_mmr.get_leaf_count() as u64, witness_mr: FixedHash::try_from(witness_mmr.get_merkle_root()?)?, + validator_node_mr: FixedHash::try_from(vn_mmr.get_merkle_root()?)?, }; Ok(mmr_roots) } diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index e1fcbbcb6a..d2c2f706db 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -30,6 +30,7 @@ use croaring::Bitmap; use tari_common_types::types::{BlockHash, Commitment, HashOutput}; use tari_utilities::hex::Hex; +use super::{ActiveValidatorNode, TemplateRegistrationEntry}; use crate::{ blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, UpdateBlockAccumulatedData}, chain_storage::{error::ChainStorageError, HorizonData, Reorg}, @@ -358,6 +359,12 @@ pub enum WriteOperation { reorg: Reorg, }, ClearAllReorgs, + InsertValidatorNode { + validator_node: ActiveValidatorNode, + }, + InsertTemplateRegistration { + template_registration: TemplateRegistrationEntry, + }, } impl fmt::Display for WriteOperation { @@ -454,6 +461,12 @@ impl fmt::Display for WriteOperation { SetHorizonData { .. } => write!(f, "Set horizon data"), InsertReorg { .. } => write!(f, "Insert reorg"), ClearAllReorgs => write!(f, "Clear all reorgs"), + InsertValidatorNode { validator_node } => { + write!(f, "Inserting VN {:?}", validator_node) + }, + InsertTemplateRegistration { template_registration } => { + write!(f, "Inserting Template {:?}", template_registration) + }, } } } diff --git a/base_layer/core/src/chain_storage/error.rs b/base_layer/core/src/chain_storage/error.rs index 93456647ce..62d33a6a91 100644 --- a/base_layer/core/src/chain_storage/error.rs +++ b/base_layer/core/src/chain_storage/error.rs @@ -134,6 +134,8 @@ pub enum ChainStorageError { UnspendableDueToDependentUtxos { details: String }, #[error("FixedHashSize Error: {0}")] FixedHashSizeError(#[from] FixedHashSizeError), + #[error("Composite key length was exceeded (THIS SHOULD NEVER HAPPEN)")] + CompositeKeyLengthExceeded, } impl ChainStorageError { diff --git a/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs b/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs index 565feb8104..0d6a7f5c09 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/composite_key.rs @@ -25,15 +25,18 @@ use std::{ ops::{Deref, DerefMut}, }; +use lmdb_zero::traits::AsLmdbBytes; use tari_utilities::hex::to_hex; -#[derive(Debug, Clone, Copy)] -pub(super) struct CompositeKey { - bytes: [u8; KEY_LEN], +use crate::chain_storage::ChainStorageError; + +#[derive(Debug, Clone)] +pub(super) struct CompositeKey { + bytes: Box<[u8; L]>, len: usize, } -impl CompositeKey { +impl CompositeKey { pub fn new() -> Self { Self { bytes: Self::new_buf(), @@ -41,10 +44,20 @@ impl CompositeKey { } } + pub fn try_from_parts>(parts: &[T]) -> Result { + let mut key = Self::new(); + for part in parts { + if !key.push(part) { + return Err(ChainStorageError::CompositeKeyLengthExceeded); + } + } + Ok(key) + } + pub fn push>(&mut self, bytes: T) -> bool { let b = bytes.as_ref(); let new_len = self.len + b.len(); - if new_len > KEY_LEN { + if new_len > L { return false; } self.bytes[self.len..new_len].copy_from_slice(b); @@ -61,18 +74,18 @@ impl CompositeKey { } /// Returns a fixed 0-filled byte array. - const fn new_buf() -> [u8; KEY_LEN] { - [0x0u8; KEY_LEN] + fn new_buf() -> Box<[u8; L]> { + Box::new([0x0u8; L]) } } -impl Display for CompositeKey { +impl Display for CompositeKey { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", to_hex(self.as_bytes())) } } -impl Deref for CompositeKey { +impl Deref for CompositeKey { type Target = [u8]; fn deref(&self) -> &Self::Target { @@ -80,14 +93,20 @@ impl Deref for CompositeKey { } } -impl DerefMut for CompositeKey { +impl DerefMut for CompositeKey { fn deref_mut(&mut self) -> &mut Self::Target { self.as_bytes_mut() } } -impl AsRef<[u8]> for CompositeKey { +impl AsRef<[u8]> for CompositeKey { fn as_ref(&self) -> &[u8] { self.as_bytes() } } + +impl AsLmdbBytes for CompositeKey { + fn as_lmdb_bytes(&self) -> &[u8] { + self.as_bytes() + } +} diff --git a/base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs b/base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs index 0cf60dda93..a0401b4771 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs @@ -82,7 +82,14 @@ where V: DeserializeOwned } } - pub fn seek_gte(&mut self, key: &[u8]) -> Result, V)>, ChainStorageError> { + // This function could be used later in cases where multiple seeks are required. + #[cfg(test)] + pub fn reset_to(&mut self, prefix_key: &'a [u8]) { + self.has_seeked = false; + self.prefix_key = prefix_key; + } + + fn seek_gte(&mut self, key: &[u8]) -> Result, V)>, ChainStorageError> { self.has_seeked = true; let seek_result = self.cursor.seek_range_k(&self.access, key).to_opt()?; let (k, v) = match seek_result { @@ -105,3 +112,66 @@ where V: DeserializeOwned Ok(Some((k.to_vec(), val))) } } + +#[cfg(test)] +mod tests { + use std::fs; + + use lmdb_zero::{db, ReadTransaction, WriteTransaction}; + use tari_storage::lmdb_store::{LMDBBuilder, LMDBConfig}; + use tari_test_utils::paths::create_temporary_data_path; + + use crate::chain_storage::lmdb_db::lmdb::{lmdb_get_prefix_cursor, lmdb_insert}; + + #[test] + fn test_lmdb_get_prefix_cursor() { + let temp_path = create_temporary_data_path(); + + let lmdb_store = LMDBBuilder::new() + .set_path(&temp_path) + .set_env_config(LMDBConfig::default()) + .set_max_number_of_databases(1) + .add_database("test", db::CREATE) + .build() + .unwrap(); + + let db = lmdb_store.get_handle("test").unwrap(); + { + let txn = WriteTransaction::new(lmdb_store.env()).unwrap(); + lmdb_insert(&txn, &db.db(), &[0xffu8, 0, 0, 0], &1u64, "test").unwrap(); + lmdb_insert(&txn, &db.db(), &[0x2bu8, 0, 0, 1], &2u64, "test").unwrap(); + lmdb_insert(&txn, &db.db(), &[0x2bu8, 0, 1, 1], &3u64, "test").unwrap(); + lmdb_insert(&txn, &db.db(), &[0x2bu8, 1, 1, 0], &4u64, "test").unwrap(); + lmdb_insert(&txn, &db.db(), &[0x2bu8, 1, 1, 1], &5u64, "test").unwrap(); + lmdb_insert(&txn, &db.db(), &[0x00u8, 1, 1, 1], &5u64, "test").unwrap(); + txn.commit().unwrap(); + } + + { + let txn = ReadTransaction::new(lmdb_store.env()).unwrap(); + let db = db.db(); + let mut cursor = lmdb_get_prefix_cursor::(&txn, &db, &[0x2b]).unwrap(); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 0, 0, 1], 2)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 0, 1, 1], 3)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 0], 4)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 1], 5)); + assert_eq!(cursor.next().unwrap(), None); + + cursor.reset_to(&[0x2b, 1, 1]); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 0], 4)); + let kv = cursor.next().unwrap().unwrap(); + assert_eq!(kv, (vec![0x2b, 1, 1, 1], 5)); + assert_eq!(cursor.next().unwrap(), None); + + cursor.reset_to(&[0x11]); + assert_eq!(cursor.next().unwrap(), None); + } + + fs::remove_dir_all(&temp_path).expect("Could not delete temporary file"); + } +} diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index e2c826d2e8..65c1b20b8b 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -20,12 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Because we use dynamically sized u8 vectors for hash types through the type alias HashOutput, -// let's ignore this clippy error in this module - -#![allow(clippy::ptr_arg)] - -use std::{convert::TryFrom, fmt, fs, fs::File, mem, ops::Deref, path::Path, sync::Arc, time::Instant}; +use std::{ + collections::HashMap, + convert::TryFrom, + fmt, + fs, + fs::File, + ops::Deref, + path::Path, + sync::Arc, + time::Instant, +}; use croaring::Bitmap; use fs2::FileExt; @@ -34,7 +39,7 @@ use log::*; use serde::{Deserialize, Serialize}; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashOutput, Signature}, + types::{BlockHash, Commitment, HashOutput, PublicKey, Signature}, }; use tari_storage::lmdb_store::{db, LMDBBuilder, LMDBConfig, LMDBStore}; use tari_utilities::{ @@ -42,6 +47,7 @@ use tari_utilities::{ ByteArray, }; +use super::{key_prefix_cursor::KeyPrefixCursor, lmdb::lmdb_get_prefix_cursor}; use crate::{ blocks::{ Block, @@ -57,6 +63,7 @@ use crate::{ db_transaction::{DbKey, DbTransaction, DbValue, WriteOperation}, error::{ChainStorageError, OrNotFound}, lmdb_db::{ + composite_key::CompositeKey, lmdb::{ fetch_db_entry_sizes, lmdb_clear, @@ -83,6 +90,7 @@ use crate::{ }, stats::DbTotalSizeStats, utxo_mined_info::UtxoMinedInfo, + ActiveValidatorNode, BlockchainBackend, DbBasicStats, DbSize, @@ -90,7 +98,9 @@ use crate::{ MmrTree, PrunedOutput, Reorg, + TemplateRegistrationEntry, }, + consensus::ConsensusManager, transactions::{ aggregated_body::AggregateBody, transaction_components::{TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, @@ -128,8 +138,25 @@ const LMDB_DB_ORPHAN_CHAIN_TIPS: &str = "orphan_chain_tips"; const LMDB_DB_ORPHAN_PARENT_MAP_INDEX: &str = "orphan_parent_map_index"; const LMDB_DB_BAD_BLOCK_LIST: &str = "bad_blocks"; const LMDB_DB_REORGS: &str = "reorgs"; - -pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Result { +const LMDB_DB_VALIDATOR_NODES: &str = "validator_nodes"; +const LMDB_DB_VALIDATOR_NODES_MAPPING: &str = "validator_nodes_mapping"; +const LMDB_DB_VALIDATOR_NODE_ENDING: &str = "validator_node_ending"; +const LMDB_DB_TEMPLATE_REGISTRATIONS: &str = "template_registrations"; + +/// HeaderHash(32), mmr_pos(4), hash(32) +type InputKey = CompositeKey<68>; +/// HeaderHash(32), mmr_pos(4), hash(32) +type KernelKey = CompositeKey<68>; +/// HeaderHash(32), mmr_pos(4), hash(32) +type OutputKey = CompositeKey<68>; +/// Height(8), Hash(32) +type ValidatorNodeRegistrationKey = CompositeKey<40>; + +pub fn create_lmdb_database>( + path: P, + config: LMDBConfig, + consensus_manager: ConsensusManager, +) -> Result { let flags = db::CREATE; debug!(target: LOG_TARGET, "Creating LMDB database at {:?}", path.as_ref()); std::fs::create_dir_all(&path)?; @@ -166,10 +193,14 @@ pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Resu .add_database(LMDB_DB_ORPHAN_PARENT_MAP_INDEX, flags | db::DUPSORT) .add_database(LMDB_DB_BAD_BLOCK_LIST, flags) .add_database(LMDB_DB_REORGS, flags | db::INTEGERKEY) + .add_database(LMDB_DB_VALIDATOR_NODES, flags | db::DUPSORT) + .add_database(LMDB_DB_VALIDATOR_NODES_MAPPING, flags | db::DUPSORT) + .add_database(LMDB_DB_VALIDATOR_NODE_ENDING, flags | db::INTEGERKEY | db::DUPSORT) + .add_database(LMDB_DB_TEMPLATE_REGISTRATIONS, flags | db::DUPSORT) .build() .map_err(|err| ChainStorageError::CriticalError(format!("Could not create LMDB store:{}", err)))?; debug!(target: LOG_TARGET, "LMDB database creation successful"); - LMDBDatabase::new(&lmdb_store, file_lock) + LMDBDatabase::new(&lmdb_store, file_lock, consensus_manager) } /// This is a lmdb-based blockchain database for persistent storage of the chain state. @@ -224,11 +255,24 @@ pub struct LMDBDatabase { bad_blocks: DatabaseRef, /// Stores reorgs by epochtime and Reorg reorgs: DatabaseRef, + /// Maps VN Public Key -> ActiveValidatorNode + validator_nodes: DatabaseRef, + /// Maps VN Shard Key -> VN Public Key + validator_nodes_mapping: DatabaseRef, + /// Maps the end block height of nodes + validator_nodes_ending: DatabaseRef, + /// Maps CodeTemplateRegistration -> TemplateRegistration + template_registrations: DatabaseRef, _file_lock: Arc, + consensus_manager: ConsensusManager, } impl LMDBDatabase { - pub fn new(store: &LMDBStore, file_lock: File) -> Result { + pub fn new( + store: &LMDBStore, + file_lock: File, + consensus_manager: ConsensusManager, + ) -> Result { let env = store.env(); let db = Self { @@ -259,9 +303,14 @@ impl LMDBDatabase { orphan_parent_map_index: get_database(store, LMDB_DB_ORPHAN_PARENT_MAP_INDEX)?, bad_blocks: get_database(store, LMDB_DB_BAD_BLOCK_LIST)?, reorgs: get_database(store, LMDB_DB_REORGS)?, + validator_nodes: get_database(store, LMDB_DB_VALIDATOR_NODES)?, + validator_nodes_mapping: get_database(store, LMDB_DB_VALIDATOR_NODES_MAPPING)?, + validator_nodes_ending: get_database(store, LMDB_DB_VALIDATOR_NODE_ENDING)?, + template_registrations: get_database(store, LMDB_DB_TEMPLATE_REGISTRATIONS)?, env, env_config: store.env_config(), _file_lock: Arc::new(file_lock), + consensus_manager, }; run_migrations(&db)?; @@ -462,6 +511,12 @@ impl LMDBDatabase { ClearAllReorgs => { lmdb_clear(&write_txn, &self.reorgs)?; }, + InsertValidatorNode { validator_node } => { + self.insert_validator_node(&write_txn, validator_node)?; + }, + InsertTemplateRegistration { template_registration } => { + self.insert_template_registration(&write_txn, template_registration)?; + }, } } write_txn.commit()?; @@ -469,7 +524,7 @@ impl LMDBDatabase { Ok(()) } - fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 24] { + fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 27] { [ ("metadata_db", &self.metadata_db), ("headers_db", &self.headers_db), @@ -501,6 +556,9 @@ impl LMDBDatabase { ("orphan_parent_map_index", &self.orphan_parent_map_index), ("bad_blocks", &self.bad_blocks), ("reorgs", &self.reorgs), + ("validator_nodes", &self.validator_nodes), + ("validator_nodes_mapping", &self.validator_nodes_mapping), + ("template_registrations", &self.template_registrations), ] } @@ -510,19 +568,16 @@ impl LMDBDatabase { key: &OutputKey, ) -> Result { let mut output: TransactionOutputRowData = - lmdb_get(txn, &self.utxos_db, key.as_bytes()).or_not_found("TransactionOutput", "key", key.to_hex())?; + lmdb_get(txn, &self.utxos_db, key).or_not_found("TransactionOutput", "key", key.to_string())?; let pruned_output = output .output .take() .ok_or_else(|| ChainStorageError::DataInconsistencyDetected { function: "prune_output", - details: format!( - "Attempt to prune output that has already been pruned for key {}", - key.to_hex() - ), + details: format!("Attempt to prune output that has already been pruned for key {}", key), })?; // output.output is None - lmdb_replace(txn, &self.utxos_db, key.as_bytes(), &output)?; + lmdb_replace(txn, &self.utxos_db, key, &output)?; Ok(pruned_output) } @@ -538,7 +593,7 @@ impl LMDBDatabase { let output_hash = output.hash(); let witness_hash = output.witness_hash(); - let output_key = OutputKey::new(header_hash.as_slice(), mmr_position, &[]); + let output_key = OutputKey::try_from_parts(&[header_hash.as_slice(), mmr_position.to_le_bytes().as_slice()])?; lmdb_insert( txn, @@ -552,13 +607,13 @@ impl LMDBDatabase { txn, &*self.txos_hash_to_index_db, output_hash.as_slice(), - &(mmr_position, output_key.as_bytes()), + &(mmr_position, output_key.to_vec()), "txos_hash_to_index_db", )?; lmdb_insert( txn, &*self.utxos_db, - output_key.as_bytes(), + &output_key, &TransactionOutputRowData { output: Some(output.clone()), header_hash: *header_hash, @@ -590,18 +645,18 @@ impl LMDBDatabase { header_hash.to_hex(), ))); } - let key = OutputKey::new(header_hash.as_slice(), mmr_position, &[]); + let key = OutputKey::try_from_parts(&[header_hash.as_slice(), mmr_position.to_le_bytes().as_slice()])?; lmdb_insert( txn, &*self.txos_hash_to_index_db, output_hash.as_slice(), - &(mmr_position, key.as_bytes()), + &(mmr_position, key.to_vec()), "txos_hash_to_index_db", )?; lmdb_insert( txn, &*self.utxos_db, - key.as_bytes(), + &key, &TransactionOutputRowData { output: None, header_hash: *header_hash, @@ -623,7 +678,11 @@ impl LMDBDatabase { mmr_position: u32, ) -> Result<(), ChainStorageError> { let hash = kernel.hash(); - let key = KernelKey::new(header_hash.as_slice(), mmr_position, hash.as_slice()); + let key = KernelKey::try_from_parts(&[ + header_hash.as_slice(), + mmr_position.to_le_bytes().as_slice(), + hash.as_slice(), + ])?; lmdb_insert( txn, @@ -647,7 +706,7 @@ impl LMDBDatabase { lmdb_insert( txn, &*self.kernels_db, - key.as_bytes(), + &key, &TransactionKernelRowData { kernel: kernel.clone(), header_hash: *header_hash, @@ -686,11 +745,15 @@ impl LMDBDatabase { )?; let hash = input.canonical_hash(); - let key = InputKey::new(header_hash.as_slice(), mmr_position, hash.as_slice()); + let key = InputKey::try_from_parts(&[ + header_hash.as_slice(), + mmr_position.to_le_bytes().as_slice(), + hash.as_slice(), + ])?; lmdb_insert( txn, &*self.inputs_db, - key.as_bytes(), + &key, &TransactionInputRowDataRef { input: &input.to_compact(), header_hash, @@ -1234,6 +1297,16 @@ impl LMDBDatabase { None => return Err(ChainStorageError::UnspendableInput), }, }; + + let features = input.features()?; + if let Some(vn_reg) = features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + { + self.delete_validator_node(txn, &vn_reg.public_key, &input.output_hash())?; + } + if !output_mmr.delete(index) { return Err(ChainStorageError::InvalidOperation(format!( "Could not delete index {} from the output MMR", @@ -1252,6 +1325,44 @@ impl LMDBDatabase { mmr_count )) })?; + + let output_hash = output.hash(); + if let Some(vn_reg) = output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + { + let shard_key = vn_reg.derive_shard_key(&block_hash); + + let validator_node = ActiveValidatorNode { + shard_key, + from_height: header.height + 1, // The node is active one block after it's mined + to_height: header.height + + 1 + + self.consensus_manager + .consensus_constants(header.height) + .validator_node_timeout(), + public_key: vn_reg.public_key.clone(), + output_hash, + }; + self.insert_validator_node(txn, &validator_node)?; + } + if let Some(template_reg) = output + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.template_registration()) + { + let record = TemplateRegistrationEntry { + registration_data: template_reg.clone(), + output_hash, + block_height: header.height, + block_hash, + }; + + self.insert_template_registration(txn, &record)?; + } self.insert_output( txn, &block_hash, @@ -1403,8 +1514,8 @@ impl LMDBDatabase { &u64::from(pos + 1).to_be_bytes(), ) .or_not_found("BlockHeader", "mmr_position", pos.to_string())?; - let key = OutputKey::new(&hash, *pos, &[]); - debug!(target: LOG_TARGET, "Pruning output: {}", key.to_hex()); + let key = OutputKey::try_from_parts(&[hash.as_slice(), pos.to_le_bytes().as_slice()])?; + debug!(target: LOG_TARGET, "Pruning output: {}", key); self.prune_output(write_txn, &key)?; } @@ -1493,6 +1604,73 @@ impl LMDBDatabase { Ok(()) } + fn insert_validator_node( + &self, + txn: &WriteTransaction<'_>, + validator_node: &ActiveValidatorNode, + ) -> Result<(), ChainStorageError> { + let mut key = validator_node.public_key.to_vec(); + key.extend(validator_node.output_hash.as_slice()); + lmdb_insert(txn, &self.validator_nodes, &key, validator_node, "validator_nodes")?; + lmdb_insert_dup( + txn, + &self.validator_nodes_ending, + &validator_node.to_height.to_le_bytes(), + validator_node, + )?; + lmdb_insert( + txn, + &self.validator_nodes_mapping, + &validator_node.shard_key, + &validator_node.public_key.as_bytes(), + "validator_nodes_mapping", + ) + } + + fn delete_validator_node( + &self, + txn: &WriteTransaction<'_>, + public_key: &PublicKey, + output_hash: &HashOutput, + ) -> Result<(), ChainStorageError> { + let mut key = public_key.to_vec(); + key.extend(output_hash.as_slice()); + let x: ActiveValidatorNode = + lmdb_get(txn, &self.validator_nodes, &key)?.ok_or_else(|| ChainStorageError::ValueNotFound { + entity: "ActiveValidatorNode", + field: "public_key and outputhash", + value: key.to_hex(), + })?; + + lmdb_delete_key_value(txn, &self.validator_nodes_ending, &x.to_height.to_le_bytes(), &x)?; + lmdb_delete(txn, &self.validator_nodes, &key, "validator_nodes")?; + lmdb_delete( + txn, + &self.validator_nodes_mapping, + &x.shard_key, + "validator_nodes_mapping", + )?; + Ok(()) + } + + fn insert_template_registration( + &self, + txn: &WriteTransaction<'_>, + template_registration: &TemplateRegistrationEntry, + ) -> Result<(), ChainStorageError> { + let key = ValidatorNodeRegistrationKey::try_from_parts(&[ + template_registration.block_height.to_le_bytes().as_slice(), + template_registration.output_hash.as_slice(), + ])?; + lmdb_insert( + txn, + &self.template_registrations, + &key, + template_registration, + "template_registrations", + ) + } + fn fetch_output_in_txn( &self, txn: &ConstTransaction<'_>, @@ -1889,8 +2067,12 @@ impl BlockchainBackend for LMDBDatabase { if let Some((header_hash, mmr_position, hash)) = lmdb_get::<_, (HashOutput, u32, HashOutput)>(&txn, &self.kernel_excess_sig_index, key.as_slice())? { - let key = KernelKey::new(header_hash.deref(), mmr_position, hash.deref()); - Ok(lmdb_get(&txn, &self.kernels_db, key.as_bytes())? + let key = KernelKey::try_from_parts(&[ + header_hash.as_slice(), + mmr_position.to_le_bytes().as_slice(), + hash.as_slice(), + ])?; + Ok(lmdb_get(&txn, &self.kernels_db, &key)? .map(|kernel: TransactionKernelRowData| (kernel.kernel, header_hash))) } else { Ok(None) @@ -2299,6 +2481,76 @@ impl BlockchainBackend for LMDBDatabase { let txn = self.read_transaction()?; lmdb_filter_map_values(&txn, &self.reorgs, Some) } + + // The clippy warning is because PublicKey has a public inner type that could change. In this case + // it should be fine to ignore the warning. You could also change the logic to use something + // other than a hashmap. + #[allow(clippy::mutable_key_type)] + fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError> { + let txn = self.read_transaction()?; + let validator_node_timeout = self + .consensus_manager + .consensus_constants(height) + .validator_node_timeout(); + let mut pub_keys = HashMap::new(); + + let end = height + validator_node_timeout; + for h in height..end { + lmdb_get_multiple(&txn, &self.validator_nodes_ending, &h.to_le_bytes())? + .into_iter() + .for_each(|v: ActiveValidatorNode| { + if v.from_height <= height { + if let Some((shard_key, start)) = + pub_keys.insert(v.public_key.clone(), (v.shard_key, v.from_height)) + { + // If the node is already in the map, check if the start height is higher. If it is, replace + // the old value with the new one. + if start > v.from_height { + pub_keys.insert(v.public_key, (shard_key, start)); + } + } + } + }); + } + + // now remove the heights + Ok(pub_keys + .into_iter() + .map(|(pk, (shard_key, _))| (pk, shard_key)) + .collect()) + } + + fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError> { + let txn = self.read_transaction()?; + let mut validator_nodes: Vec = + lmdb_fetch_matching_after(&txn, &self.validator_nodes, public_key.as_bytes())?; + validator_nodes = validator_nodes + .into_iter() + .filter(|a| a.from_height <= height && height <= a.to_height) + .collect(); + // get the last one + validator_nodes.sort_by(|a, b| a.from_height.cmp(&b.from_height)); + + Ok(validator_nodes.into_iter().map(|a| a.shard_key).last()) + } + + fn fetch_template_registrations( + &self, + start_height: u64, + end_height: u64, + ) -> Result, ChainStorageError> { + let txn = self.read_transaction()?; + let mut result = vec![]; + for _ in start_height..=end_height { + let height = start_height.to_le_bytes(); + let mut cursor: KeyPrefixCursor = + lmdb_get_prefix_cursor(&txn, &self.template_registrations, &height)?; + while let Some((_, val)) = cursor.next()? { + result.push(val); + } + } + Ok(result) + } } // Fetch the chain metadata @@ -2559,33 +2811,6 @@ impl<'a, 'b> DeletedBitmapModel<'a, WriteTransaction<'b>> { } } -struct CompositeKey { - key: Vec, -} - -impl CompositeKey { - pub fn new(header_hash: &[u8], mmr_position: u32, hash: &[u8]) -> CompositeKey { - let mut key = Vec::with_capacity(header_hash.len() + mem::size_of::() + hash.len()); - key.extend_from_slice(header_hash); - key.extend_from_slice(&mmr_position.to_be_bytes()); - key.extend_from_slice(hash); - - CompositeKey { key } - } - - pub fn as_bytes(&self) -> &[u8] { - &self.key - } - - pub fn to_hex(&self) -> String { - self.key.to_hex() - } -} - -type InputKey = CompositeKey; -type KernelKey = CompositeKey; -type OutputKey = CompositeKey; - fn run_migrations(db: &LMDBDatabase) -> Result<(), ChainStorageError> { const MIGRATION_VERSION: u64 = 1; let txn = db.read_transaction()?; diff --git a/base_layer/core/src/chain_storage/lmdb_db/mod.rs b/base_layer/core/src/chain_storage/lmdb_db/mod.rs index e462684b17..dd25794e99 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/mod.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/mod.rs @@ -28,6 +28,7 @@ use tari_crypto::hash_domain; use crate::transactions::transaction_components::{TransactionInput, TransactionKernel, TransactionOutput}; // mod composite_key; +mod composite_key; pub(crate) mod helpers; pub(crate) mod key_prefix_cursor; mod lmdb; diff --git a/base_layer/core/src/chain_storage/mod.rs b/base_layer/core/src/chain_storage/mod.rs index 8dfa32d3bc..6777a0fd05 100644 --- a/base_layer/core/src/chain_storage/mod.rs +++ b/base_layer/core/src/chain_storage/mod.rs @@ -79,3 +79,9 @@ mod target_difficulties; mod utxo_mined_info; pub use target_difficulties::TargetDifficulties; pub use utxo_mined_info::*; + +mod active_validator_node; +pub use active_validator_node::ActiveValidatorNode; + +mod template_registation; +pub use template_registation::TemplateRegistrationEntry; diff --git a/applications/tari_app_grpc/src/conversions/sidechain_features.rs b/base_layer/core/src/chain_storage/template_registation.rs similarity index 75% rename from applications/tari_app_grpc/src/conversions/sidechain_features.rs rename to base_layer/core/src/chain_storage/template_registation.rs index e97bda9428..b13c8370b5 100644 --- a/applications/tari_app_grpc/src/conversions/sidechain_features.rs +++ b/base_layer/core/src/chain_storage/template_registation.rs @@ -1,4 +1,4 @@ -// Copyright 2022. The Tari Project +// Copyright 2022, The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -20,22 +20,15 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::convert::TryFrom; +use serde::{Deserialize, Serialize}; +use tari_common_types::types::FixedHash; -use tari_core::transactions::transaction_components::SideChainFeatures; +use crate::transactions::transaction_components::CodeTemplateRegistration; -use crate::tari_rpc as grpc; - -impl From for grpc::SideChainFeatures { - fn from(_value: SideChainFeatures) -> Self { - Self {} - } -} - -impl TryFrom for SideChainFeatures { - type Error = String; - - fn try_from(_features: grpc::SideChainFeatures) -> Result { - Ok(Self {}) - } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TemplateRegistrationEntry { + pub registration_data: CodeTemplateRegistration, + pub output_hash: FixedHash, + pub block_height: u64, + pub block_hash: FixedHash, } diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index 03b0910a6e..f90229ff3b 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -41,7 +41,6 @@ use crate::{ }, txn_schema, }; - fn setup() -> BlockchainDatabase { create_new_blockchain() } @@ -71,6 +70,7 @@ fn apply_mmr_to_block(db: &BlockchainDatabase, block: Block) -> Bl block.header.output_mmr_size = mmr_roots.output_mmr_size; block.header.kernel_mr = mmr_roots.kernel_mr; block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size; + block.header.validator_node_mr = mmr_roots.validator_node_mr; block } diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 08f0eecd03..e0efd0979a 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -94,6 +94,8 @@ pub struct ConsensusConstants { kernel_version_range: RangeInclusive, /// An allowlist of output types permitted_output_types: &'static [OutputType], + /// How long does it take to timeout validator node registration + validator_node_timeout: u64, } // todo: remove this once OutputFeaturesVersion is removed in favor of just TransactionOutputVersion @@ -288,6 +290,10 @@ impl ConsensusConstants { self.permitted_output_types } + pub fn validator_node_timeout(&self) -> u64 { + self.validator_node_timeout + } + pub fn localnet() -> Vec { let difficulty_block_window = 90; let mut algos = HashMap::new(); @@ -325,6 +331,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: OutputType::all(), + validator_node_timeout: 100, }] } @@ -365,6 +372,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }] } @@ -407,7 +415,9 @@ impl ConsensusConstants { input_version_range, output_version_range, kernel_version_range, - permitted_output_types: Self::current_permitted_output_types(), + // igor is the first network to support the new output types + permitted_output_types: OutputType::all(), + validator_node_timeout: 100, }] } @@ -458,6 +468,7 @@ impl ConsensusConstants { output_version_range: output_version_range.clone(), kernel_version_range: kernel_version_range.clone(), permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }, ConsensusConstants { effective_from_height: 23000, @@ -481,6 +492,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }, ] } @@ -533,6 +545,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 50, }; let consensus_constants_2 = ConsensusConstants { effective_from_height: 23000, @@ -597,6 +610,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }] } diff --git a/base_layer/core/src/consensus/consensus_encoding.rs b/base_layer/core/src/consensus/consensus_encoding.rs index 2a998daa03..1917766f0d 100644 --- a/base_layer/core/src/consensus/consensus_encoding.rs +++ b/base_layer/core/src/consensus/consensus_encoding.rs @@ -30,10 +30,13 @@ mod hashing; mod integers; mod micro_tari; mod script; +mod string; mod vec; + use std::io; pub use hashing::{ConsensusHasher, DomainSeparatedConsensusHasher}; +pub use string::MaxSizeString; pub use vec::MaxSizeVec; pub use self::bytes::MaxSizeBytes; @@ -93,6 +96,16 @@ impl FromConsensusBytes for T { } } +pub fn read_byte(reader: &mut R) -> Result { + let mut buf = [0u8; 1]; + reader.read_exact(&mut buf)?; + Ok(buf[0]) +} + +pub fn write_byte(writer: &mut W, byte: u8) -> Result<(), io::Error> { + writer.write_all(&[byte]) +} + #[cfg(test)] pub mod test { use super::*; diff --git a/base_layer/core/src/consensus/consensus_encoding/bytes.rs b/base_layer/core/src/consensus/consensus_encoding/bytes.rs index 6e29da6753..a3a4385fe5 100644 --- a/base_layer/core/src/consensus/consensus_encoding/bytes.rs +++ b/base_layer/core/src/consensus/consensus_encoding/bytes.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::{ + cmp, convert::TryFrom, io, io::{Error, Read, Write}, @@ -28,6 +29,7 @@ use std::{ }; use integer_encoding::{VarInt, VarIntReader, VarIntWriter}; +use serde::{Deserialize, Serialize}; use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; @@ -47,10 +49,34 @@ impl ConsensusEncodingSized for Vec { } } +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)] pub struct MaxSizeBytes { inner: Vec, } +impl MaxSizeBytes { + pub fn into_vec(self) -> Vec { + self.inner + } + + pub fn from_bytes_checked>(bytes: T) -> Option { + let b = bytes.as_ref(); + if b.len() > MAX { + None + } else { + Some(Self { inner: b.to_vec() }) + } + } + + pub fn from_bytes_truncate>(bytes: T) -> Self { + let b = bytes.as_ref(); + let len = cmp::min(b.len(), MAX); + Self { + inner: b[..len].to_vec(), + } + } +} + impl From> for Vec { fn from(value: MaxSizeBytes) -> Self { value.inner @@ -68,6 +94,18 @@ impl TryFrom> for MaxSizeBytes { } } +impl ConsensusEncoding for MaxSizeBytes { + fn consensus_encode(&self, writer: &mut W) -> Result<(), io::Error> { + self.inner.consensus_encode(writer) + } +} + +impl ConsensusEncodingSized for MaxSizeBytes { + fn consensus_encode_exact_size(&self) -> usize { + self.inner.consensus_encode_exact_size() + } +} + impl ConsensusDecoding for MaxSizeBytes { fn consensus_decode(reader: &mut R) -> Result { let len = reader.read_varint()?; @@ -108,7 +146,8 @@ impl ConsensusEncoding for &[u8] { impl ConsensusEncodingSized for &[u8] { fn consensus_encode_exact_size(&self) -> usize { - self.len() + let len = self.len(); + len.required_space() + len } } @@ -139,7 +178,7 @@ mod test { use rand::{rngs::OsRng, RngCore}; use super::*; - use crate::consensus::{check_consensus_encoding_correctness, ToConsensusBytes}; + use crate::consensus::check_consensus_encoding_correctness; #[test] fn it_encodes_and_decodes_correctly() { @@ -147,9 +186,18 @@ mod test { OsRng.fill_bytes(&mut subject); check_consensus_encoding_correctness(subject).unwrap(); + // &[u8] consensus encoding + let mut buf = Vec::new(); + let slice = subject.as_slice(); + slice.consensus_encode(&mut buf).unwrap(); + assert_eq!(buf.len(), slice.consensus_encode_exact_size()); + let mut reader = buf.as_slice(); + let decoded: MaxSizeBytes<1024> = ConsensusDecoding::consensus_decode(&mut reader).unwrap(); + assert_eq!(&*decoded, slice); + assert!(reader.is_empty()); + // Get vec encoding with length byte - let encoded = subject.to_vec().to_consensus_bytes(); - let decoded = MaxSizeBytes::<1024>::consensus_decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(*decoded, *subject.as_slice()); + let subject = MaxSizeBytes::<1024>::from_bytes_checked(&subject).unwrap(); + check_consensus_encoding_correctness(subject).unwrap(); } } diff --git a/base_layer/core/src/consensus/consensus_encoding/string.rs b/base_layer/core/src/consensus/consensus_encoding/string.rs new file mode 100644 index 0000000000..41bb97cc1a --- /dev/null +++ b/base_layer/core/src/consensus/consensus_encoding/string.rs @@ -0,0 +1,215 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + convert::TryFrom, + fmt::Display, + io, + io::{Read, Write}, +}; + +use serde::{Deserialize, Serialize}; + +use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, MaxSizeBytes}; + +/// A string that can only be a up to MAX length long +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct MaxSizeString { + string: String, +} + +impl MaxSizeString { + pub fn from_str_checked(s: &str) -> Option { + if s.len() > MAX { + return None; + } + Some(Self { string: s.to_string() }) + } + + pub fn from_utf8_bytes_checked>(bytes: T) -> Option { + let b = bytes.as_ref(); + if b.len() > MAX { + return None; + } + + let s = String::from_utf8(b.to_vec()).ok()?; + Some(Self { string: s }) + } + + pub fn len(&self) -> usize { + self.string.len() + } + + pub fn is_empty(&self) -> bool { + self.string.is_empty() + } + + pub fn as_str(&self) -> &str { + &self.string + } + + pub fn into_string(self) -> String { + self.string + } +} + +impl TryFrom for MaxSizeString { + type Error = MaxSizeStringLengthError; + + fn try_from(value: String) -> Result { + if value.len() > MAX { + return Err(MaxSizeStringLengthError { + actual: value.len(), + expected: MAX, + }); + } + Ok(Self { string: value }) + } +} + +impl TryFrom<&str> for MaxSizeString { + type Error = MaxSizeStringLengthError; + + fn try_from(value: &str) -> Result { + if value.len() > MAX { + return Err(MaxSizeStringLengthError { + actual: value.len(), + expected: MAX, + }); + } + Ok(Self { + string: value.to_string(), + }) + } +} + +impl AsRef<[u8]> for MaxSizeString { + fn as_ref(&self) -> &[u8] { + self.string.as_ref() + } +} + +impl Display for MaxSizeString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.string) + } +} + +impl ConsensusEncoding for MaxSizeString { + fn consensus_encode(&self, writer: &mut W) -> Result<(), io::Error> { + self.string.as_bytes().consensus_encode(writer) + } +} + +impl ConsensusEncodingSized for MaxSizeString { + fn consensus_encode_exact_size(&self) -> usize { + self.string.as_bytes().consensus_encode_exact_size() + } +} + +impl ConsensusDecoding for MaxSizeString { + fn consensus_decode(reader: &mut R) -> Result { + let raw_bytes = MaxSizeBytes::::consensus_decode(reader)?; + let s = String::from_utf8(raw_bytes.into_vec()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8"))?; + Ok(Self { string: s }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Invalid String length: expected {expected}, got {actual}")] +pub struct MaxSizeStringLengthError { + expected: usize, + actual: usize, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::check_consensus_encoding_correctness; + + mod from_str_checked { + use super::*; + + #[test] + fn it_returns_none_if_size_exceeded() { + let s = MaxSizeString::<10>::from_str_checked("12345678901234567890"); + assert_eq!(s, None); + } + + #[test] + fn it_returns_some_if_size_in_bounds() { + let s = MaxSizeString::<0>::from_str_checked("").unwrap(); + assert_eq!(s.as_str(), ""); + assert_eq!(s.len(), 0); + + let s = MaxSizeString::<10>::from_str_checked("1234567890").unwrap(); + assert_eq!(s.as_str(), "1234567890"); + assert_eq!(s.len(), 10); + + let s = MaxSizeString::<10>::from_str_checked("1234").unwrap(); + assert_eq!(s.as_str(), "1234"); + assert_eq!(s.len(), 4); + + let s = MaxSizeString::<8>::from_str_checked("🚀🚀").unwrap(); + assert_eq!(s.as_str(), "🚀🚀"); + // 8 here because an emoji char take 4 bytes each + assert_eq!(s.len(), 8); + } + } + + mod from_utf8_bytes_checked { + use super::*; + + #[test] + fn it_returns_none_if_size_exceeded() { + let s = MaxSizeString::<10>::from_utf8_bytes_checked(&[0u8; 11]); + assert_eq!(s, None); + } + + #[test] + fn it_returns_some_if_size_in_bounds() { + let s = MaxSizeString::<12>::from_utf8_bytes_checked("💡🧭🛖".as_bytes()).unwrap(); + assert_eq!(s.as_str(), "💡🧭🛖"); + assert_eq!(s.len(), 12); + } + + #[test] + fn it_returns_none_if_invalid_utf8() { + let s = MaxSizeString::<10>::from_utf8_bytes_checked(&[255u8; 10]); + assert_eq!(s, None); + } + } + + mod consensus_encoding { + use super::*; + + #[test] + fn it_encodes_and_decodes_correctly() { + let s = MaxSizeString::<16>::from_utf8_bytes_checked("💡🧭🛖".as_bytes()).unwrap(); + check_consensus_encoding_correctness(s).unwrap(); + + let s = MaxSizeString::<0>::from_str_checked("").unwrap(); + check_consensus_encoding_correctness(s).unwrap(); + } + } +} diff --git a/base_layer/core/src/consensus/mod.rs b/base_layer/core/src/consensus/mod.rs index cff83e7f5f..595bf05942 100644 --- a/base_layer/core/src/consensus/mod.rs +++ b/base_layer/core/src/consensus/mod.rs @@ -33,6 +33,8 @@ mod consensus_encoding; #[cfg(test)] pub(crate) use consensus_encoding::test::check_consensus_encoding_correctness; pub use consensus_encoding::{ + read_byte, + write_byte, ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, @@ -40,6 +42,7 @@ pub use consensus_encoding::{ DomainSeparatedConsensusHasher, FromConsensusBytes, MaxSizeBytes, + MaxSizeString, MaxSizeVec, ToConsensusBytes, }; diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs index 087d948c66..570ec2de91 100644 --- a/base_layer/core/src/covenants/fields.rs +++ b/base_layer/core/src/covenants/fields.rs @@ -92,7 +92,7 @@ impl OutputField { Features => &output.features as &dyn Any, FeaturesOutputType => &output.features.output_type as &dyn Any, FeaturesMaturity => &output.features.maturity as &dyn Any, - FeaturesSideChainFeatures => &output.features.sidechain_features as &dyn Any, + FeaturesSideChainFeatures => &output.features.sidechain_feature as &dyn Any, FeaturesMetadata => &output.features.metadata as &dyn Any, }; val.downcast_ref::() @@ -109,7 +109,7 @@ impl OutputField { Features => output.features.to_consensus_bytes(), FeaturesOutputType => output.features.output_type.to_consensus_bytes(), FeaturesMaturity => output.features.maturity.to_consensus_bytes(), - FeaturesSideChainFeatures => output.features.sidechain_features.to_consensus_bytes(), + FeaturesSideChainFeatures => output.features.sidechain_feature.to_consensus_bytes(), FeaturesMetadata => output.features.metadata.to_consensus_bytes(), } } @@ -145,7 +145,7 @@ impl OutputField { .unwrap_or(false), FeaturesSideChainFeatures => input .features() - .map(|features| features.sidechain_features == output.features.sidechain_features) + .map(|features| features.sidechain_feature == output.features.sidechain_feature) .unwrap_or(false), FeaturesMetadata => input .features() @@ -228,7 +228,7 @@ impl OutputField { #[allow(dead_code)] #[allow(dead_code)] - pub fn features_sidechain_features() -> Self { + pub fn features_sidechain_feature() -> Self { OutputField::FeaturesSideChainFeatures } @@ -249,7 +249,7 @@ impl Display for OutputField { Covenant => write!(f, "field::covenant"), Features => write!(f, "field::features"), FeaturesOutputType => write!(f, "field::features_flags"), - FeaturesSideChainFeatures => write!(f, "field::features_sidechain_features"), + FeaturesSideChainFeatures => write!(f, "field::features_sidechain_feature"), FeaturesMetadata => write!(f, "field::features_metadata"), FeaturesMaturity => write!(f, "field::features_maturity"), } @@ -335,13 +335,14 @@ impl FromIterator for OutputFields { #[cfg(test)] mod test { + use tari_common_types::types::{Commitment, PublicKey}; use tari_script::script; use super::*; use crate::{ covenant, - covenants::test::{create_input, create_outputs}, + covenants::test::{create_input, create_outputs, make_sample_sidechain_feature}, transactions::{ test_helpers::UtxoTestParams, transaction_components::{OutputFeatures, OutputType, SpentOutput}, @@ -352,14 +353,15 @@ mod test { use super::*; mod is_eq { + use super::*; - use crate::transactions::transaction_components::SideChainFeatures; #[test] fn it_returns_true_if_eq() { + let side_chain_features = make_sample_sidechain_feature(); let output = create_outputs(1, UtxoTestParams { features: OutputFeatures { - sidechain_features: Some(Box::new(SideChainFeatures {})), + sidechain_feature: Some(side_chain_features), ..Default::default() }, script: script![Drop Nop], @@ -378,10 +380,7 @@ mod test { .is_eq(&output, &output.features.output_type) .unwrap()); assert!(OutputField::FeaturesSideChainFeatures - .is_eq(&output, &SideChainFeatures {}) - .unwrap()); - assert!(OutputField::FeaturesSideChainFeatures - .is_eq(&output, output.features.sidechain_features.as_ref().unwrap()) + .is_eq(&output, output.features.sidechain_feature.as_ref().unwrap()) .unwrap()); assert!(OutputField::FeaturesMetadata .is_eq(&output, &output.features.metadata) @@ -393,9 +392,10 @@ mod test { #[test] fn it_returns_false_if_not_eq() { + let side_chain_features = make_sample_sidechain_feature(); let output = create_outputs(1, UtxoTestParams { features: OutputFeatures { - sidechain_features: Some(Box::new(SideChainFeatures {})), + sidechain_feature: Some(side_chain_features), ..Default::default() }, script: script![Drop Nop], diff --git a/base_layer/core/src/covenants/filters/and.rs b/base_layer/core/src/covenants/filters/and.rs index 726b7e16e1..22aacff685 100644 --- a/base_layer/core/src/covenants/filters/and.rs +++ b/base_layer/core/src/covenants/filters/and.rs @@ -45,7 +45,6 @@ mod test { use crate::{ covenant, covenants::{filters::test::setup_filter_test, test::create_input}, - transactions::transaction_components::SideChainFeatures, }; #[test] @@ -56,12 +55,9 @@ mod test { let input = create_input(); let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { outputs[5].features.maturity = 42; - outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures {})); outputs[7].features.maturity = 42; - outputs[7].features.sidechain_features = Some(Box::new(SideChainFeatures {})); // Does not have maturity = 42 outputs[8].features.maturity = 123; - outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures {})); }); let mut output_set = OutputSet::new(&outputs); diff --git a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs index 1eaf6e9dc5..8a72b58c71 100644 --- a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs +++ b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs @@ -49,18 +49,18 @@ mod test { covenant, covenants::{ filters::test::setup_filter_test, - test::create_input, + test::{create_input, make_sample_sidechain_feature}, BaseLayerCovenantsDomain, COVENANTS_FIELD_HASHER_LABEL, }, - transactions::transaction_components::{OutputFeatures, SideChainFeatures}, + transactions::transaction_components::OutputFeatures, }; #[test] fn it_filters_outputs_with_fields_that_hash_to_given_hash() { let features = OutputFeatures { maturity: 42, - sidechain_features: Some(Box::new(SideChainFeatures {})), + sidechain_feature: Some(make_sample_sidechain_feature()), ..Default::default() }; let mut hasher = Challenge::new(); diff --git a/base_layer/core/src/covenants/filters/fields_preserved.rs b/base_layer/core/src/covenants/filters/fields_preserved.rs index 9b64b8a1a3..0f37390d0d 100644 --- a/base_layer/core/src/covenants/filters/fields_preserved.rs +++ b/base_layer/core/src/covenants/filters/fields_preserved.rs @@ -41,7 +41,7 @@ mod test { use crate::{ covenant, covenants::{filters::test::setup_filter_test, test::create_input}, - transactions::transaction_components::{OutputType, SideChainFeatures}, + transactions::transaction_components::OutputType, }; #[test] @@ -49,17 +49,13 @@ mod test { let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_output_type))); let mut input = create_input(); input.set_maturity(42).unwrap(); - input.features_mut().unwrap().sidechain_features = Some(Box::new(SideChainFeatures {})); input.features_mut().unwrap().output_type = OutputType::Standard; let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { outputs[5].features.maturity = 42; - outputs[5].features.sidechain_features = Some(Box::new(SideChainFeatures {})); outputs[5].features.output_type = OutputType::Standard; outputs[7].features.maturity = 42; outputs[7].features.output_type = OutputType::Standard; - outputs[7].features.sidechain_features = Some(Box::new(SideChainFeatures {})); outputs[8].features.maturity = 42; - outputs[8].features.sidechain_features = Some(Box::new(SideChainFeatures {})); outputs[8].features.output_type = OutputType::Coinbase; }); let mut output_set = OutputSet::new(&outputs); diff --git a/base_layer/core/src/covenants/test.rs b/base_layer/core/src/covenants/test.rs index 96a700ca99..677444a73d 100644 --- a/base_layer/core/src/covenants/test.rs +++ b/base_layer/core/src/covenants/test.rs @@ -20,13 +20,20 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::iter; +use std::{convert::TryInto, iter}; use crate::{ covenants::{context::CovenantContext, Covenant}, transactions::{ test_helpers::{TestParams, UtxoTestParams}, - transaction_components::{TransactionInput, TransactionOutput}, + transaction_components::{ + BuildInfo, + CodeTemplateRegistration, + SideChainFeature, + TemplateType, + TransactionInput, + TransactionOutput, + }, }, }; @@ -50,3 +57,20 @@ pub fn create_context<'a>(covenant: &Covenant, input: &'a TransactionInput, bloc let tokens = covenant.tokens().to_vec(); CovenantContext::new(tokens.into(), input, block_height) } + +pub fn make_sample_sidechain_feature() -> SideChainFeature { + let template_reg = CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: "test".to_string().try_into().unwrap(), + template_version: 0, + template_type: TemplateType::Wasm { abi_version: 0 }, + build_info: BuildInfo { + repo_url: "https://github.com/tari-project/tari.git".try_into().unwrap(), + commit_hash: Default::default(), + }, + binary_sha: Default::default(), + binary_url: "https://github.com/tari-project/tari.git".try_into().unwrap(), + }; + SideChainFeature::TemplateRegistration(template_reg) +} diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 472f87080b..9f197d737a 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -109,6 +109,15 @@ mod domain_hashing { ); pub type InputMmrHasherBlake256 = DomainSeparatedHasher; pub type PrunedInputMmr = MerkleMountainRange; + + hash_domain!( + ValidatorNodeMmrHashDomain, + "com.tari.tari_project.base_layer.core.validator_node_mmr", + 1 + ); + pub type ValidatorNodeMmrHasherBlake256 = DomainSeparatedHasher; + pub type ValidatorNodeMmr = MerkleMountainRange>; } + #[cfg(feature = "base_node")] pub use domain_hashing::*; diff --git a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs index da95343643..d78c6b8022 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs @@ -307,6 +307,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -363,6 +364,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -415,6 +417,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let count = 1 + (u16::try_from(block.tx_hashes.len()).unwrap()); let mut hashes = Vec::with_capacity(count as usize); @@ -466,6 +469,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let hash = Hash::null(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -521,6 +525,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -572,6 +577,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let monero_data = MoneroPowData { header: Default::default(), @@ -614,6 +620,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_mr: FixedHash::zero(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); diff --git a/base_layer/core/src/proto/block.proto b/base_layer/core/src/proto/block.proto index d42555b2f6..452ef7a2cb 100644 --- a/base_layer/core/src/proto/block.proto +++ b/base_layer/core/src/proto/block.proto @@ -51,6 +51,8 @@ message BlockHeader { uint64 output_mmr_size = 14; // Sum of script offsets for all kernels in this block. bytes total_script_offset = 15; + // Merkle root of validator nodes + bytes validator_node_merkle_root = 16; } // A Tari block. Blocks are linked together into a blockchain. diff --git a/base_layer/core/src/proto/block_header.rs b/base_layer/core/src/proto/block_header.rs index 47fa2e20c2..a49668ec85 100644 --- a/base_layer/core/src/proto/block_header.rs +++ b/base_layer/core/src/proto/block_header.rs @@ -68,6 +68,7 @@ impl TryFrom for BlockHeader { total_script_offset, nonce: header.nonce, pow, + validator_node_mr: FixedHash::try_from(header.validator_node_merkle_root).map_err(|err| err.to_string())?, }) } } @@ -90,6 +91,7 @@ impl From for proto::BlockHeader { pow: Some(proto::ProofOfWork::from(header.pow)), kernel_mmr_size: header.kernel_mmr_size, output_mmr_size: header.output_mmr_size, + validator_node_merkle_root: header.validator_node_mr.to_vec(), } } } diff --git a/base_layer/core/src/proto/mod.rs b/base_layer/core/src/proto/mod.rs index 41209a82b2..d78b990bb6 100644 --- a/base_layer/core/src/proto/mod.rs +++ b/base_layer/core/src/proto/mod.rs @@ -52,4 +52,6 @@ mod block; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] mod block_header; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] +mod sidechain_feature; +#[cfg(any(feature = "base_node", feature = "base_node_proto"))] mod utils; diff --git a/base_layer/core/src/proto/sidechain_feature.proto b/base_layer/core/src/proto/sidechain_feature.proto new file mode 100644 index 0000000000..4e3d1d9487 --- /dev/null +++ b/base_layer/core/src/proto/sidechain_feature.proto @@ -0,0 +1,45 @@ +// Copyright 2022 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +syntax = "proto3"; + +import "types.proto"; + +package tari.types; + +message SideChainFeature { + oneof side_chain_feature { + ValidatorNodeRegistration validator_node_registration = 1; + TemplateRegistration template_registration = 2; + } +} + +message ValidatorNodeRegistration { + bytes public_key = 1; + Signature signature = 2; +} + +message TemplateRegistration { + bytes author_public_key = 1; + Signature author_signature = 2; + string template_name = 3; + uint32 template_version = 4; + TemplateType template_type = 5; + BuildInfo build_info = 6; + bytes binary_sha = 7; + string binary_url = 8; +} + +message TemplateType { + oneof template_type { + WasmInfo wasm = 1; + } +} + message WasmInfo { + uint32 abi_version = 1; + } + + message BuildInfo { + string repo_url = 1; + bytes commit_hash = 2; +} diff --git a/base_layer/core/src/proto/sidechain_feature.rs b/base_layer/core/src/proto/sidechain_feature.rs new file mode 100644 index 0000000000..7f3998c67c --- /dev/null +++ b/base_layer/core/src/proto/sidechain_feature.rs @@ -0,0 +1,196 @@ +// Copyright 2019, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Impls for sidechain_feature proto + +use std::convert::{TryFrom, TryInto}; + +use tari_common_types::types::{PublicKey, Signature}; +use tari_utilities::ByteArray; + +use crate::{ + consensus::MaxSizeString, + proto, + transactions::transaction_components::{ + BuildInfo, + CodeTemplateRegistration, + SideChainFeature, + TemplateType, + ValidatorNodeRegistration, + }, +}; + +//---------------------------------- SideChainFeature --------------------------------------------// +impl From for proto::types::SideChainFeature { + fn from(value: SideChainFeature) -> Self { + Self { + side_chain_feature: Some(value.into()), + } + } +} + +impl From for proto::types::side_chain_feature::SideChainFeature { + fn from(value: SideChainFeature) -> Self { + match value { + SideChainFeature::ValidatorNodeRegistration(template_reg) => { + proto::types::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(template_reg.into()) + }, + SideChainFeature::TemplateRegistration(template_reg) => { + proto::types::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg.into()) + }, + } + } +} + +impl TryFrom for SideChainFeature { + type Error = String; + + fn try_from(features: proto::types::side_chain_feature::SideChainFeature) -> Result { + match features { + proto::types::side_chain_feature::SideChainFeature::ValidatorNodeRegistration(vn_reg) => { + Ok(SideChainFeature::ValidatorNodeRegistration(vn_reg.try_into()?)) + }, + proto::types::side_chain_feature::SideChainFeature::TemplateRegistration(template_reg) => { + Ok(SideChainFeature::TemplateRegistration(template_reg.try_into()?)) + }, + } + } +} + +// -------------------------------- ValidatorNodeRegistration -------------------------------- // +impl TryFrom for ValidatorNodeRegistration { + type Error = String; + + fn try_from(value: proto::types::ValidatorNodeRegistration) -> Result { + Ok(Self { + public_key: PublicKey::from_bytes(&value.public_key).map_err(|e| e.to_string())?, + signature: value + .signature + .map(Signature::try_from) + .ok_or("signature not provided")??, + }) + } +} + +impl From for proto::types::ValidatorNodeRegistration { + fn from(value: ValidatorNodeRegistration) -> Self { + Self { + public_key: value.public_key.to_vec(), + signature: Some(value.signature.into()), + } + } +} + +// -------------------------------- TemplateRegistration -------------------------------- // +impl TryFrom for CodeTemplateRegistration { + type Error = String; + + fn try_from(value: proto::types::TemplateRegistration) -> Result { + Ok(Self { + author_public_key: PublicKey::from_bytes(&value.author_public_key).map_err(|e| e.to_string())?, + author_signature: value + .author_signature + .map(Signature::try_from) + .ok_or("author_signature not provided")??, + template_name: MaxSizeString::try_from(value.template_name).map_err(|e| e.to_string())?, + template_version: value + .template_version + .try_into() + .map_err(|_| "Invalid template version")?, + template_type: value + .template_type + .map(TryFrom::try_from) + .ok_or("Template type not provided")??, + build_info: value + .build_info + .map(TryFrom::try_from) + .ok_or("Build info not provided")??, + binary_sha: value.binary_sha.try_into().map_err(|_| "Invalid commit sha")?, + binary_url: MaxSizeString::try_from(value.binary_url).map_err(|e| e.to_string())?, + }) + } +} + +impl From for proto::types::TemplateRegistration { + fn from(value: CodeTemplateRegistration) -> Self { + Self { + author_public_key: value.author_public_key.to_vec(), + author_signature: Some(value.author_signature.into()), + template_name: value.template_name.to_string(), + template_version: u32::from(value.template_version), + template_type: Some(value.template_type.into()), + build_info: Some(value.build_info.into()), + binary_sha: value.binary_sha.to_vec(), + binary_url: value.binary_url.to_string(), + } + } +} + +// -------------------------------- TemplateType -------------------------------- // +impl TryFrom for TemplateType { + type Error = String; + + fn try_from(value: proto::types::TemplateType) -> Result { + let template_type = value.template_type.ok_or("Template type not provided")?; + match template_type { + proto::types::template_type::TemplateType::Wasm(wasm) => Ok(TemplateType::Wasm { + abi_version: wasm.abi_version.try_into().map_err(|_| "abi_version overflowed")?, + }), + } + } +} + +impl From for proto::types::TemplateType { + fn from(value: TemplateType) -> Self { + match value { + TemplateType::Wasm { abi_version } => Self { + template_type: Some(proto::types::template_type::TemplateType::Wasm( + proto::types::WasmInfo { + abi_version: abi_version.into(), + }, + )), + }, + } + } +} + +// -------------------------------- BuildInfo -------------------------------- // + +impl TryFrom for BuildInfo { + type Error = String; + + fn try_from(value: proto::types::BuildInfo) -> Result { + Ok(Self { + repo_url: value.repo_url.try_into().map_err(|_| "Invalid repo url")?, + commit_hash: value.commit_hash.try_into().map_err(|_| "Invalid commit hash")?, + }) + } +} + +impl From for proto::types::BuildInfo { + fn from(value: BuildInfo) -> Self { + Self { + repo_url: value.repo_url.into_string(), + commit_hash: value.commit_hash.into_vec(), + } + } +} diff --git a/base_layer/core/src/proto/transaction.proto b/base_layer/core/src/proto/transaction.proto index 231580af57..a34aba7226 100644 --- a/base_layer/core/src/proto/transaction.proto +++ b/base_layer/core/src/proto/transaction.proto @@ -4,6 +4,7 @@ syntax = "proto3"; import "types.proto"; +import "sidechain_feature.proto"; package tari.types; @@ -97,26 +98,7 @@ message OutputFeatures { // require a min maturity of the Coinbase_lock_height, this should be checked on receiving new blocks. uint64 maturity = 3; bytes metadata = 4; - SideChainFeatures sidechain_features = 6; -} - -message SideChainFeatures {} - - -message TemplateParameter { - uint32 template_id = 1; - uint32 template_data_version = 2; - bytes template_data = 3; -} - -message PublicFunction { - bytes name = 1; - FunctionRef function = 2; -} - -message FunctionRef { - bytes template_id = 1; - uint32 function_id = 2; + SideChainFeature sidechain_feature = 5; } // The components of the block or transaction. The same struct can be used for either, since in Mimblewimble, diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index 8639b03c46..5a8b2beff3 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -44,7 +44,7 @@ use crate::{ OutputFeatures, OutputFeaturesVersion, OutputType, - SideChainFeatures, + SideChainFeature, Transaction, TransactionInput, TransactionInputVersion, @@ -294,9 +294,10 @@ impl TryFrom for OutputFeatures { type Error = String; fn try_from(features: proto::types::OutputFeatures) -> Result { - let sidechain_features = features - .sidechain_features - .map(SideChainFeatures::try_from) + let sidechain_feature = features + .sidechain_feature + .and_then(|features| features.side_chain_feature) + .map(SideChainFeature::try_from) .transpose()?; let flags = features @@ -311,7 +312,7 @@ impl TryFrom for OutputFeatures { OutputType::from_byte(flags).ok_or_else(|| "Invalid or unrecognised output type".to_string())?, features.maturity, features.metadata, - sidechain_features, + sidechain_feature, )) } } @@ -323,26 +324,11 @@ impl From for proto::types::OutputFeatures { maturity: features.maturity, metadata: features.metadata, version: features.version as u32, - sidechain_features: features.sidechain_features.map(|v| *v).map(Into::into), + sidechain_feature: features.sidechain_feature.map(Into::into), } } } -//---------------------------------- SideChainFeatures --------------------------------------------// -impl From for proto::types::SideChainFeatures { - fn from(_value: SideChainFeatures) -> Self { - Self {} - } -} - -impl TryFrom for SideChainFeatures { - type Error = String; - - fn try_from(_features: proto::types::SideChainFeatures) -> Result { - Ok(Self {}) - } -} - //---------------------------------- AggregateBody --------------------------------------------// impl TryFrom for AggregateBody { diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 072364f788..6641c2d86f 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -32,7 +32,7 @@ use croaring::Bitmap; use tari_common::configuration::Network; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{Commitment, HashOutput, Signature}, + types::{Commitment, HashOutput, PublicKey, Signature}, }; use tari_storage::lmdb_store::LMDBConfig; use tari_test_utils::paths::create_temporary_data_path; @@ -66,6 +66,7 @@ use crate::{ MmrTree, PrunedOutput, Reorg, + TemplateRegistrationEntry, UtxoMinedInfo, Validators, }, @@ -159,17 +160,19 @@ pub struct TempDatabase { impl TempDatabase { pub fn new() -> Self { let temp_path = create_temporary_data_path(); + let rules = create_consensus_rules(); Self { - db: Some(create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap()), + db: Some(create_lmdb_database(&temp_path, LMDBConfig::default(), rules).unwrap()), path: temp_path, delete_on_drop: true, } } pub fn from_path>(temp_path: P) -> Self { + let rules = create_consensus_rules(); Self { - db: Some(create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap()), + db: Some(create_lmdb_database(&temp_path, LMDBConfig::default(), rules).unwrap()), path: temp_path.as_ref().to_path_buf(), delete_on_drop: true, } @@ -410,6 +413,25 @@ impl BlockchainBackend for TempDatabase { fn fetch_all_reorgs(&self) -> Result, ChainStorageError> { self.db.as_ref().unwrap().fetch_all_reorgs() } + + fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError> { + self.db.as_ref().unwrap().fetch_active_validator_nodes(height) + } + + fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError> { + self.db.as_ref().unwrap().get_shard_key(height, public_key) + } + + fn fetch_template_registrations( + &self, + start_height: u64, + end_height: u64, + ) -> Result, ChainStorageError> { + self.db + .as_ref() + .unwrap() + .fetch_template_registrations(start_height, end_height) + } } pub fn create_chained_blocks>( diff --git a/base_layer/core/src/transactions/transaction_components/error.rs b/base_layer/core/src/transactions/transaction_components/error.rs index 1ef013fb59..43e0913ce9 100644 --- a/base_layer/core/src/transactions/transaction_components/error.rs +++ b/base_layer/core/src/transactions/transaction_components/error.rs @@ -73,6 +73,8 @@ pub enum TransactionError { ConsensusEncodingError(String), #[error("Committee contains too many members: contains {len} members but maximum is {max}")] InvalidCommitteeLength { len: usize, max: usize }, + #[error("Missing validator node signature")] + MissingValidatorNodeSignature, } impl From for TransactionError { diff --git a/base_layer/core/src/transactions/transaction_components/kernel_features.rs b/base_layer/core/src/transactions/transaction_components/kernel_features.rs index f85efad411..68856f8b60 100644 --- a/base_layer/core/src/transactions/transaction_components/kernel_features.rs +++ b/base_layer/core/src/transactions/transaction_components/kernel_features.rs @@ -38,6 +38,8 @@ bitflags! { const COINBASE_KERNEL = 1u8; /// Burned output transaction const BURN_KERNEL = 2u8; + /// Validator node registration transaction + const VALIDATOR_NODE_REGISTRATION = 3u8; } } @@ -56,6 +58,10 @@ impl KernelFeatures { pub fn is_burned(&self) -> bool { self.contains(KernelFeatures::BURN_KERNEL) } + + pub fn create_validator_node_registration() -> KernelFeatures { + KernelFeatures::VALIDATOR_NODE_REGISTRATION + } } impl Default for KernelFeatures { diff --git a/base_layer/core/src/transactions/transaction_components/output_features.rs b/base_layer/core/src/transactions/transaction_components/output_features.rs index f18dfc2ac9..e7c75d94de 100644 --- a/base_layer/core/src/transactions/transaction_components/output_features.rs +++ b/base_layer/core/src/transactions/transaction_components/output_features.rs @@ -29,11 +29,17 @@ use std::{ }; use serde::{Deserialize, Serialize}; +use tari_common_types::types::{PublicKey, Signature}; use super::OutputFeaturesVersion; use crate::{ consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, MaxSizeBytes}, - transactions::transaction_components::{side_chain::SideChainFeatures, OutputType}, + transactions::transaction_components::{ + side_chain::SideChainFeature, + CodeTemplateRegistration, + OutputType, + ValidatorNodeRegistration, + }, }; /// Options for UTXO's @@ -46,24 +52,23 @@ pub struct OutputFeatures { /// require a min maturity of the Coinbase_lock_height, this should be checked on receiving new blocks. pub maturity: u64, pub metadata: Vec, - pub sidechain_features: Option>, + pub sidechain_feature: Option, } impl OutputFeatures { pub fn new( version: OutputFeaturesVersion, - flags: OutputType, + output_type: OutputType, maturity: u64, metadata: Vec, - sidechain_features: Option, + sidechain_feature: Option, ) -> OutputFeatures { - let boxed_sidechain_features = sidechain_features.map(Box::new); OutputFeatures { version, - output_type: flags, + output_type, maturity, metadata, - sidechain_features: boxed_sidechain_features, + sidechain_feature, } } @@ -71,14 +76,14 @@ impl OutputFeatures { flags: OutputType, maturity: u64, metadata: Vec, - sidechain_features: Option, + sidechain_feature: Option, ) -> OutputFeatures { OutputFeatures::new( OutputFeaturesVersion::get_current_version(), flags, maturity, metadata, - sidechain_features, + sidechain_feature, ) } @@ -98,6 +103,29 @@ impl OutputFeatures { } } + /// Creates template registration output features + pub fn for_template_registration(template_registration: CodeTemplateRegistration) -> OutputFeatures { + OutputFeatures { + output_type: OutputType::CodeTemplateRegistration, + sidechain_feature: Some(SideChainFeature::TemplateRegistration(template_registration)), + ..Default::default() + } + } + + pub fn for_validator_node_registration( + validator_node_public_key: PublicKey, + validator_node_signature: Signature, + ) -> OutputFeatures { + OutputFeatures { + output_type: OutputType::ValidatorNodeRegistration, + sidechain_feature: Some(SideChainFeature::ValidatorNodeRegistration(ValidatorNodeRegistration { + public_key: validator_node_public_key, + signature: validator_node_signature, + })), + ..Default::default() + } + } + pub fn is_coinbase(&self) -> bool { matches!(self.output_type, OutputType::Coinbase) } @@ -108,7 +136,7 @@ impl ConsensusEncoding for OutputFeatures { self.version.consensus_encode(writer)?; self.maturity.consensus_encode(writer)?; self.output_type.consensus_encode(writer)?; - self.sidechain_features.consensus_encode(writer)?; + self.sidechain_feature.consensus_encode(writer)?; self.metadata.consensus_encode(writer)?; Ok(()) @@ -124,14 +152,14 @@ impl ConsensusDecoding for OutputFeatures { let version = OutputFeaturesVersion::consensus_decode(reader)?; let maturity = u64::consensus_decode(reader)?; let flags = OutputType::consensus_decode(reader)?; - let sidechain_features = > as ConsensusDecoding>::consensus_decode(reader)?; + let sidechain_feature = ConsensusDecoding::consensus_decode(reader)?; const MAX_METADATA_SIZE: usize = 1024; let metadata = as ConsensusDecoding>::consensus_decode(reader)?; Ok(Self { version, output_type: flags, maturity, - sidechain_features, + sidechain_feature, metadata: metadata.into(), }) } @@ -167,17 +195,43 @@ impl Display for OutputFeatures { #[cfg(test)] mod test { + use std::convert::TryInto; + + use tari_utilities::hex::from_hex; + use super::*; - use crate::consensus::check_consensus_encoding_correctness; + use crate::{ + consensus::{check_consensus_encoding_correctness, MaxSizeString}, + transactions::transaction_components::{BuildInfo, TemplateType}, + }; - #[allow(clippy::too_many_lines)] fn make_fully_populated_output_features(version: OutputFeaturesVersion) -> OutputFeatures { OutputFeatures { version, output_type: OutputType::Standard, maturity: u64::MAX, metadata: vec![1; 1024], - sidechain_features: Some(Box::new(SideChainFeatures {})), + sidechain_feature: Some(SideChainFeature::TemplateRegistration(CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: MaxSizeString::from_str_checked("🚀🚀🚀🚀🚀🚀🚀🚀").unwrap(), + template_version: 1, + template_type: TemplateType::Wasm { abi_version: 123 }, + build_info: BuildInfo { + repo_url: "/dns/github.com/https/tari_project/wasm_examples".try_into().unwrap(), + commit_hash: from_hex("ea29c9f92973fb7eda913902ff6173c62cb1e5df") + .unwrap() + .try_into() + .unwrap(), + }, + binary_sha: from_hex("c93747637517e3de90839637f0ce1ab7c8a3800b") + .unwrap() + .try_into() + .unwrap(), + binary_url: "/dns4/github.com/https/tari_project/wasm_examples/releases/download/v0.0.6/coin.zip" + .try_into() + .unwrap(), + })), } } @@ -193,7 +247,7 @@ mod test { #[test] fn it_encodes_and_decodes_correctly_in_none_case() { let mut subject = make_fully_populated_output_features(OutputFeaturesVersion::V1); - subject.sidechain_features = None; + subject.sidechain_feature = None; check_consensus_encoding_correctness(subject).unwrap(); } } diff --git a/base_layer/core/src/transactions/transaction_components/output_type.rs b/base_layer/core/src/transactions/transaction_components/output_type.rs index cba40dcbf3..1927d8c3e8 100644 --- a/base_layer/core/src/transactions/transaction_components/output_type.rs +++ b/base_layer/core/src/transactions/transaction_components/output_type.rs @@ -44,6 +44,10 @@ pub enum OutputType { Coinbase = 1, /// Output is a burned output and can not be spent ever. Burn = 2, + /// Output defines a validator node registration + ValidatorNodeRegistration = 3, + /// Output defines a new re-usable code template. + CodeTemplateRegistration = 4, } impl OutputType { @@ -59,7 +63,20 @@ impl OutputType { } pub const fn all() -> &'static [Self] { - &[OutputType::Standard, OutputType::Coinbase, OutputType::Burn] + &[ + OutputType::Standard, + OutputType::Coinbase, + OutputType::Burn, + OutputType::ValidatorNodeRegistration, + OutputType::CodeTemplateRegistration, + ] + } + + pub fn is_sidechain_type(&self) -> bool { + matches!( + self, + OutputType::ValidatorNodeRegistration | OutputType::CodeTemplateRegistration + ) } } @@ -113,7 +130,9 @@ mod tests { assert_eq!(OutputType::from_byte(0), Some(OutputType::Standard)); assert_eq!(OutputType::from_byte(1), Some(OutputType::Coinbase)); assert_eq!(OutputType::from_byte(2), Some(OutputType::Burn)); - assert_eq!(OutputType::from_byte(255), None); + assert_eq!(OutputType::from_byte(3), Some(OutputType::ValidatorNodeRegistration)); + assert_eq!(OutputType::from_byte(4), Some(OutputType::CodeTemplateRegistration)); + assert_eq!(OutputType::from_byte(108), None); } #[test] diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs index 22ff243a37..4922052163 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs @@ -20,13 +20,14 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -mod sidechain_features; -pub use sidechain_features::SideChainFeatures; -// Length of FixedString -pub const FIXED_STR_LEN: usize = 32; -pub type FixedString = [u8; FIXED_STR_LEN]; +mod sidechain_feature; +pub use sidechain_feature::SideChainFeature; +mod template_registration; +mod validator_node_registration; use tari_crypto::{hash::blake2::Blake256, hash_domain, hashing::DomainSeparatedHasher}; +pub use template_registration::{BuildInfo, CodeTemplateRegistration, TemplateType}; +pub use validator_node_registration::ValidatorNodeRegistration; hash_domain!( ContractAcceptanceHashDomain, diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs b/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs new file mode 100644 index 0000000000..1fb81f2801 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_feature.rs @@ -0,0 +1,136 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io::{Error, ErrorKind, Read, Write}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + consensus::{read_byte, write_byte, ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}, + transactions::transaction_components::{CodeTemplateRegistration, ValidatorNodeRegistration}, +}; + +#[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq)] +pub enum SideChainFeature { + ValidatorNodeRegistration(ValidatorNodeRegistration), + TemplateRegistration(CodeTemplateRegistration), +} + +impl SideChainFeature { + pub fn template_registration(&self) -> Option<&CodeTemplateRegistration> { + match self { + Self::TemplateRegistration(v) => Some(v), + _ => None, + } + } + + pub fn validator_node_registration(&self) -> Option<&ValidatorNodeRegistration> { + match self { + Self::ValidatorNodeRegistration(v) => Some(v), + _ => None, + } + } + + pub fn as_byte(&self) -> u8 { + #[allow(clippy::enum_glob_use)] + use SideChainFeature::*; + match self { + ValidatorNodeRegistration(_) => 0x01, + TemplateRegistration(_) => 0x02, + } + } +} + +impl ConsensusEncoding for SideChainFeature { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + #[allow(clippy::enum_glob_use)] + use SideChainFeature::*; + write_byte(writer, self.as_byte())?; + match self { + ValidatorNodeRegistration(validator_node_registration) => { + validator_node_registration.consensus_encode(writer)?; + }, + TemplateRegistration(template_registration) => { + template_registration.consensus_encode(writer)?; + }, + } + Ok(()) + } +} + +impl ConsensusEncodingSized for SideChainFeature {} + +impl ConsensusDecoding for SideChainFeature { + fn consensus_decode(reader: &mut R) -> Result { + #[allow(clippy::enum_glob_use)] + use SideChainFeature::*; + let byte = read_byte(reader)?; + match byte { + 0x01 => Ok(ValidatorNodeRegistration(ConsensusDecoding::consensus_decode(reader)?)), + 0x02 => Ok(TemplateRegistration(ConsensusDecoding::consensus_decode(reader)?)), + _ => Err(Error::new( + ErrorKind::InvalidData, + format!("Invalid SideChainFeatures byte '{}'", byte), + )), + } + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use tari_utilities::hex::from_hex; + + use super::*; + use crate::{ + consensus::{check_consensus_encoding_correctness, MaxSizeString}, + transactions::transaction_components::{BuildInfo, TemplateType}, + }; + + #[test] + fn it_encodes_and_decodes_correctly() { + let subject = SideChainFeature::TemplateRegistration(CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: MaxSizeString::from_str_checked("🚀🚀🚀🚀🚀🚀🚀🚀").unwrap(), + template_version: 1, + template_type: TemplateType::Wasm { abi_version: 123 }, + build_info: BuildInfo { + repo_url: "/dns/github.com/https/tari_project/wasm_examples".try_into().unwrap(), + commit_hash: from_hex("ea29c9f92973fb7eda913902ff6173c62cb1e5df") + .unwrap() + .try_into() + .unwrap(), + }, + binary_sha: from_hex("c93747637517e3de90839637f0ce1ab7c8a3800b") + .unwrap() + .try_into() + .unwrap(), + binary_url: "/dns4/github.com/https/tari_project/wasm_examples/releases/download/v0.0.6/coin.zip" + .try_into() + .unwrap(), + }); + + check_consensus_encoding_correctness(subject).unwrap(); + } +} diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs b/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs new file mode 100644 index 0000000000..83f67156f6 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs @@ -0,0 +1,186 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io::{Error, ErrorKind, Read, Write}; + +use serde::{Deserialize, Serialize}; +use tari_common_types::types::{PublicKey, Signature}; + +use crate::consensus::{ + read_byte, + ConsensusDecoding, + ConsensusEncoding, + ConsensusEncodingSized, + MaxSizeBytes, + MaxSizeString, +}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub struct CodeTemplateRegistration { + pub author_public_key: PublicKey, + pub author_signature: Signature, + pub template_name: MaxSizeString<32>, + pub template_version: u16, + pub template_type: TemplateType, + pub build_info: BuildInfo, + pub binary_sha: MaxSizeBytes<32>, + pub binary_url: MaxSizeString<255>, +} + +impl ConsensusEncoding for CodeTemplateRegistration { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + self.author_public_key.consensus_encode(writer)?; + self.author_signature.consensus_encode(writer)?; + self.template_name.consensus_encode(writer)?; + self.template_version.consensus_encode(writer)?; + self.template_type.consensus_encode(writer)?; + self.build_info.consensus_encode(writer)?; + self.binary_sha.consensus_encode(writer)?; + self.binary_url.consensus_encode(writer)?; + Ok(()) + } +} + +impl ConsensusEncodingSized for CodeTemplateRegistration {} + +impl ConsensusDecoding for CodeTemplateRegistration { + fn consensus_decode(reader: &mut R) -> Result { + let author_public_key = PublicKey::consensus_decode(reader)?; + let author_signature = Signature::consensus_decode(reader)?; + let template_name = MaxSizeString::consensus_decode(reader)?; + let template_version = u16::consensus_decode(reader)?; + let template_type = TemplateType::consensus_decode(reader)?; + let build_info = BuildInfo::consensus_decode(reader)?; + let binary_sha = MaxSizeBytes::consensus_decode(reader)?; + let binary_url = MaxSizeString::consensus_decode(reader)?; + + Ok(CodeTemplateRegistration { + author_public_key, + author_signature, + template_name, + template_version, + template_type, + build_info, + binary_sha, + binary_url, + }) + } +} + +// -------------------------------- TemplateType -------------------------------- // + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub enum TemplateType { + /// Indicates that the template is a WASM module + Wasm { abi_version: u16 }, +} + +impl TemplateType { + fn as_type_byte(&self) -> u8 { + match self { + TemplateType::Wasm { .. } => 0, + } + } +} + +impl ConsensusEncoding for TemplateType { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write_all(&[self.as_type_byte()])?; + match self { + TemplateType::Wasm { abi_version } => { + abi_version.consensus_encode(writer)?; + }, + } + + Ok(()) + } +} + +impl ConsensusEncodingSized for TemplateType {} + +impl ConsensusDecoding for TemplateType { + fn consensus_decode(reader: &mut R) -> Result { + let type_byte = read_byte(reader)?; + match type_byte { + 0 => { + let abi_version = u16::consensus_decode(reader)?; + Ok(TemplateType::Wasm { abi_version }) + }, + _ => Err(Error::new(ErrorKind::InvalidData, "Invalid template type")), + } + } +} + +// -------------------------------- BuildInfo -------------------------------- // + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub struct BuildInfo { + pub repo_url: MaxSizeString<255>, + pub commit_hash: MaxSizeBytes<32>, +} + +impl ConsensusEncoding for BuildInfo { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + self.repo_url.consensus_encode(writer)?; + self.commit_hash.consensus_encode(writer)?; + Ok(()) + } +} + +impl ConsensusEncodingSized for BuildInfo {} + +impl ConsensusDecoding for BuildInfo { + fn consensus_decode(reader: &mut R) -> Result { + let repo_url = MaxSizeString::consensus_decode(reader)?; + let commit_hash = MaxSizeBytes::consensus_decode(reader)?; + Ok(Self { repo_url, commit_hash }) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use super::*; + use crate::consensus::check_consensus_encoding_correctness; + + #[test] + fn it_encodes_and_decodes_correctly() { + let subject = CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: "🐢 all the way down".try_into().unwrap(), + template_version: 0xff, + template_type: TemplateType::Wasm { abi_version: 0xffff }, + build_info: BuildInfo { + repo_url: "https://github.com/tari-project/wasm_template.git".try_into().unwrap(), + commit_hash: Default::default(), + }, + binary_sha: Default::default(), + binary_url: "/dns4/github.com/tcp/443/http/tari-project/wasm_examples/releases/download/v0.0.6/coin.zip" + .try_into() + .unwrap(), + }; + + check_consensus_encoding_correctness(subject).unwrap(); + } +} diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs new file mode 100644 index 0000000000..c37fb83689 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs @@ -0,0 +1,137 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io::{Error, Read, Write}; + +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use tari_common_types::types::{FixedHash, PrivateKey, PublicKey, Signature}; +use tari_crypto::keys::PublicKey as PublicKeyT; + +use crate::{ + consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, DomainSeparatedConsensusHasher}, + transactions::TransactionHashDomain, +}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub struct ValidatorNodeRegistration { + pub public_key: PublicKey, + pub signature: Signature, +} + +impl ValidatorNodeRegistration { + pub fn is_valid_signature_for(&self, msg: &[u8]) -> bool { + let challenge = Self::construct_challenge(&self.public_key, self.signature.get_public_nonce(), msg); + self.signature.verify_challenge(&self.public_key, &*challenge) + } + + pub fn new_signed(private_key: &PrivateKey, msg: &[u8]) -> Self { + let (secret_nonce, public_nonce) = PublicKey::random_keypair(&mut OsRng); + let public_key = PublicKey::from_secret_key(private_key); + let challenge = Self::construct_challenge(&public_key, &public_nonce, msg); + let signature = Signature::sign(private_key.clone(), secret_nonce, &*challenge) + .expect("Sign cannot fail with 32-byte challenge and a RistrettoPublicKey"); + Self { public_key, signature } + } + + pub fn construct_challenge(public_key: &PublicKey, public_nonce: &PublicKey, msg: &[u8]) -> FixedHash { + DomainSeparatedConsensusHasher::::new("validator_node_registration") + .chain(public_key) + .chain(public_nonce) + .chain(&msg) + .finalize() + .into() + } + + pub fn derive_shard_key(&self, block_hash: &FixedHash) -> [u8; 32] { + DomainSeparatedConsensusHasher::::new("validator_node_root") + // + .chain(self) + .chain(block_hash) + .finalize() + } +} + +impl ConsensusEncoding for ValidatorNodeRegistration { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + self.public_key.consensus_encode(writer)?; + self.signature.consensus_encode(writer)?; + Ok(()) + } +} + +impl ConsensusEncodingSized for ValidatorNodeRegistration { + fn consensus_encode_exact_size(&self) -> usize { + self.public_key.consensus_encode_exact_size() + self.signature.consensus_encode_exact_size() + } +} + +impl ConsensusDecoding for ValidatorNodeRegistration { + fn consensus_decode(reader: &mut R) -> Result { + Ok(Self { + public_key: ConsensusDecoding::consensus_decode(reader)?, + signature: ConsensusDecoding::consensus_decode(reader)?, + }) + } +} + +#[cfg(test)] +mod test { + use rand::rngs::OsRng; + use tari_crypto::keys::SecretKey; + + use super::*; + use crate::consensus::check_consensus_encoding_correctness; + + fn create_instance() -> ValidatorNodeRegistration { + let sk = PrivateKey::random(&mut OsRng); + ValidatorNodeRegistration::new_signed(&sk, b"valid") + } + + #[test] + fn it_encodes_and_decodes_correctly() { + check_consensus_encoding_correctness(create_instance()).unwrap(); + } + + mod is_valid_signature_for { + use super::*; + + #[test] + fn it_returns_true_for_invalid_signature() { + let reg = create_instance(); + assert!(reg.is_valid_signature_for(b"valid")); + } + + #[test] + fn it_returns_false_for_invalid_challenge() { + let reg = create_instance(); + assert!(!reg.is_valid_signature_for(b"there's wally")); + } + + #[test] + fn it_returns_false_for_invalid_signature() { + let mut reg = create_instance(); + reg.public_key = create_instance().public_key; + assert!(!reg.is_valid_signature_for(b"valid")); + } + } +} diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index ca75c3ba42..ff4b9175aa 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -216,6 +216,23 @@ impl TransactionOutput { Ok(()) } + pub fn verify_validator_node_signature(&self) -> Result<(), TransactionError> { + if let Some(validator_node_reg) = self + .features + .sidechain_feature + .as_ref() + .and_then(|f| f.validator_node_registration()) + { + // TODO: figure out what the validator node should sign + if !validator_node_reg.is_valid_signature_for(b"") { + return Err(TransactionError::InvalidSignatureError( + "Validator node signature is not valid!".to_string(), + )); + } + } + Ok(()) + } + /// Attempt to rewind the range proof to reveal the mask (blinding factor) pub fn recover_mask( &self, diff --git a/base_layer/core/src/transactions/weight.rs b/base_layer/core/src/transactions/weight.rs index aedb0c83c2..9c38dbbbde 100644 --- a/base_layer/core/src/transactions/weight.rs +++ b/base_layer/core/src/transactions/weight.rs @@ -52,6 +52,11 @@ impl WeightParams { pub struct TransactionWeight(WeightParams); impl TransactionWeight { + /// Constructor + pub fn new(weight_params: WeightParams) -> Self { + Self(weight_params) + } + /// Creates a new `TransactionWeight` with latest weight params pub fn latest() -> Self { Self(WeightParams::v1()) diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index 9c7ce9373a..6058d59efd 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -411,6 +411,7 @@ impl BlockValidator { helpers::check_permitted_output_types(&constants, output)?; helpers::check_tari_script_byte_size(&output.script, max_script_size)?; output.verify_metadata_signature()?; + output.verify_validator_node_signature()?; helpers::check_not_duplicate_txo(&*db, output)?; commitment_sum = &commitment_sum + &output.commitment; } diff --git a/base_layer/core/src/validation/block_validators/test.rs b/base_layer/core/src/validation/block_validators/test.rs index 0be1c4527b..2a5df07937 100644 --- a/base_layer/core/src/validation/block_validators/test.rs +++ b/base_layer/core/src/validation/block_validators/test.rs @@ -88,6 +88,7 @@ async fn it_checks_the_coinbase_reward() { let (block, _) = blockchain.create_chained_block(block_spec!("A", parent: "GB", reward: 10 * T, )); let err = validator.validate_block_body(block.block().clone()).await.unwrap_err(); + println!("err {:?}", err); assert!(matches!( err, ValidationError::TransactionError(TransactionError::InvalidCoinbase) diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 21604824d0..52a1b09bc1 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -134,6 +134,8 @@ pub enum ValidationError { OutputTypeNotPermitted { output_type: OutputType }, #[error("FixedHash size error: {0}")] FixedHashSizeError(#[from] FixedHashSizeError), + #[error("Validator node MMR is not correct")] + ValidatorNodeMmmrError, } // ChainStorageError has a ValidationError variant, so to prevent a cyclic dependency we use a string representation in diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index fce1696d85..595a3b53fe 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -547,6 +547,19 @@ pub fn check_mmr_roots(header: &BlockHeader, mmr_roots: &MmrRoots) -> Result<(), kind: "Input", })); } + if header.validator_node_mr != mmr_roots.validator_node_mr { + warn!( + target: LOG_TARGET, + "Block header validator node merkle root in {} do not match calculated root. Header.validator_node_mr: \ + {}, Calculated: {}", + header.hash().to_hex(), + header.validator_node_mr.to_hex(), + mmr_roots.validator_node_mr.to_hex() + ); + return Err(ValidationError::BlockError(BlockValidationError::MismatchedMmrRoots { + kind: "Validator Node", + })); + } Ok(()) } diff --git a/base_layer/core/tests/chain_storage_tests/chain_backend.rs b/base_layer/core/tests/chain_storage_tests/chain_backend.rs index a3de96d2c6..822c456eee 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_backend.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_backend.rs @@ -23,7 +23,7 @@ use tari_common::configuration::Network; use tari_core::{ chain_storage::{create_lmdb_database, BlockchainBackend, ChainStorageError, DbKey, DbTransaction, DbValue}, - consensus::ConsensusManagerBuilder, + consensus::{ConsensusManager, ConsensusManagerBuilder}, test_helpers::blockchain::create_test_db, tx, }; @@ -69,17 +69,18 @@ fn test_lmdb_file_lock() { // Perform test { - let db = create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let db = create_lmdb_database(&temp_path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); - match create_lmdb_database(&temp_path, LMDBConfig::default()) { + match create_lmdb_database(&temp_path, LMDBConfig::default(), consensus_manager.clone()) { Err(ChainStorageError::CannotAcquireFileLock) => {}, _ => panic!("Should not be able to make this db"), } drop(db); - let _db2 = - create_lmdb_database(&temp_path, LMDBConfig::default()).expect("Should be able to make a new lmdb now"); + let _db2 = create_lmdb_database(&temp_path, LMDBConfig::default(), consensus_manager) + .expect("Should be able to make a new lmdb now"); } // Cleanup test data - in Windows the LMBD `set_mapsize` sets file size equals to map size; Linux use sparse files diff --git a/base_layer/core/tests/chain_storage_tests/chain_storage.rs b/base_layer/core/tests/chain_storage_tests/chain_storage.rs index 2b7e872425..a69c5a71f5 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_storage.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_storage.rs @@ -36,7 +36,7 @@ use tari_core::{ MmrTree, Validators, }, - consensus::{emission::Emission, ConsensusConstantsBuilder, ConsensusManagerBuilder}, + consensus::{emission::Emission, ConsensusConstantsBuilder, ConsensusManager, ConsensusManagerBuilder}, proof_of_work::Difficulty, test_helpers::blockchain::{ create_store_with_consensus, @@ -1544,7 +1544,6 @@ fn test_orphan_cleanup_on_reorg() { fn test_orphan_cleanup_delete_all_orphans() { let path = create_temporary_data_path(); let network = Network::LocalNet; - let consensus_manager = ConsensusManagerBuilder::new(network).build(); let validators = Validators::new( MockValidator::new(true), MockValidator::new(true), @@ -1558,7 +1557,8 @@ fn test_orphan_cleanup_delete_all_orphans() { }; // Test cleanup during runtime { - let db = create_lmdb_database(&path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(network).build(); + let db = create_lmdb_database(&path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); let store = BlockchainDatabase::new( db, consensus_manager.clone(), @@ -1611,13 +1611,14 @@ fn test_orphan_cleanup_delete_all_orphans() { // Test orphans are present on open { - let db = create_lmdb_database(&path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let db = create_lmdb_database(&path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); let store = BlockchainDatabase::new( db, consensus_manager.clone(), validators.clone(), config, - DifficultyCalculator::new(consensus_manager.clone(), Default::default()), + DifficultyCalculator::new(consensus_manager, Default::default()), ) .unwrap(); assert_eq!(store.db_read_access().unwrap().orphan_count().unwrap(), 5); @@ -1625,7 +1626,8 @@ fn test_orphan_cleanup_delete_all_orphans() { // Test orphans cleanup on open { - let db = create_lmdb_database(&path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let db = create_lmdb_database(&path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); config.cleanup_orphans_at_startup = true; let store = BlockchainDatabase::new( db, diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index 1564a42877..328a158974 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -58,6 +58,7 @@ use tari_core::{ KernelMmr, KernelMmrHasherBlake256, MutableOutputMmr, + ValidatorNodeMmr, WitnessMmr, WitnessMmrHasherBlake256, }; @@ -107,24 +108,24 @@ fn genesis_template( (block, output) } -#[test] // #[ignore = "used to generate a new esmeralda genesis block"] /// This is a helper function to generate and print out a block that can be used as the genesis block. /// 1. Run `cargo test --package tari_core --test mempool -- helpers::block_builders::print_new_genesis_block_esmeralda /// --exact --nocapture` /// 1. The block and range proof will be printed /// 1. Profit! +#[test] fn print_new_genesis_block_esmeralda() { print_new_genesis_block(Network::Esmeralda); } -#[test] // #[ignore = "used to generate a new igor genesis block"] /// This is a helper function to generate and print out a block that can be used as the genesis block. /// 1. Run `cargo test --package tari_core --test mempool -- helpers::block_builders::print_new_genesis_block_igor /// --exact --nocapture` /// 1. The block and range proof will be printed /// 1. Profit! +#[test] fn print_new_genesis_block_igor() { print_new_genesis_block(Network::Igor); } @@ -159,12 +160,14 @@ fn print_new_genesis_block(network: Network) { witness_mmr.push(utxo.witness_hash().to_vec()).unwrap(); let mut output_mmr = MutableOutputMmr::new(Vec::new(), Bitmap::create()).unwrap(); output_mmr.push(utxo.hash().to_vec()).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); header.kernel_mr = FixedHash::try_from(kernel_mmr.get_merkle_root().unwrap()).unwrap(); header.kernel_mmr_size += 1; header.output_mr = FixedHash::try_from(output_mmr.get_merkle_root().unwrap()).unwrap(); header.witness_mr = FixedHash::try_from(witness_mmr.get_merkle_root().unwrap()).unwrap(); header.output_mmr_size += 1; + header.validator_node_mr = FixedHash::try_from(vn_mmr.get_merkle_root().unwrap()).unwrap(); // header.kernel_mr = kernel.hash(); // header.kernel_mmr_size += 1; @@ -213,6 +216,7 @@ fn print_new_genesis_block(network: Network) { println!("header output_mr: {}", block.header.output_mr.to_hex()); println!("header witness_mr: {}", block.header.witness_mr.to_hex()); println!("header kernel_mr: {}", block.header.kernel_mr.to_hex()); + println!("header validator_node_mr: {}", block.header.validator_node_mr.to_hex()); println!( "header total_kernel_offset: {}", block.header.total_kernel_offset.to_hex() diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 7e544b3562..92a11c17bb 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -31,7 +31,7 @@ use chacha20poly1305::XChaCha20Poly1305; use chrono::NaiveDateTime; use tari_common_types::{ transaction::{ImportStatus, TxId}, - types::PublicKey, + types::{PublicKey, Signature}, }; use tari_comms::types::CommsPublicKey; use tari_core::{ @@ -88,6 +88,13 @@ pub enum TransactionServiceRequest { fee_per_gram: MicroTari, message: String, }, + RegisterValidatorNode { + validator_node_public_key: CommsPublicKey, + validator_node_signature: Signature, + selection_criteria: UtxoSelectionCriteria, + fee_per_gram: MicroTari, + message: String, + }, SendOneSidedTransaction { dest_pubkey: CommsPublicKey, amount: MicroTari, @@ -137,51 +144,59 @@ pub enum TransactionServiceRequest { impl fmt::Display for TransactionServiceRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::GetPendingInboundTransactions => f.write_str("GetPendingInboundTransactions"), - Self::GetPendingOutboundTransactions => f.write_str("GetPendingOutboundTransactions"), - Self::GetCompletedTransactions => f.write_str("GetCompletedTransactions"), - Self::GetCancelledPendingInboundTransactions => f.write_str("GetCancelledPendingInboundTransactions"), - Self::GetCancelledPendingOutboundTransactions => f.write_str("GetCancelledPendingOutboundTransactions"), - Self::GetCancelledCompletedTransactions => f.write_str("GetCancelledCompletedTransactions"), - Self::GetCompletedTransaction(t) => f.write_str(&format!("GetCompletedTransaction({})", t)), + Self::GetPendingInboundTransactions => write!(f, "GetPendingInboundTransactions"), + Self::GetPendingOutboundTransactions => write!(f, "GetPendingOutboundTransactions"), + Self::GetCompletedTransactions => write!(f, "GetCompletedTransactions"), + Self::GetCancelledPendingInboundTransactions => write!(f, "GetCancelledPendingInboundTransactions"), + Self::GetCancelledPendingOutboundTransactions => write!(f, "GetCancelledPendingOutboundTransactions"), + Self::GetCancelledCompletedTransactions => write!(f, "GetCancelledCompletedTransactions"), + Self::GetCompletedTransaction(t) => write!(f, "GetCompletedTransaction({})", t), Self::SendTransaction { dest_pubkey, amount, message, .. - } => f.write_str(&format!( + } => write!( + f, "SendTransaction (to {}, {}, {})", dest_pubkey.to_hex(), amount, message - )), - Self::BurnTari { amount, message, .. } => f.write_str(&format!("Burning Tari ({}, {})", amount, message)), + ), + Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), + Self::RegisterValidatorNode { + validator_node_public_key, + message, + .. + } => write!(f, "Registering VN ({}, {})", validator_node_public_key, message), Self::SendOneSidedTransaction { dest_pubkey, amount, message, .. - } => f.write_str(&format!( + } => write!( + f, "SendOneSidedTransaction (to {}, {}, {})", dest_pubkey.to_hex(), amount, message - )), + ), Self::SendOneSidedToStealthAddressTransaction { dest_pubkey, amount, message, .. - } => f.write_str(&format!( + } => write!( + f, "SendOneSidedToStealthAddressTransaction (to {}, {}, {})", dest_pubkey.to_hex(), amount, message - )), + ), Self::SendShaAtomicSwapTransaction(k, _, v, _, msg) => { - f.write_str(&format!("SendShaAtomicSwapTransaction (to {}, {}, {})", k, v, msg)) + write!(f, "SendShaAtomicSwapTransaction (to {}, {}, {})", k, v, msg) }, - Self::CancelTransaction(t) => f.write_str(&format!("CancelTransaction ({})", t)), + Self::CancelTransaction(t) => write!(f, "CancelTransaction ({})", t), Self::ImportUtxoWithStatus { amount, source_public_key, @@ -191,7 +206,8 @@ impl fmt::Display for TransactionServiceRequest { tx_id, current_height, mined_timestamp, - } => f.write_str(&format!( + } => write!( + f, "ImportUtxo (from {}, {}, {} with maturity {} and {:?} and {:?} and {:?} and {:?})", source_public_key, amount, @@ -201,22 +217,22 @@ impl fmt::Display for TransactionServiceRequest { tx_id, current_height, mined_timestamp - )), - Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => f.write_str(&format!("SubmitTransaction ({})", tx_id)), - Self::SetLowPowerMode => f.write_str("SetLowPowerMode "), - Self::SetNormalPowerMode => f.write_str("SetNormalPowerMode"), - Self::ApplyEncryption(_) => f.write_str("ApplyEncryption"), - Self::RemoveEncryption => f.write_str("RemoveEncryption"), + ), + Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => write!(f, "SubmitTransaction ({})", tx_id), + Self::SetLowPowerMode => write!(f, "SetLowPowerMode "), + Self::SetNormalPowerMode => write!(f, "SetNormalPowerMode"), + Self::ApplyEncryption(_) => write!(f, "ApplyEncryption"), + Self::RemoveEncryption => write!(f, "RemoveEncryption"), Self::GenerateCoinbaseTransaction(_, _, bh) => { - f.write_str(&format!("GenerateCoinbaseTransaction (Blockheight {})", bh)) + write!(f, "GenerateCoinbaseTransaction (Blockheight {})", bh) }, - Self::RestartTransactionProtocols => f.write_str("RestartTransactionProtocols"), - Self::RestartBroadcastProtocols => f.write_str("RestartBroadcastProtocols"), - Self::GetNumConfirmationsRequired => f.write_str("GetNumConfirmationsRequired"), - Self::SetNumConfirmationsRequired(_) => f.write_str("SetNumConfirmationsRequired"), - Self::GetAnyTransaction(t) => f.write_str(&format!("GetAnyTransaction({})", t)), - Self::ValidateTransactions => f.write_str("ValidateTransactions"), - Self::ReValidateTransactions => f.write_str("ReValidateTransactions"), + Self::RestartTransactionProtocols => write!(f, "RestartTransactionProtocols"), + Self::RestartBroadcastProtocols => write!(f, "RestartBroadcastProtocols"), + Self::GetNumConfirmationsRequired => write!(f, "GetNumConfirmationsRequired"), + Self::SetNumConfirmationsRequired(_) => write!(f, "SetNumConfirmationsRequired"), + Self::GetAnyTransaction(t) => write!(f, "GetAnyTransaction({})", t), + Self::ValidateTransactions => write!(f, "ValidateTransactions"), + Self::ReValidateTransactions => write!(f, "ReValidateTransactions"), Self::GetFeePerGramStatsPerBlock { count } => { write!(f, "GetFeePerGramEstimatesPerBlock(count: {})", count,) }, @@ -455,6 +471,30 @@ impl TransactionServiceHandle { } } + pub async fn register_validator_node( + &mut self, + validator_node_public_key: PublicKey, + validator_node_signature: Signature, + selection_criteria: UtxoSelectionCriteria, + fee_per_gram: MicroTari, + message: String, + ) -> Result { + match self + .handle + .call(TransactionServiceRequest::RegisterValidatorNode { + validator_node_public_key, + validator_node_signature, + selection_criteria, + fee_per_gram, + message, + }) + .await?? + { + TransactionServiceResponse::TransactionSent(tx_id) => Ok(tx_id), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + pub async fn send_one_sided_transaction( &mut self, dest_pubkey: CommsPublicKey, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 420fe93d03..1681a35f50 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -35,7 +35,7 @@ use rand::rngs::OsRng; use sha2::Sha256; use tari_common_types::{ transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, - types::{PrivateKey, PublicKey}, + types::{PrivateKey, PublicKey, Signature}, }; use tari_comms::{ peer_manager::NodeIdentity, @@ -654,6 +654,27 @@ where ) .await .map(TransactionServiceResponse::TransactionSent), + TransactionServiceRequest::RegisterValidatorNode { + validator_node_public_key, + validator_node_signature, + selection_criteria, + fee_per_gram, + message, + } => { + let rp = reply_channel.take().expect("Cannot be missing"); + self.register_validator_node( + validator_node_public_key, + validator_node_signature, + selection_criteria, + fee_per_gram, + message, + send_transaction_join_handles, + transaction_broadcast_join_handles, + rp, + ) + .await?; + return Ok(()); + }, TransactionServiceRequest::SendShaAtomicSwapTransaction( dest_pubkey, amount, @@ -1454,6 +1475,40 @@ where Ok(tx_id) } + pub async fn register_validator_node( + &mut self, + validator_node_public_key: CommsPublicKey, + validator_node_signature: Signature, + selection_criteria: UtxoSelectionCriteria, + fee_per_gram: MicroTari, + message: String, + join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + reply_channel: oneshot::Sender>, + ) -> Result<(), TransactionServiceError> { + let output_features = + OutputFeatures::for_validator_node_registration(validator_node_public_key, validator_node_signature); + let tx_meta = + TransactionMetadata::new_with_features(0.into(), 3, KernelFeatures::create_validator_node_registration()); + self.send_transaction( + self.node_identity.public_key().clone(), + MicroTari::from(1), + selection_criteria, + output_features, + fee_per_gram, + message, + tx_meta, + join_handles, + transaction_broadcast_join_handles, + reply_channel, + ) + .await + } + /// Sends a one side payment transaction to a recipient /// # Arguments /// 'dest_pubkey': The Comms pubkey of the recipient node diff --git a/clients/nodejs/base_node_grpc_client/src/index.js b/clients/nodejs/base_node_grpc_client/src/index.js index 333f7012c8..7a9a219d6d 100644 --- a/clients/nodejs/base_node_grpc_client/src/index.js +++ b/clients/nodejs/base_node_grpc_client/src/index.js @@ -42,7 +42,8 @@ function Client(address = "127.0.0.1:18142") { "getTipInfo", "searchUtxos", "getTokens", - "getNetworkDifficulty" + "getNetworkDifficulty", + "getActiveValidatorNodes", ]; methods.forEach((method) => { this[method] = (arg) => this.inner[method]().sendMessage(arg); diff --git a/common/config/presets/e_validator_node.toml b/common/config/presets/e_validator_node.toml index 92faffc067..fc3385986a 100644 --- a/common/config/presets/e_validator_node.toml +++ b/common/config/presets/e_validator_node.toml @@ -26,17 +26,20 @@ # The Tari console wallet's GRPC address. (default = "/ip4/127.0.0.1/tcp/18143") #wallet_grpc_address = "127.0.0.1/tcp/18143" -# If set to false, there will be no scanning at all. (default = true) -#scan_for_assets = true - # How often do we want to scan the base layer for changes. (default = 10) -#new_asset_scanning_interval = 10 - -# If set then only the specific assets will be checked. (= [""]) (default = ) -# assets_allow_list = +#base_layer_scanning_interval = 10 # The relative path to store persistent data (default = "data/validator_node") #data_dir = "data/validator_node" -# GRPC address of the validator node application (default = "/ip4/127.0.0.1/tcp/18144") -#grpc_address = "/ip4/127.0.0.1/tcp/18144" +# JSON-RPC listener address +# json_rpc_address = "127.0.0.1:18200" + +# HTTP UI listener address +# http_ui_address = "127.0.0.1:5000" + +# Set to true to enable auto registration for each epoch. +# auto_register = true + +[validator_node.p2p] +# transport = "tor" \ No newline at end of file diff --git a/integration_tests/helpers/transactionBuilder.js b/integration_tests/helpers/transactionBuilder.js index a165b22d23..d0acd2aa0a 100644 --- a/integration_tests/helpers/transactionBuilder.js +++ b/integration_tests/helpers/transactionBuilder.js @@ -224,7 +224,7 @@ class TransactionBuilder { unique_id: features.unique_id ? Buffer.from(features.unique_id, "utf8") : null, - sidechain_features: null, + sidechain_feature: null, parent_public_key: null, asset: null, mint_non_fungible: null, diff --git a/integration_tests/helpers/transactionOutputHashing.js b/integration_tests/helpers/transactionOutputHashing.js index d51178d4c9..21df98e064 100644 --- a/integration_tests/helpers/transactionOutputHashing.js +++ b/integration_tests/helpers/transactionOutputHashing.js @@ -22,7 +22,7 @@ const featuresToConsensusBytes = function (features) { Buffer.from([parseInt(features.maturity || 0)]), // output_type Buffer.from([features.output_type]), - // sidechain_features + // sidechain_feature // TODO: SideChainFeatures encodeOption(null), // metadata