diff --git a/Cargo.lock b/Cargo.lock index 502bc426cf..b51ffe62c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4703,6 +4703,7 @@ dependencies = [ "borsh", "digest 0.9.0", "lazy_static", + "newtype-ops", "rand 0.7.3", "serde", "tari_common", diff --git a/applications/tari_app_grpc/proto/types.proto b/applications/tari_app_grpc/proto/types.proto index 6861cb1651..3c5e25a50b 100644 --- a/applications/tari_app_grpc/proto/types.proto +++ b/applications/tari_app_grpc/proto/types.proto @@ -124,7 +124,7 @@ message ConsensusConstants { /// 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; + uint64 validator_node_validity_period = 19; /// The height at which these constants become effective uint64 effective_from_height = 20; /// Current version of the blockchain @@ -143,4 +143,6 @@ message ConsensusConstants { Range kernel_version_range = 28; /// An allowlist of output types repeated OutputType permitted_output_types = 29; + /// The length of an epoch + uint64 epoch_length = 30; } diff --git a/applications/tari_app_grpc/src/conversions/consensus_constants.rs b/applications/tari_app_grpc/src/conversions/consensus_constants.rs index e35256a08e..b9ca20b2a5 100644 --- a/applications/tari_app_grpc/src/conversions/consensus_constants.rs +++ b/applications/tari_app_grpc/src/conversions/consensus_constants.rs @@ -113,7 +113,6 @@ 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(), @@ -125,6 +124,8 @@ impl From for grpc::ConsensusConstants { max_randomx_seed_height: cc.max_randomx_seed_height(), output_version_range: Some(output_version_range), permitted_output_types, + validator_node_validity_period: cc.validator_node_validity_period().as_u64(), + epoch_length: cc.epoch_length(), } } } diff --git a/applications/tari_app_grpc/src/conversions/sidechain_feature.rs b/applications/tari_app_grpc/src/conversions/sidechain_feature.rs index 815f0c5818..45841ec988 100644 --- a/applications/tari_app_grpc/src/conversions/sidechain_feature.rs +++ b/applications/tari_app_grpc/src/conversions/sidechain_feature.rs @@ -31,6 +31,7 @@ use tari_core::{ SideChainFeature, TemplateType, ValidatorNodeRegistration, + ValidatorNodeSignature, }, }; use tari_utilities::ByteArray; @@ -79,21 +80,21 @@ 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 + Ok(ValidatorNodeRegistration::new(ValidatorNodeSignature::new( + PublicKey::from_bytes(&value.public_key).map_err(|e| e.to_string())?, + 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()), + public_key: value.public_key().to_vec(), + signature: Some(value.signature().into()), } } } diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 61a8687c04..6db1e910ac 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -193,6 +193,7 @@ pub async fn claim_htlc_refund( } pub async fn register_validator_node( + amount: MicroTari, mut wallet_transaction_service: TransactionServiceHandle, validator_node_public_key: PublicKey, validator_node_signature: Signature, @@ -202,6 +203,7 @@ pub async fn register_validator_node( ) -> Result { wallet_transaction_service .register_validator_node( + amount, validator_node_public_key, validator_node_signature, selection_criteria, @@ -971,6 +973,7 @@ pub async fn command_runner( }, RegisterValidatorNode(args) => { let tx_id = register_validator_node( + args.amount, transaction_service.clone(), args.validator_node_public_key.into(), Signature::new( diff --git a/applications/tari_console_wallet/src/cli.rs b/applications/tari_console_wallet/src/cli.rs index cc862a107e..95622091b1 100644 --- a/applications/tari_console_wallet/src/cli.rs +++ b/applications/tari_console_wallet/src/cli.rs @@ -271,6 +271,7 @@ pub struct HashPasswordArgs { #[derive(Debug, Args, Clone)] pub struct RegisterValidatorNodeArgs { + pub amount: MicroTari, pub validator_node_public_key: UniPublicKey, pub validator_node_public_nonce: UniPublicKey, pub validator_node_signature: Vec, 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 c382113f2b..94fe7da849 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -86,13 +86,23 @@ use tari_common_types::{ types::{BlockHash, PublicKey, Signature}, }; use tari_comms::{multiaddr::Multiaddr, types::CommsPublicKey, CommsNode}; -use tari_core::transactions::{ - tari_amount::{MicroTari, T}, - transaction_components::{CodeTemplateRegistration, OutputFeatures, OutputType, SideChainFeature, UnblindedOutput}, +use tari_core::{ + consensus::{ConsensusConstants, ConsensusManager}, + transactions::{ + tari_amount::{MicroTari, T}, + transaction_components::{ + CodeTemplateRegistration, + OutputFeatures, + OutputType, + SideChainFeature, + UnblindedOutput, + }, + }, }; use tari_utilities::{hex::Hex, ByteArray}; use tari_wallet::{ connectivity_service::{OnlineStatus, WalletConnectivityInterface}, + error::WalletStorageError, output_manager_service::{handle::OutputManagerHandle, UtxoSelectionCriteria}, transaction_service::{ handle::TransactionServiceHandle, @@ -127,11 +137,13 @@ async fn send_transaction_event( pub struct WalletGrpcServer { wallet: WalletSqlite, + rules: ConsensusManager, } impl WalletGrpcServer { pub fn new(wallet: WalletSqlite) -> Self { - Self { wallet } + let rules = ConsensusManager::builder(wallet.network.as_network()).build(); + Self { wallet, rules } } fn get_transaction_service(&self) -> TransactionServiceHandle { @@ -145,6 +157,18 @@ impl WalletGrpcServer { fn comms(&self) -> &CommsNode { &self.wallet.comms } + + fn get_consensus_constants(&self) -> Result<&ConsensusConstants, WalletStorageError> { + // If we don't have the chain metadata, we hope that VNReg consensus constants did not change - worst case, we + // spend more than we need to or the the transaction is rejected. + let height = self + .wallet + .db + .get_chain_metadata()? + .map(|m| m.height_of_longest_chain()) + .unwrap_or_default(); + Ok(self.rules.consensus_constants(height)) + } } #[tonic::async_trait] @@ -955,10 +979,17 @@ impl wallet_server::Wallet for WalletGrpcServer { .validator_node_signature .ok_or_else(|| Status::invalid_argument("Validator node signature is missing!"))? .try_into() - .unwrap(); + .map_err(|_| Status::invalid_argument("Validator node signature is malformed!"))?; + + let constants = self.get_consensus_constants().map_err(|e| { + error!(target: LOG_TARGET, "Failed to get consensus constants: {}", e); + Status::internal("failed to fetch consensus constants") + })?; + // TODO: we need to set the output maturity let response = match transaction_service .register_validator_node( + constants.validator_node_registration_min_deposit_amount(), validator_node_public_key, validator_node_signature, UtxoSelectionCriteria::default(), diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index 5dd6ead196..e2b253cf66 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -9,12 +9,14 @@ edition = "2018" [dependencies] tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.16.5" } tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag="v0.4.10" } +# TODO: remove this dependency and move Network into tari_common_types tari_common = { version = "^0.41", path = "../../common" } base64 = "0.13.0" borsh = "0.9.3" digest = "0.9.0" lazy_static = "1.4.0" +newtype-ops = "0.1" rand = "0.7.3" serde = { version = "1.0.106", features = ["derive"] } thiserror = "1.0.29" diff --git a/base_layer/common_types/src/epoch.rs b/base_layer/common_types/src/epoch.rs new file mode 100644 index 0000000000..3deb4acb7c --- /dev/null +++ b/base_layer/common_types/src/epoch.rs @@ -0,0 +1,44 @@ +// 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 newtype_ops::newtype_ops; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)] +pub struct VnEpoch(pub u64); + +impl VnEpoch { + pub fn as_u64(&self) -> u64 { + self.0 + } + + pub fn to_be_bytes(&self) -> [u8; 8] { + self.0.to_be_bytes() + } + + pub fn saturating_sub(self, other: VnEpoch) -> VnEpoch { + VnEpoch(self.0.saturating_sub(other.0)) + } +} + +newtype_ops! { [VnEpoch] {add sub mul div} {:=} Self Self } +newtype_ops! { [VnEpoch] {add sub mul div} {:=} &Self &Self } +newtype_ops! { [VnEpoch] {add sub mul div} {:=} Self &Self } diff --git a/base_layer/common_types/src/lib.rs b/base_layer/common_types/src/lib.rs index c4cad69f91..77b1476550 100644 --- a/base_layer/common_types/src/lib.rs +++ b/base_layer/common_types/src/lib.rs @@ -23,6 +23,7 @@ pub mod chain_metadata; pub mod dammsum; pub mod emoji; +pub mod epoch; pub mod grpc_authentication; pub mod tari_address; pub mod transaction; diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 937dff6f19..8c7c58b2a8 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -222,7 +222,7 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // // use croaring::Bitmap; // use std::convert::TryFrom; - // use crate::{KernelMmr, MutableOutputMmr, WitnessMmr}; + // use crate::{chain_storage::calculate_validator_node_mr, KernelMmr, MutableOutputMmr, WitnessMmr}; // let mut kernel_mmr = KernelMmr::new(Vec::new()); // for k in block.body.kernels() { @@ -247,7 +247,7 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // } // } - // let vn_mmr = ValidatorNodeMmr::new(Vec::new()); + // let vn_mmr = calculate_validator_node_mr(&[]).unwrap(); // 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(); @@ -363,17 +363,17 @@ fn get_esmeralda_genesis_block_raw() -> Block { mod test { use croaring::Bitmap; - use tari_common_types::types::Commitment; + use tari_common_types::{epoch::VnEpoch, types::Commitment}; use super::*; use crate::{ + chain_storage::calculate_validator_node_mr, consensus::ConsensusManager, test_helpers::blockchain::create_new_blockchain_with_network, transactions::{transaction_components::transaction_output::batch_verify_range_proofs, CryptoFactories}, validation::{ChainBalanceValidator, FinalHorizonStateValidation}, KernelMmr, MutableOutputMmr, - ValidatorNodeMmr, WitnessMmr, }; @@ -428,7 +428,7 @@ mod test { let mut witness_mmr = WitnessMmr::new(Vec::new()); let mut output_mmr = MutableOutputMmr::new(Vec::new(), Bitmap::create()).unwrap(); - let mut vn_mmr = ValidatorNodeMmr::new(Vec::new()); + let mut vn_nodes = 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(); @@ -439,25 +439,19 @@ mod test { .as_ref() .and_then(|f| f.validator_node_registration()) .unwrap(); - vn_mmr.push(reg.derive_shard_key(block.hash()).to_vec()).unwrap(); + vn_nodes.push(( + reg.public_key().clone(), + reg.derive_shard_key(None, VnEpoch(0), VnEpoch(0), block.hash()), + )); } } + 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!( - 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!( - vn_mmr.get_merkle_root().unwrap().as_slice(), - block.header().validator_node_mr.as_slice() + calculate_validator_node_mr(&vn_nodes).unwrap(), + block.header().validator_node_mr, ); // Check that the faucet UTXOs balance (the faucet_value consensus constant is set correctly and faucet kernel diff --git a/base_layer/core/src/chain_storage/active_validator_node.rs b/base_layer/core/src/chain_storage/active_validator_node.rs index 374317bc20..c31f2a2c33 100644 --- a/base_layer/core/src/chain_storage/active_validator_node.rs +++ b/base_layer/core/src/chain_storage/active_validator_node.rs @@ -21,13 +21,16 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use serde::{Deserialize, Serialize}; -use tari_common_types::types::{HashOutput, PublicKey}; +use tari_common_types::{ + epoch::VnEpoch, + types::{Commitment, PublicKey}, +}; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct ActiveValidatorNode { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +pub struct ValidatorNodeEntry { pub shard_key: [u8; 32], - pub from_height: u64, - pub to_height: u64, + pub start_epoch: VnEpoch, + pub end_epoch: VnEpoch, pub public_key: PublicKey, - pub output_hash: HashOutput, + pub commitment: Commitment, } diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index dea12c9747..42da836805 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -195,8 +195,12 @@ pub trait BlockchainBackend: Send + Sync { /// Fetches all tracked reorgs fn fetch_all_reorgs(&self) -> Result, ChainStorageError>; + /// Fetches the validator node set for the given height ordered according to height of registration and canonical + /// block body ordering. fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError>; + /// Returns the shard key for the validator node if valid at the given height. fn get_shard_key(&self, height: u64, public_key: PublicKey) -> Result, ChainStorageError>; + /// Returns all template registrations within (inclusive) the given height range. fn fetch_template_registrations( &self, start_height: u64, diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 8e5072f9ed..40f204ff4e 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -38,7 +38,8 @@ use tari_common_types::{ chain_metadata::ChainMetadata, types::{BlockHash, Commitment, FixedHash, HashOutput, PublicKey, Signature}, }; -use tari_mmr::pruned_hashset::PrunedHashSet; +use tari_crypto::hash::blake2::Blake256; +use tari_mmr::{error::MerkleMountainRangeError, pruned_hashset::PrunedHashSet}; use tari_utilities::{epoch_time::EpochTime, hex::Hex, ByteArray}; use super::TemplateRegistrationEntry; @@ -814,7 +815,7 @@ where B: BlockchainBackend header.timestamp = median_timestamp.increase(1); } let mut block = Block { header, body }; - let roots = calculate_mmr_roots(&*db, &block)?; + let roots = calculate_mmr_roots(&*db, self.rules(), &block)?; block.header.kernel_mr = roots.kernel_mr; block.header.kernel_mmr_size = roots.kernel_mmr_size; block.header.input_mr = roots.input_mr; @@ -835,7 +836,7 @@ where B: BlockchainBackend block.body.is_sorted(), "calculate_mmr_roots expected a sorted block body, however the block body was not sorted" ); - let mmr_roots = calculate_mmr_roots(&*db, &block)?; + let mmr_roots = calculate_mmr_roots(&*db, self.rules(), &block)?; Ok((block, mmr_roots)) } @@ -1256,7 +1257,11 @@ impl std::fmt::Display for MmrRoots { #[allow(clippy::too_many_lines)] #[allow(clippy::similar_names)] -pub fn calculate_mmr_roots(db: &T, block: &Block) -> Result { +pub fn calculate_mmr_roots( + db: &T, + rules: &ConsensusManager, + block: &Block, +) -> Result { let header = &block.header; let body = &block.body; @@ -1357,9 +1362,17 @@ 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 block_height = block.header.height; + let epoch_len = rules.consensus_constants(block_height).epoch_length(); + let validator_node_mr = if block_height % epoch_len == 0 { + // At epoch boundary, the MR is rebuilt from the current validator set + let validator_nodes = db.fetch_active_validator_nodes(block_height)?; + FixedHash::try_from(calculate_validator_node_mr(&validator_nodes)?)? + } else { + // MR is unchanged except for epoch boundary + let tip_header = fetch_header(db, block_height - 1)?; + tip_header.validator_node_mr + }; let mmr_roots = MmrRoots { kernel_mr: FixedHash::try_from(kernel_mmr.get_merkle_root()?)?, @@ -1368,11 +1381,28 @@ 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()?)?, + validator_node_mr, }; Ok(mmr_roots) } +pub fn calculate_validator_node_mr( + validator_nodes: &[(PublicKey, [u8; 32])], +) -> Result { + fn hash_node((pk, s): &(PublicKey, [u8; 32])) -> Vec { + use digest::Digest; + Blake256::new() + .chain(pk.as_bytes()) + .chain(s.as_slice()) + .finalize() + .to_vec() + } + + let vn_mmr = ValidatorNodeMmr::new(validator_nodes.iter().map(hash_node).collect()); + let merkle_root = vn_mmr.get_merkle_root()?; + Ok(merkle_root) +} + pub fn fetch_header(db: &T, block_num: u64) -> Result { fetch!(db, block_num, BlockHeader) } diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index d2c2f706db..e1fcbbcb6a 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -30,7 +30,6 @@ 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}, @@ -359,12 +358,6 @@ pub enum WriteOperation { reorg: Reorg, }, ClearAllReorgs, - InsertValidatorNode { - validator_node: ActiveValidatorNode, - }, - InsertTemplateRegistration { - template_registration: TemplateRegistrationEntry, - }, } impl fmt::Display for WriteOperation { @@ -461,12 +454,6 @@ 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 62d33a6a91..240d7725ed 100644 --- a/base_layer/core/src/chain_storage/error.rs +++ b/base_layer/core/src/chain_storage/error.rs @@ -136,6 +136,8 @@ pub enum ChainStorageError { FixedHashSizeError(#[from] FixedHashSizeError), #[error("Composite key length was exceeded (THIS SHOULD NEVER HAPPEN)")] CompositeKeyLengthExceeded, + #[error("Failed to decode key bytes: {0}")] + FromKeyBytesFailed(String), } 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 0d6a7f5c09..38eff4d464 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 @@ -65,7 +65,7 @@ impl CompositeKey { true } - fn as_bytes(&self) -> &[u8] { + pub fn as_bytes(&self) -> &[u8] { &self.bytes[..self.len] } diff --git a/base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs b/base_layer/core/src/chain_storage/lmdb_db/cursors.rs similarity index 59% rename from base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs rename to base_layer/core/src/chain_storage/lmdb_db/cursors.rs index 30dc674658..0718a97be2 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/key_prefix_cursor.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/cursors.rs @@ -25,7 +25,10 @@ use std::marker::PhantomData; use lmdb_zero::{ConstAccessor, Cursor, LmdbResultExt}; use serde::de::DeserializeOwned; -use crate::chain_storage::{lmdb_db::helpers::deserialize, ChainStorageError}; +use crate::chain_storage::{ + lmdb_db::{composite_key::CompositeKey, helpers::deserialize}, + ChainStorageError, +}; pub struct KeyPrefixCursor<'a, V> { cursor: Cursor<'a, 'a>, @@ -93,44 +96,103 @@ where V: DeserializeOwned } } +pub struct LmdbReadCursor<'a, V> { + cursor: Cursor<'a, 'a>, + value_type: PhantomData, + access: ConstAccessor<'a>, +} + +impl<'a, V: DeserializeOwned> LmdbReadCursor<'a, V> { + pub(super) fn new(cursor: Cursor<'a, 'a>, access: ConstAccessor<'a>) -> Self { + Self { + cursor, + access, + value_type: PhantomData, + } + } + + /// Returns the item at the cursor, progressing forwards until there are no more elements + pub fn next(&mut self) -> Result, ChainStorageError> { + convert_result(self.cursor.next(&self.access)) + } + + pub fn next_dup(&mut self) -> Result, ChainStorageError> { + convert_result(self.cursor.next_dup(&self.access)) + } + + pub fn seek_range(&mut self, key: &[u8]) -> Result, ChainStorageError> { + convert_result(self.cursor.seek_range_k(&self.access, key)) + } +} + +pub trait FromKeyBytes { + fn from_key_bytes(bytes: &[u8]) -> Result + where Self: Sized; +} + +impl FromKeyBytes for u64 { + fn from_key_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if bytes.len() != 8 { + return Err(ChainStorageError::FromKeyBytesFailed( + "Invalid byte length for u64 key".to_string(), + )); + } + let mut buf = [0u8; 8]; + buf.copy_from_slice(&bytes[..8]); + Ok(u64::from_be_bytes(buf)) + } +} + +impl FromKeyBytes for CompositeKey { + fn from_key_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if bytes.len() != SZ { + return Err(ChainStorageError::FromKeyBytesFailed( + "Invalid byte length for CompositeKey".to_string(), + )); + } + let mut key = CompositeKey::::new(); + key.push(bytes); + Ok(key) + } +} + +fn convert_result( + result: lmdb_zero::Result<(&[u8], &[u8])>, +) -> Result, ChainStorageError> { + match result.to_opt()? { + Some((k, v)) => Ok(Some((K::from_key_bytes(k)?, deserialize(v)?))), + None => Ok(None), + } +} + #[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}; + use crate::chain_storage::{ + lmdb_db::lmdb::{lmdb_get_prefix_cursor, lmdb_insert}, + tests::temp_db::TempLmdbDatabase, + }; #[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 database = TempLmdbDatabase::new(); + let db = database.default_db(); { - 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(); + let txn = database.write_transaction(); + lmdb_insert(&txn, db, &[0xffu8, 0, 0, 0], &1u64, "test").unwrap(); + lmdb_insert(&txn, db, &[0x2bu8, 0, 0, 1], &2u64, "test").unwrap(); + lmdb_insert(&txn, db, &[0x2bu8, 0, 1, 1], &3u64, "test").unwrap(); + lmdb_insert(&txn, db, &[0x2bu8, 1, 1, 0], &4u64, "test").unwrap(); + lmdb_insert(&txn, db, &[0x2bu8, 1, 1, 1], &5u64, "test").unwrap(); + lmdb_insert(&txn, 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 txn = database.read_transaction(); + 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(); @@ -151,7 +213,5 @@ mod tests { 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.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs index 75e238b088..dffda61a1c 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs @@ -42,8 +42,8 @@ use tari_utilities::hex::to_hex; use crate::chain_storage::{ error::ChainStorageError, lmdb_db::{ + cursors::KeyPrefixCursor, helpers::{deserialize, serialize}, - key_prefix_cursor::KeyPrefixCursor, }, OrNotFound, }; 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 e4bbcf0d32..88c9cc3314 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,17 +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::{ - collections::HashMap, - convert::TryFrom, - fmt, - fs, - fs::File, - ops::Deref, - path::Path, - sync::Arc, - time::Instant, -}; +use std::{convert::TryFrom, fmt, fs, fs::File, ops::Deref, path::Path, sync::Arc, time::Instant}; use croaring::Bitmap; use fs2::FileExt; @@ -47,7 +37,7 @@ use tari_utilities::{ ByteArray, }; -use super::{key_prefix_cursor::KeyPrefixCursor, lmdb::lmdb_get_prefix_cursor}; +use super::{cursors::KeyPrefixCursor, lmdb::lmdb_get_prefix_cursor}; use crate::{ blocks::{ Block, @@ -83,6 +73,7 @@ use crate::{ lmdb_len, lmdb_replace, }, + validator_node_store::ValidatorNodeStore, TransactionInputRowData, TransactionInputRowDataRef, TransactionKernelRowData, @@ -90,7 +81,6 @@ use crate::{ }, stats::DbTotalSizeStats, utxo_mined_info::UtxoMinedInfo, - ActiveValidatorNode, BlockchainBackend, DbBasicStats, DbSize, @@ -99,11 +89,18 @@ use crate::{ PrunedOutput, Reorg, TemplateRegistrationEntry, + ValidatorNodeEntry, }, - consensus::ConsensusManager, + consensus::{ConsensusConstants, ConsensusManager}, transactions::{ aggregated_body::AggregateBody, - transaction_components::{TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + transaction_components::{ + TransactionError, + TransactionInput, + TransactionKernel, + TransactionOutput, + ValidatorNodeRegistration, + }, }, MutablePrunedOutputMmr, PrunedKernelMmr, @@ -140,7 +137,6 @@ const LMDB_DB_BAD_BLOCK_LIST: &str = "bad_blocks"; const LMDB_DB_REORGS: &str = "reorgs"; 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) @@ -159,7 +155,7 @@ pub fn create_lmdb_database>( ) -> Result { let flags = db::CREATE; debug!(target: LOG_TARGET, "Creating LMDB database at {:?}", path.as_ref()); - std::fs::create_dir_all(&path)?; + fs::create_dir_all(&path)?; let file_lock = acquire_exclusive_file_lock(path.as_ref())?; @@ -193,9 +189,8 @@ pub fn create_lmdb_database>( .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_VALIDATOR_NODES, flags) + .add_database(LMDB_DB_VALIDATOR_NODES_MAPPING, flags) .add_database(LMDB_DB_TEMPLATE_REGISTRATIONS, flags | db::DUPSORT) .build() .map_err(|err| ChainStorageError::CriticalError(format!("Could not create LMDB store:{}", err)))?; @@ -255,12 +250,10 @@ pub struct LMDBDatabase { bad_blocks: DatabaseRef, /// Stores reorgs by epochtime and Reorg reorgs: DatabaseRef, - /// Maps VN Public Key -> ActiveValidatorNode + /// Maps -> ActiveValidatorNode validator_nodes: DatabaseRef, - /// Maps VN Shard Key -> VN Public Key + /// Maps -> VN Shard 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, @@ -305,7 +298,6 @@ impl LMDBDatabase { 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(), @@ -511,12 +503,6 @@ 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()?; @@ -1306,7 +1292,8 @@ impl LMDBDatabase { .as_ref() .and_then(|f| f.validator_node_registration()) { - self.delete_validator_node(txn, &vn_reg.public_key, &input.output_hash())?; + self.validator_node_store(txn) + .delete(header.height, vn_reg.public_key(), input.commitment()?)?; } if !output_mmr.delete(index) { @@ -1335,20 +1322,7 @@ impl LMDBDatabase { .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)?; + self.insert_validator_node(txn, header, &output.commitment, vn_reg)?; } if let Some(template_reg) = output .features @@ -1419,6 +1393,49 @@ impl LMDBDatabase { Ok(()) } + fn validator_node_store<'a, T: Deref>>( + &'a self, + txn: &'a T, + ) -> ValidatorNodeStore<'a, T> { + ValidatorNodeStore::new(txn, self.validator_nodes.clone(), self.validator_nodes_mapping.clone()) + } + + fn insert_validator_node( + &self, + txn: &WriteTransaction<'_>, + header: &BlockHeader, + commitment: &Commitment, + vn_reg: &ValidatorNodeRegistration, + ) -> Result<(), ChainStorageError> { + let store = self.validator_node_store(txn); + let constants = self.get_consensus_constants(header.height); + let current_epoch = constants.block_height_to_current_epoch(header.height); + + let prev_shard_key = store.get_shard_key( + (current_epoch.as_u64() - constants.validator_node_validity_period().as_u64()) * constants.epoch_length(), + current_epoch.as_u64() * constants.epoch_length(), + vn_reg.public_key(), + )?; + let shard_key = vn_reg.derive_shard_key( + prev_shard_key, + current_epoch, + constants.validator_node_registration_shuffle_interval(), + &header.prev_hash, + ); + + let next_epoch = constants.block_height_to_next_epoch(header.height); + let validator_node = ValidatorNodeEntry { + shard_key, + start_epoch: next_epoch, + end_epoch: next_epoch + constants.validator_node_validity_period(), + public_key: vn_reg.public_key().clone(), + commitment: commitment.clone(), + }; + + store.insert(header.height, &validator_node)?; + Ok(()) + } + #[allow(clippy::ptr_arg)] fn insert_block_accumulated_data( &self, @@ -1606,55 +1623,6 @@ 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<'_>, @@ -1731,6 +1699,10 @@ impl LMDBDatabase { Ok(None) } } + + fn get_consensus_constants(&self, height: u64) -> &ConsensusConstants { + self.consensus_manager.consensus_constants(height) + } } pub fn create_recovery_lmdb_database>(path: P) -> Result<(), ChainStorageError> { @@ -2514,56 +2486,74 @@ impl BlockchainBackend for LMDBDatabase { 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()) + let vn_store = self.validator_node_store(&txn); + let constants = self.consensus_manager.consensus_constants(height); + + // Get the current epoch for the height + let end_epoch = constants.block_height_to_current_epoch(height); + // Subtract the registration validaty period to get the start epoch + let start_epoch = end_epoch.saturating_sub(constants.validator_node_validity_period()); + // Convert these back to height as validators regs are indexed by height + let start_height = start_epoch.as_u64() * constants.epoch_length(); + let end_height = end_epoch.as_u64() * constants.epoch_length(); + let nodes = vn_store.get_vn_set(start_height, end_height)?; + Ok(nodes) + + // let validator_node_validaty_period = constants.validator_node_validity_period(); + // let mut pub_keys = HashMap::new(); + // + // let end = height + validator_node_validaty_period; + // for h in height..end { + // lmdb_get_multiple(&txn, &self.validator_nodes_ending, &h.to_le_bytes())? + // .into_iter() + // .for_each(|v: ValidatorNodeEntry| { + // 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()) + let store = self.validator_node_store(&txn); + let constants = self.get_consensus_constants(height); + + // Get the epoch height boundaries for our query + let current_epoch = constants.block_height_to_current_epoch(height); + let start_epoch = current_epoch.saturating_sub(constants.validator_node_validity_period()); + let start_height = start_epoch.as_u64() * constants.epoch_length(); + let end_height = current_epoch.as_u64() * constants.epoch_length(); + let maybe_shard_id = store.get_shard_key(start_height, end_height, &public_key)?; + Ok(maybe_shard_id) + + // 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( 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 dd25794e99..47bdf50973 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/mod.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/mod.rs @@ -29,11 +29,12 @@ use crate::transactions::transaction_components::{TransactionInput, TransactionK // mod composite_key; mod composite_key; +pub(crate) mod cursors; pub(crate) mod helpers; -pub(crate) mod key_prefix_cursor; mod lmdb; #[allow(clippy::module_inception)] mod lmdb_db; +mod validator_node_store; #[derive(Serialize, Deserialize, Debug)] pub(crate) struct TransactionOutputRowData { diff --git a/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs b/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs new file mode 100644 index 0000000000..ccc2f95684 --- /dev/null +++ b/base_layer/core/src/chain_storage/lmdb_db/validator_node_store.rs @@ -0,0 +1,389 @@ +// 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::{collections::HashMap, ops::Deref}; + +use lmdb_zero::{ConstTransaction, WriteTransaction}; +use tari_common_types::types::{Commitment, PublicKey}; +use tari_storage::lmdb_store::DatabaseRef; +use tari_utilities::ByteArray; + +use crate::chain_storage::{ + lmdb_db::{ + composite_key::CompositeKey, + cursors::{FromKeyBytes, LmdbReadCursor}, + lmdb::{lmdb_delete, lmdb_insert}, + }, + ChainStorageError, + ValidatorNodeEntry, +}; + +pub type ShardKey = [u8; 32]; +// +type ValidatorNodeStoreKey = CompositeKey<72>; +// +type ShardIdIndexKey = CompositeKey<72>; + +pub struct ValidatorNodeStore<'a, Txn> { + txn: &'a Txn, + db_validator_nodes: DatabaseRef, + db_validator_nodes_mapping: DatabaseRef, +} + +impl<'a, Txn: Deref>> ValidatorNodeStore<'a, Txn> { + pub fn new(txn: &'a Txn, db_height_to_vn: DatabaseRef, idx_public_key_to_shard: DatabaseRef) -> Self { + Self { + txn, + db_validator_nodes: db_height_to_vn, + db_validator_nodes_mapping: idx_public_key_to_shard, + } + } +} + +impl ValidatorNodeStore<'_, WriteTransaction<'_>> { + pub fn insert(&self, height: u64, validator: &ValidatorNodeEntry) -> Result<(), ChainStorageError> { + let key = ValidatorNodeStoreKey::try_from_parts(&[ + height.to_be_bytes().as_slice(), + validator.public_key.as_bytes(), + validator.commitment.as_bytes(), + ]) + .expect("insert: Composite key length is incorrect"); + lmdb_insert(self.txn, &self.db_validator_nodes, &key, &validator, "Validator node")?; + + let key = ShardIdIndexKey::try_from_parts(&[ + validator.public_key.as_bytes(), + height.to_be_bytes().as_slice(), + validator.commitment.as_bytes(), + ]) + .expect("insert: Composite key length is incorrect"); + lmdb_insert( + self.txn, + &self.db_validator_nodes_mapping, + &key, + &validator.shard_key, + "Validator node", + )?; + Ok(()) + } + + pub fn delete( + &self, + height: u64, + public_key: &PublicKey, + commitment: &Commitment, + ) -> Result<(), ChainStorageError> { + let key = ValidatorNodeStoreKey::try_from_parts(&[ + height.to_be_bytes().as_slice(), + public_key.as_bytes(), + commitment.as_bytes(), + ]) + .expect("delete: Composite key length is incorrect"); + lmdb_delete(self.txn, &self.db_validator_nodes, &key, "validator_nodes")?; + + let key = ShardIdIndexKey::try_from_parts(&[ + public_key.as_bytes(), + height.to_be_bytes().as_slice(), + commitment.as_bytes(), + ]) + .expect("delete: Composite key length is incorrect"); + lmdb_delete( + self.txn, + &self.db_validator_nodes_mapping, + &key, + "validator_nodes_mapping", + )?; + Ok(()) + } +} + +impl<'a, Txn: Deref>> ValidatorNodeStore<'a, Txn> { + fn db_read_cursor(&self) -> Result, ChainStorageError> { + let cursor = self.txn.cursor(self.db_validator_nodes.clone())?; + let access = self.txn.access(); + let cursor = LmdbReadCursor::new(cursor, access); + Ok(cursor) + } + + fn index_read_cursor(&self) -> Result, ChainStorageError> { + let cursor = self.txn.cursor(self.db_validator_nodes_mapping.clone())?; + let access = self.txn.access(); + let cursor = LmdbReadCursor::new(cursor, access); + Ok(cursor) + } + + /// Returns a set of tuples ordered by height of registration. + /// This set contains no duplicates. If a duplicate registration is found, the last registration is included. + pub fn get_vn_set( + &self, + start_height: u64, + end_height: u64, + ) -> Result, ChainStorageError> { + let mut cursor = self.db_read_cursor()?; + + let mut nodes = Vec::new(); + // Public key does not mutate once compressed and will always produce the same hash + #[allow(clippy::mutable_key_type)] + let mut dedup_map = HashMap::new(); + match cursor.seek_range::(&start_height.to_be_bytes())? { + Some((key, vn)) => { + let height = u64::from_key_bytes(&key[0..8])?; + if height > end_height { + return Ok(Vec::new()); + } + dedup_map.insert(vn.public_key.clone(), 0); + nodes.push(Some((vn.public_key, vn.shard_key))); + }, + None => return Ok(Vec::new()), + } + + // Start from index 1 because we already have the first entry + let mut i = 1; + while let Some((key, vn)) = cursor.next_dup::()? { + let height = u64::from_key_bytes(&key[0..8])?; + if height > end_height { + break; + } + if let Some(dup_idx) = dedup_map.insert(vn.public_key.clone(), i) { + // Remove duplicate registrations within the set without changing index order + let node_mut = nodes + .get_mut(dup_idx) + .expect("get_vn_set: internal dedeup map is not in sync with nodes"); + *node_mut = None; + } + nodes.push(Some((vn.public_key, vn.shard_key))); + i += 1; + } + + let mut vn_set = nodes.into_iter().flatten().collect::>(); + vn_set.sort_by(|(_, a), (_, b)| a.cmp(b)); + Ok(vn_set) + } + + pub fn get_shard_key( + &self, + start_height: u64, + end_height: u64, + public_key: &PublicKey, + ) -> Result, ChainStorageError> { + let mut cursor = self.index_read_cursor()?; + let key = ShardIdIndexKey::try_from_parts(&[public_key.as_bytes(), &start_height.to_be_bytes()]) + .expect("fetch_shard_key: Composite key length is incorrect"); + + // TODO: unused_assignments is a false positive, we might be able to remove this exclusion in future + #[allow(unused_assignments)] + let mut shard_key = None; + // Find the first entry at or above start_height + match cursor.seek_range::(key.as_bytes())? { + Some((key, s)) => { + if key[0..32] != *public_key.as_bytes() { + return Ok(None); + } + let height = u64::from_key_bytes(&key[32..40])?; + if height > end_height { + return Ok(None); + } + shard_key = Some(s); + }, + None => return Ok(None), + } + + // If there are any subsequent entries less than the end height, use that instead. + while let Some((key, s)) = cursor.next::()? { + if key[0..32] != *public_key.as_bytes() { + break; + } + let height = u64::from_key_bytes(&key[32..40])?; + if height > end_height { + break; + } + shard_key = Some(s); + } + Ok(shard_key) + } +} + +#[cfg(test)] +mod tests { + use tari_test_utils::unpack_enum; + + use super::*; + use crate::{ + chain_storage::tests::temp_db::TempLmdbDatabase, + test_helpers::{make_hash, new_public_key}, + }; + + const DBS: &[&str] = &["validator_node_store", "validator_node_index"]; + + fn create_store<'a, Txn: Deref>>( + db: &TempLmdbDatabase, + txn: &'a Txn, + ) -> ValidatorNodeStore<'a, Txn> { + let store_db = db.get_db(DBS[0]).clone(); + let index_db = db.get_db(DBS[1]).clone(); + ValidatorNodeStore::new(txn, store_db, index_db) + } + + fn insert_n_vns( + store: &ValidatorNodeStore<'_, WriteTransaction<'_>>, + start_height: u64, + n: usize, + ) -> Vec<(PublicKey, ShardKey)> { + let mut nodes = Vec::new(); + for i in 0..n { + let public_key = new_public_key(); + let shard_key = make_hash(public_key.as_bytes()); + store + .insert(start_height + i as u64, &ValidatorNodeEntry { + public_key: public_key.clone(), + shard_key, + commitment: Commitment::from_public_key(&new_public_key()), + ..Default::default() + }) + .unwrap(); + nodes.push((public_key, shard_key)); + } + nodes.sort_by(|(_, a), (_, b)| a.cmp(b)); + nodes + } + + mod insert { + use super::*; + + #[test] + fn it_inserts_validator_nodes() { + let db = TempLmdbDatabase::with_dbs(DBS); + let txn = db.write_transaction(); + let store = create_store(&db, &txn); + let nodes = insert_n_vns(&store, 1, 3); + let set = store.get_vn_set(1, 3).unwrap(); + assert_eq!(set[0], nodes[0]); + assert_eq!(set[1], nodes[1]); + assert_eq!(set[2], nodes[2]); + } + + #[test] + fn it_does_not_allow_duplicate_entries() { + let db = TempLmdbDatabase::with_dbs(DBS); + let txn = db.write_transaction(); + let store = create_store(&db, &txn); + let p1 = new_public_key(); + let entry = ValidatorNodeEntry { + shard_key: make_hash(p1.as_bytes()), + public_key: p1, + commitment: Commitment::from_public_key(&new_public_key()), + ..Default::default() + }; + store.insert(1, &entry).unwrap(); + let err = store.insert(1, &entry).unwrap_err(); + unpack_enum!(ChainStorageError::KeyExists { .. } = err); + } + } + + mod get_vn_set { + use super::*; + + #[test] + fn it_returns_a_deduped_set_of_validator_nodes() { + let db = TempLmdbDatabase::with_dbs(DBS); + let txn = db.write_transaction(); + let store = create_store(&db, &txn); + let nodes = insert_n_vns(&store, 1, 3); + // Node 0 and 1 re-register at height 4 + + let s0 = make_hash(nodes[0].1); + store + .insert(4, &ValidatorNodeEntry { + public_key: nodes[0].0.clone(), + shard_key: s0, + commitment: Commitment::from_public_key(&new_public_key()), + ..Default::default() + }) + .unwrap(); + + let s1 = make_hash(nodes[1].1); + // The commitment is used last in the key and so changes the order they appear in the LMDB btree. + // We insert them in reverse order to demonstrate that insert order does not necessarily match the vn set + // order. + let mut ordered_commitments = vec![ + Commitment::from_public_key(&new_public_key()), + Commitment::from_public_key(&new_public_key()), + ]; + ordered_commitments.sort(); + store + .insert(5, &ValidatorNodeEntry { + public_key: nodes[1].0.clone(), + shard_key: make_hash(s1), + commitment: ordered_commitments[1].clone(), + ..Default::default() + }) + .unwrap(); + // This insert is counted as before the previous one because the commitment is "less" + store + .insert(5, &ValidatorNodeEntry { + public_key: nodes[1].0.clone(), + shard_key: s1, + commitment: ordered_commitments[0].clone(), + ..Default::default() + }) + .unwrap(); + + let set = store.get_vn_set(1, 5).unwrap(); + // s1 and s2 have replaced the previous shard keys, and are now ordered last since they come after node2 + assert_eq!(set.len(), 3); + assert_eq!(set.iter().filter(|s| s.0 == nodes[1].0).count(), 1); + } + } + + mod get_shard_key { + use super::*; + + #[test] + fn it_returns_latest_shard_key() { + let db = TempLmdbDatabase::with_dbs(DBS); + let txn = db.write_transaction(); + let store = create_store(&db, &txn); + let nodes = insert_n_vns(&store, 1, 3); + let new_shard_key = make_hash(nodes[0].1); + store + .insert(4, &ValidatorNodeEntry { + public_key: nodes[0].0.clone(), + shard_key: new_shard_key, + commitment: Commitment::from_public_key(&new_public_key()), + + ..Default::default() + }) + .unwrap(); + + // Height 0-3 has original shard key + let s = store.get_shard_key(0, 3, &nodes[0].0).unwrap().unwrap(); + assert_eq!(s, nodes[0].1); + // Height 0-4 has shard key that was replaced at height 4 + let s = store.get_shard_key(0, 4, &nodes[0].0).unwrap().unwrap(); + assert_eq!(s, new_shard_key); + assert!(store.get_shard_key(5, 5, &nodes[0].0).unwrap().is_none()); + let s = store.get_shard_key(0, 3, &nodes[1].0).unwrap().unwrap(); + assert_eq!(s, nodes[1].1); + let s = store.get_shard_key(0, 3, &nodes[2].0).unwrap().unwrap(); + assert_eq!(s, nodes[2].1); + } + } +} diff --git a/base_layer/core/src/chain_storage/mod.rs b/base_layer/core/src/chain_storage/mod.rs index 6777a0fd05..077148bdec 100644 --- a/base_layer/core/src/chain_storage/mod.rs +++ b/base_layer/core/src/chain_storage/mod.rs @@ -37,6 +37,7 @@ pub use block_add_result::BlockAddResult; mod blockchain_database; pub use blockchain_database::{ calculate_mmr_roots, + calculate_validator_node_mr, fetch_header, fetch_headers, fetch_target_difficulty_for_next_block, @@ -81,7 +82,7 @@ pub use target_difficulties::TargetDifficulties; pub use utxo_mined_info::*; mod active_validator_node; -pub use active_validator_node::ActiveValidatorNode; +pub use active_validator_node::ValidatorNodeEntry; mod template_registation; pub use template_registation::TemplateRegistrationEntry; diff --git a/base_layer/core/src/chain_storage/tests/mod.rs b/base_layer/core/src/chain_storage/tests/mod.rs index 28a3520df2..6a6784235a 100644 --- a/base_layer/core/src/chain_storage/tests/mod.rs +++ b/base_layer/core/src/chain_storage/tests/mod.rs @@ -21,3 +21,4 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mod blockchain_database; +pub mod temp_db; diff --git a/base_layer/core/src/chain_storage/tests/temp_db.rs b/base_layer/core/src/chain_storage/tests/temp_db.rs new file mode 100644 index 0000000000..786fc1cd9c --- /dev/null +++ b/base_layer/core/src/chain_storage/tests/temp_db.rs @@ -0,0 +1,89 @@ +// 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::{collections::HashMap, path::PathBuf}; + +use lmdb_zero::{db, ReadTransaction, WriteTransaction}; +use tari_storage::lmdb_store::{DatabaseRef, LMDBBuilder, LMDBConfig}; +use tari_test_utils::paths::create_temporary_data_path; + +pub struct TempLmdbDatabase { + temp_path: PathBuf, + default_db: Option, + dbs: HashMap<&'static str, DatabaseRef>, +} + +impl TempLmdbDatabase { + pub fn new() -> Self { + Self::with_dbs(&[]) + } + + pub fn with_dbs(dbs: &[&'static str]) -> Self { + let temp_path = create_temporary_data_path(); + + let mut builder = LMDBBuilder::new() + .set_path(&temp_path) + .set_env_config(LMDBConfig::default()) + .set_max_number_of_databases(1) + .add_database("__default", db::CREATE); + + for db in dbs { + builder = builder.add_database(db, db::CREATE) + } + let lmdb_store = builder.build().unwrap(); + + let default_db = lmdb_store.get_handle("__default").unwrap(); + + Self { + temp_path, + default_db: Some(default_db.db()), + dbs: dbs + .iter() + .map(|db| (*db, lmdb_store.get_handle(db).unwrap().db())) + .collect(), + } + } + + pub fn default_db(&self) -> &DatabaseRef { + self.default_db.as_ref().unwrap() + } + + pub fn get_db(&self, name: &'static str) -> &DatabaseRef { + self.dbs.get(name).unwrap() + } + + pub fn write_transaction(&self) -> WriteTransaction<'_> { + WriteTransaction::new(self.default_db().env()).unwrap() + } + + pub fn read_transaction(&self) -> ReadTransaction<'_> { + ReadTransaction::new(self.default_db().env()).unwrap() + } +} + +impl Drop for TempLmdbDatabase { + fn drop(&mut self) { + drop(self.default_db.take()); + drop(self.dbs.drain()); + let _ignore = std::fs::remove_dir_all(&self.temp_path); + } +} diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index ed8412cbb2..bf54a2fb61 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -27,6 +27,7 @@ use std::{ use chrono::{DateTime, Duration, Utc}; use tari_common::configuration::Network; +use tari_common_types::epoch::VnEpoch; use tari_script::{script, OpcodeVersion}; use tari_utilities::epoch_time::EpochTime; @@ -95,10 +96,15 @@ 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, /// Coinbase outputs are allowed to have metadata, but it has the following length limit coinbase_output_features_metadata_max_length: usize, + /// Epoch duration in blocks + vn_epoch_length: u64, + /// The number of Epochs that a validator node registration is valid + vn_validity_period: VnEpoch, + vn_registration_min_deposit_amount: MicroTari, + vn_registration_lock_height: u64, + vn_registration_shuffle_interval: VnEpoch, } // todo: remove this once OutputFeaturesVersion is removed in favor of just TransactionOutputVersion @@ -297,8 +303,35 @@ impl ConsensusConstants { self.permitted_output_types } - pub fn validator_node_timeout(&self) -> u64 { - self.validator_node_timeout + pub fn validator_node_validity_period(&self) -> VnEpoch { + self.vn_validity_period + } + + pub fn validator_node_registration_shuffle_interval(&self) -> VnEpoch { + self.vn_registration_shuffle_interval + } + + pub fn validator_node_registration_min_deposit_amount(&self) -> MicroTari { + self.vn_registration_min_deposit_amount + } + + pub fn validator_node_registration_min_lock_height(&self) -> u64 { + self.vn_registration_lock_height + } + + /// Returns the current epoch from the given height + pub fn block_height_to_current_epoch(&self, height: u64) -> VnEpoch { + VnEpoch(height / self.vn_epoch_length) + } + + pub fn epoch_length(&self) -> u64 { + self.vn_epoch_length + } + + /// Returns the next epoch up from the given height + pub fn block_height_to_next_epoch(&self, height: u64) -> VnEpoch { + let rem = height % self.vn_epoch_length; + VnEpoch((height + rem) / self.vn_epoch_length) } pub fn localnet() -> Vec { @@ -338,7 +371,11 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: OutputType::all(), - validator_node_timeout: 100, + vn_epoch_length: 10, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }] } @@ -380,7 +417,11 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), - validator_node_timeout: 0, + vn_epoch_length: 60, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }] } @@ -392,13 +433,13 @@ impl ConsensusConstants { max_target_time: 1800, min_difficulty: 60_000_000.into(), max_difficulty: u64::MAX.into(), - target_time: 150, + target_time: 15, }); algos.insert(PowAlgorithm::Monero, PowAlgorithmConstants { max_target_time: 1200, min_difficulty: 60_000.into(), max_difficulty: u64::MAX.into(), - target_time: 100, + target_time: 10, }); let (input_version_range, output_version_range, kernel_version_range) = version_zero(); vec![ConsensusConstants { @@ -426,7 +467,11 @@ impl ConsensusConstants { kernel_version_range, // igor is the first network to support the new output types permitted_output_types: OutputType::all(), - validator_node_timeout: 100, + vn_epoch_length: 10, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }] } @@ -478,7 +523,11 @@ 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, + vn_epoch_length: 60, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }, ConsensusConstants { @@ -503,7 +552,11 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), - validator_node_timeout: 0, + vn_epoch_length: 60, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }, ] @@ -552,7 +605,11 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), - validator_node_timeout: 50, + vn_epoch_length: 60, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }; @@ -597,7 +654,11 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), - validator_node_timeout: 0, + vn_epoch_length: 60, + vn_validity_period: VnEpoch(100), + vn_registration_min_deposit_amount: MicroTari(0), + vn_registration_lock_height: 0, + vn_registration_shuffle_interval: VnEpoch(100), coinbase_output_features_metadata_max_length: 64, }] } diff --git a/base_layer/core/src/proto/sidechain_feature.rs b/base_layer/core/src/proto/sidechain_feature.rs index 7f3998c67c..84cf91af51 100644 --- a/base_layer/core/src/proto/sidechain_feature.rs +++ b/base_layer/core/src/proto/sidechain_feature.rs @@ -36,6 +36,7 @@ use crate::{ SideChainFeature, TemplateType, ValidatorNodeRegistration, + ValidatorNodeSignature, }, }; @@ -81,21 +82,21 @@ impl TryFrom for ValidatorNodeRegistrat 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 + Ok(Self::new(ValidatorNodeSignature::new( + PublicKey::from_bytes(&value.public_key).map_err(|e| e.to_string())?, + 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()), + public_key: value.public_key().to_vec(), + signature: Some(value.signature().into()), } } } diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index d87828f32a..0f95a033f9 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -182,6 +182,10 @@ impl TempDatabase { self.delete_on_drop = false; self } + + pub fn db(&self) -> &LMDBDatabase { + self.db.as_ref().unwrap() + } } impl Default for TempDatabase { diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index a9287b7951..ab79299e9f 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -26,9 +26,11 @@ use std::{iter, path::Path, sync::Arc}; pub use block_spec::{BlockSpec, BlockSpecs}; -use rand::{distributions::Alphanumeric, Rng}; +use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; use tari_common::configuration::Network; +use tari_common_types::types::PublicKey; use tari_comms::PeerManager; +use tari_crypto::keys::PublicKey as PublicKeyT; use tari_storage::{lmdb_store::LMDBBuilder, LMDBWrapper}; use crate::{ @@ -147,3 +149,13 @@ pub fn create_chain_header(header: BlockHeader, prev_accum: &BlockHeaderAccumula .unwrap(); ChainHeader::try_construct(header, accumulated_data).unwrap() } + +pub fn new_public_key() -> PublicKey { + PublicKey::random_keypair(&mut OsRng).1 +} + +pub fn make_hash>(preimage: T) -> [u8; 32] { + use digest::Digest; + use tari_crypto::hash::blake2::Blake256; + Blake256::new().chain(preimage.as_ref()).finalize().into() +} 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 7c8c3098f2..b1b52d7160 100644 --- a/base_layer/core/src/transactions/transaction_components/kernel_features.rs +++ b/base_layer/core/src/transactions/transaction_components/kernel_features.rs @@ -32,8 +32,6 @@ bitflags! { const COINBASE_KERNEL = 1u8; /// Burned output transaction const BURN_KERNEL = 2u8; - /// Validator node registration transaction - const VALIDATOR_NODE_REGISTRATION = 3u8; } } @@ -52,10 +50,6 @@ 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 034e3407d3..b5af636792 100644 --- a/base_layer/core/src/transactions/transaction_components/output_features.rs +++ b/base_layer/core/src/transactions/transaction_components/output_features.rs @@ -36,6 +36,7 @@ use crate::transactions::transaction_components::{ CodeTemplateRegistration, OutputType, ValidatorNodeRegistration, + ValidatorNodeSignature, }; /// Options for UTXO's @@ -114,14 +115,22 @@ impl OutputFeatures { ) -> OutputFeatures { OutputFeatures { output_type: OutputType::ValidatorNodeRegistration, - sidechain_feature: Some(SideChainFeature::ValidatorNodeRegistration(ValidatorNodeRegistration { - public_key: validator_node_public_key, - signature: validator_node_signature, - })), + sidechain_feature: Some(SideChainFeature::ValidatorNodeRegistration( + ValidatorNodeRegistration::new(ValidatorNodeSignature::new( + validator_node_public_key, + validator_node_signature, + )), + )), ..Default::default() } } + pub fn validator_node_registration(&self) -> Option<&ValidatorNodeRegistration> { + self.sidechain_feature + .as_ref() + .and_then(|s| s.validator_node_registration()) + } + pub fn is_coinbase(&self) -> bool { matches!(self.output_type, OutputType::Coinbase) } 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 4922052163..35b39167a9 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 @@ -25,9 +25,12 @@ pub use sidechain_feature::SideChainFeature; mod template_registration; mod validator_node_registration; +mod validator_node_signature; + use tari_crypto::{hash::blake2::Blake256, hash_domain, hashing::DomainSeparatedHasher}; pub use template_registration::{BuildInfo, CodeTemplateRegistration, TemplateType}; pub use validator_node_registration::ValidatorNodeRegistration; +pub use validator_node_signature::{ValidatorNodeHashDomain, ValidatorNodeSignature}; hash_domain!( ContractAcceptanceHashDomain, 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 index 29c38cbcef..8917a57beb 100644 --- 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 @@ -21,69 +21,94 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use borsh::{BorshDeserialize, BorshSerialize}; -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 tari_common_types::{ + epoch::VnEpoch, + types::{FixedHash, PublicKey, Signature}, +}; +use tari_utilities::ByteArray; -use crate::{consensus::DomainSeparatedConsensusHasher, transactions::TransactionHashDomain}; +use crate::{ + consensus::DomainSeparatedConsensusHasher, + transactions::{transaction_components::ValidatorNodeSignature, TransactionHashDomain}, + U256, +}; #[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] pub struct ValidatorNodeRegistration { - pub public_key: PublicKey, - pub signature: Signature, + signature: ValidatorNodeSignature, } impl ValidatorNodeRegistration { + pub fn new(signature: ValidatorNodeSignature) -> Self { + Self { signature } + } + 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) + self.signature.is_valid_signature_for(msg) } - 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_raw(private_key, secret_nonce, &*challenge) - .expect("Sign cannot fail with 32-byte challenge and a RistrettoPublicKey"); - Self { public_key, signature } + pub fn derive_shard_key( + &self, + prev_shard_key: Option<[u8; 32]>, + epoch: VnEpoch, + interval: VnEpoch, + block_hash: &FixedHash, + ) -> [u8; 32] { + match prev_shard_key { + Some(prev) => { + if does_require_new_shard_key(self.public_key(), epoch, interval) { + generate_shard_key(self.public_key(), &**block_hash) + } else { + prev + } + }, + None => generate_shard_key(self.public_key(), &**block_hash), + } } - 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 public_key(&self) -> &PublicKey { + self.signature.public_key() } - pub fn derive_shard_key(&self, block_hash: &FixedHash) -> [u8; 32] { - DomainSeparatedConsensusHasher::::new("validator_node_root") - // - .chain(self) - .chain(block_hash) - .finalize() + pub fn signature(&self) -> &Signature { + self.signature.signature() } } +fn does_require_new_shard_key(public_key: &PublicKey, epoch: VnEpoch, interval: VnEpoch) -> bool { + let pk = U256::from_big_endian(public_key.as_bytes()); + let epoch = U256::from(epoch.as_u64()); + let interval = U256::from(interval.as_u64()); + (pk + epoch) % interval == U256::zero() +} + +fn generate_shard_key(public_key: &PublicKey, entropy: &[u8; 32]) -> [u8; 32] { + DomainSeparatedConsensusHasher::::new("validator_node_shard_key") + .chain(public_key) + .chain(entropy) + .finalize() +} + #[cfg(test)] mod test { use rand::rngs::OsRng; + use tari_common_types::types::PrivateKey; use tari_crypto::keys::SecretKey; use super::*; + use crate::test_helpers::new_public_key; fn create_instance() -> ValidatorNodeRegistration { let sk = PrivateKey::random(&mut OsRng); - ValidatorNodeRegistration::new_signed(&sk, b"valid") + ValidatorNodeRegistration::new(ValidatorNodeSignature::sign(&sk, b"valid")) } mod is_valid_signature_for { use super::*; #[test] - fn it_returns_true_for_invalid_signature() { + fn it_returns_true_for_valid_signature() { let reg = create_instance(); assert!(reg.is_valid_signature_for(b"valid")); } @@ -97,8 +122,27 @@ mod test { #[test] fn it_returns_false_for_invalid_signature() { let mut reg = create_instance(); - reg.public_key = create_instance().public_key; + reg = ValidatorNodeRegistration::new(ValidatorNodeSignature::new( + reg.public_key().clone(), + Signature::default(), + )); assert!(!reg.is_valid_signature_for(b"valid")); } } + + mod does_require_new_shard_key { + use super::*; + + #[test] + fn it_returns_true_a_set_number_of_times_over_a_range_of_epochs() { + const INTERVAL: VnEpoch = VnEpoch(100); + const NUM_EPOCHS: u64 = 1000; + let pk = new_public_key(); + let count = (0u64..NUM_EPOCHS) + .filter(|e| does_require_new_shard_key(&pk, VnEpoch(*e), INTERVAL)) + .count() as u64; + + assert_eq!(count, NUM_EPOCHS / INTERVAL.as_u64()); + } + } } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs new file mode 100644 index 0000000000..b289131d00 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_signature.rs @@ -0,0 +1,74 @@ +// 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 borsh::{BorshDeserialize, BorshSerialize}; +use rand::rngs::OsRng; +use serde::{Deserialize, Serialize}; +use tari_common_types::types::{FixedHash, PrivateKey, PublicKey, Signature}; +use tari_crypto::{hash::blake2::Blake256, hash_domain, hashing::DomainSeparatedHasher, keys::PublicKey as PublicKeyT}; +use tari_utilities::ByteArray; + +hash_domain!(ValidatorNodeHashDomain, "com.tari.dan_layer.validator_node", 0); + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)] +pub struct ValidatorNodeSignature { + public_key: PublicKey, + signature: Signature, +} + +impl ValidatorNodeSignature { + pub fn new(public_key: PublicKey, signature: Signature) -> Self { + Self { public_key, signature } + } + + // TODO: pass in commitment instead of arbitrary message + pub fn sign(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); + // TODO: Changing to use the new signing API requires a lot of changes + let signature = Signature::sign_raw(private_key, secret_nonce, &*challenge) + .expect("Sign cannot fail with 32-byte challenge and a RistrettoPublicKey"); + Self { public_key, signature } + } + + fn construct_challenge(public_key: &PublicKey, public_nonce: &PublicKey, msg: &[u8]) -> FixedHash { + let hasher = DomainSeparatedHasher::::new_with_label("registration") + .chain(public_key.as_bytes()) + .chain(public_nonce.as_bytes()) + .chain(msg); + digest::Digest::finalize(hasher).into() + } + + 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 public_key(&self) -> &PublicKey { + &self.public_key + } + + pub fn signature(&self) -> &Signature { + &self.signature + } +} 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 b23d9fa45c..7c886cb405 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -226,8 +226,10 @@ impl TransactionOutput { .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"") { + // TODO(SECURITY): Signing this with a blank msg allows the signature to be replayed. Using the commitment + // is ideal as uniqueness is enforced. However, because the VN and wallet have different + // keys this becomes difficult. Fix this once we have decided on a solution. + if !validator_node_reg.is_valid_signature_for(&[]) { return Err(TransactionError::InvalidSignatureError( "Validator node signature is not valid!".to_string(), )); 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 af446e357e..a3d5e99dd7 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -439,6 +439,7 @@ impl BlockValidator { output.verify_metadata_signature()?; output.verify_validator_node_signature()?; helpers::check_not_duplicate_txo(&*db, output)?; + helpers::check_validator_node_registration_utxo(&constants, output)?; commitment_sum = &commitment_sum + &output.commitment; } if !bypass_range_proof_verification { diff --git a/base_layer/core/src/validation/block_validators/body_only.rs b/base_layer/core/src/validation/block_validators/body_only.rs index bfee04b11b..9f6cbce725 100644 --- a/base_layer/core/src/validation/block_validators/body_only.rs +++ b/base_layer/core/src/validation/block_validators/body_only.rs @@ -120,7 +120,7 @@ impl PostOrphanBodyValidation for BodyOnlyValidator { "Block validation: All inputs, outputs and kernels are valid for {}", block_id ); - let mmr_roots = chain_storage::calculate_mmr_roots(backend, block.block())?; + let mmr_roots = chain_storage::calculate_mmr_roots(backend, &self.rules, block.block())?; helpers::check_mmr_roots(block.header(), &mmr_roots)?; trace!( target: LOG_TARGET, diff --git a/base_layer/core/src/validation/block_validators/orphan.rs b/base_layer/core/src/validation/block_validators/orphan.rs index 161e4ac69f..cc557a33af 100644 --- a/base_layer/core/src/validation/block_validators/orphan.rs +++ b/base_layer/core/src/validation/block_validators/orphan.rs @@ -38,6 +38,7 @@ use crate::{ check_permitted_output_types, check_sorting_and_duplicates, check_total_burned, + check_validator_node_registration_utxo, validate_versions, }, OrphanValidation, @@ -97,6 +98,7 @@ impl OrphanValidation for OrphanBlockValidator { for output in block.body.outputs() { check_permitted_output_types(constants, output)?; + check_validator_node_registration_utxo(constants, output)?; } check_total_burned(&block.body)?; diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 52a1b09bc1..765739d128 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -30,7 +30,10 @@ use crate::{ chain_storage::ChainStorageError, covenants::CovenantError, proof_of_work::{monero_rx::MergeMineError, PowError}, - transactions::transaction_components::{OutputType, TransactionError}, + transactions::{ + tari_amount::MicroTari, + transaction_components::{OutputType, TransactionError}, + }, }; #[derive(Debug, Error)] @@ -136,6 +139,12 @@ pub enum ValidationError { FixedHashSizeError(#[from] FixedHashSizeError), #[error("Validator node MMR is not correct")] ValidatorNodeMmmrError, + #[error("Validator registration has invalid minimum amount {actual}, must be at least {min}")] + ValidatorNodeRegistrationMinDepositAmount { min: MicroTari, actual: MicroTari }, + #[error("Validator registration has invalid maturity {actual}, must be at least {min}")] + ValidatorNodeRegistrationMinLockHeight { min: u64, actual: u64 }, + #[error("Validator node registration signature failed verification")] + InvalidValidatorNodeSignature, } // 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 02c5d2dccb..93e45957f9 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -434,6 +434,7 @@ pub fn check_outputs( check_permitted_output_types(constants, output)?; check_tari_script_byte_size(&output.script, max_script_size)?; check_not_duplicate_txo(db, output)?; + check_validator_node_registration_utxo(constants, output)?; } Ok(()) } @@ -831,6 +832,34 @@ pub fn validate_versions( Ok(()) } +pub fn check_validator_node_registration_utxo( + consensus_constants: &ConsensusConstants, + utxo: &TransactionOutput, +) -> Result<(), ValidationError> { + if let Some(reg) = utxo.features.validator_node_registration() { + if utxo.minimum_value_promise < consensus_constants.validator_node_registration_min_deposit_amount() { + return Err(ValidationError::ValidatorNodeRegistrationMinDepositAmount { + min: consensus_constants.validator_node_registration_min_deposit_amount(), + actual: utxo.minimum_value_promise, + }); + } + if utxo.features.maturity < consensus_constants.validator_node_registration_min_lock_height() { + return Err(ValidationError::ValidatorNodeRegistrationMinLockHeight { + min: consensus_constants.validator_node_registration_min_lock_height(), + actual: utxo.features.maturity, + }); + } + + // TODO(SECURITY): Signing this with a blank msg allows the signature to be replayed. Using the commitment + // is ideal as uniqueness is enforced. However, because the VN and wallet have different + // keys this becomes difficult. Fix this once we have decided on a solution. + if !reg.is_valid_signature_for(&[]) { + return Err(ValidationError::InvalidValidatorNodeSignature); + } + } + Ok(()) +} + #[cfg(test)] mod test { use tari_test_utils::unpack_enum; diff --git a/base_layer/core/src/validation/transaction_validators.rs b/base_layer/core/src/validation/transaction_validators.rs index 59da19b2a9..3bf36144d3 100644 --- a/base_layer/core/src/validation/transaction_validators.rs +++ b/base_layer/core/src/validation/transaction_validators.rs @@ -32,6 +32,7 @@ use crate::{ check_outputs, check_permitted_output_types, check_total_burned, + check_validator_node_registration_utxo, validate_versions, }, MempoolTransactionValidation, @@ -138,6 +139,7 @@ impl MempoolTransactionValidation for TxConsensusValidator validate_versions(tx.body(), consensus_constants)?; for output in tx.body.outputs() { check_permitted_output_types(consensus_constants, output)?; + check_validator_node_registration_utxo(consensus_constants, output)?; } Ok(()) } diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index bb80a3ebd8..3c9a7897b6 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -28,7 +28,13 @@ use tari_common::configuration::Network; use tari_common_types::types::{Commitment, FixedHash, PublicKey}; use tari_core::{ blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, NewBlockTemplate}, - chain_storage::{BlockAddResult, BlockchainBackend, BlockchainDatabase, ChainStorageError}, + chain_storage::{ + calculate_validator_node_mr, + BlockAddResult, + BlockchainBackend, + BlockchainDatabase, + ChainStorageError, + }, consensus::{emission::Emission, ConsensusConstants, ConsensusManager, ConsensusManagerBuilder}, covenants::Covenant, proof_of_work::{sha3x_difficulty, AchievedTargetDifficulty, Difficulty}, @@ -58,7 +64,6 @@ use tari_core::{ KernelMmr, KernelMmrHasherBlake256, MutableOutputMmr, - ValidatorNodeMmr, WitnessMmr, WitnessMmrHasherBlake256, }; @@ -160,14 +165,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()); + let vn_mr = calculate_validator_node_mr(&[]).unwrap(); 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.validator_node_mr = FixedHash::try_from(vn_mr).unwrap(); // header.kernel_mr = kernel.hash(); // header.kernel_mmr_size += 1; diff --git a/base_layer/core/tests/helpers/database.rs b/base_layer/core/tests/helpers/database.rs index d071376855..169ad00eb2 100644 --- a/base_layer/core/tests/helpers/database.rs +++ b/base_layer/core/tests/helpers/database.rs @@ -30,8 +30,6 @@ use tari_core::{ use crate::helpers::block_builders::create_coinbase; -// use tari_test_utils::paths::create_temporary_data_path; - /// Create a partially constructed block using the provided set of transactions /// is chain_block, or rename it to `create_orphan_block` and drop the prev_block argument #[allow(dead_code)] diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 81d190b241..1d2ebf6e49 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -89,6 +89,7 @@ pub enum TransactionServiceRequest { message: String, }, RegisterValidatorNode { + amount: MicroTari, validator_node_public_key: CommsPublicKey, validator_node_signature: Signature, selection_criteria: UtxoSelectionCriteria, @@ -463,6 +464,7 @@ impl TransactionServiceHandle { pub async fn register_validator_node( &mut self, + amount: MicroTari, validator_node_public_key: PublicKey, validator_node_signature: Signature, selection_criteria: UtxoSelectionCriteria, @@ -472,6 +474,7 @@ impl TransactionServiceHandle { match self .handle .call(TransactionServiceRequest::RegisterValidatorNode { + amount, validator_node_public_key, validator_node_signature, selection_criteria, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 5b55a6a353..4c0c8fb4c4 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -651,6 +651,7 @@ where .await .map(TransactionServiceResponse::TransactionSent), TransactionServiceRequest::RegisterValidatorNode { + amount, validator_node_public_key, validator_node_signature, selection_criteria, @@ -659,6 +660,7 @@ where } => { let rp = reply_channel.take().expect("Cannot be missing"); self.register_validator_node( + amount, validator_node_public_key, validator_node_signature, selection_criteria, @@ -1483,6 +1485,7 @@ where pub async fn register_validator_node( &mut self, + amount: MicroTari, validator_node_public_key: CommsPublicKey, validator_node_signature: Signature, selection_criteria: UtxoSelectionCriteria, @@ -1498,16 +1501,14 @@ where ) -> 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.resources.wallet_identity.address.clone(), - MicroTari::from(1), + amount, selection_criteria, output_features, fee_per_gram, message, - tx_meta, + TransactionMetadata::default(), join_handles, transaction_broadcast_join_handles, reply_channel,