From 96a30c1662a88e10059da17d114148fe06bf9c43 Mon Sep 17 00:00:00 2001 From: Martin Stefcek <35243812+Cifko@users.noreply.github.com> Date: Tue, 6 Sep 2022 14:58:41 +0200 Subject: [PATCH] feat: add validator node registration (#4507) --- .../tari_app_grpc/proto/base_node.proto | 22 ++ applications/tari_app_grpc/proto/block.proto | 4 + .../tari_app_grpc/proto/transaction.proto | 2 + applications/tari_app_grpc/proto/wallet.proto | 15 ++ .../src/conversions/active_validator_node.rs | 59 ++++++ .../src/conversions/block_header.rs | 2 + .../tari_app_grpc/src/conversions/mod.rs | 1 + .../src/conversions/new_block_template.rs | 2 + .../src/conversions/output_features.rs | 15 ++ applications/tari_base_node/src/builder.rs | 2 + .../src/grpc/base_node_grpc_server.rs | 96 +++++++++ applications/tari_base_node/src/recovery.rs | 13 +- .../src/automation/commands.rs | 36 +++- applications/tari_console_wallet/src/cli.rs | 10 + .../src/grpc/wallet_grpc_server.rs | 42 ++++ .../tari_console_wallet/src/wallet_modes.rs | 1 + .../comms_interface/comms_request.rs | 8 + .../comms_interface/comms_response.rs | 6 +- .../comms_interface/inbound_handlers.rs | 14 +- .../comms_interface/local_interface.rs | 30 +++ .../src/base_node/sync/header_sync/error.rs | 8 + .../base_node/sync/header_sync/validator.rs | 8 +- base_layer/core/src/blocks/block_header.rs | 11 +- base_layer/core/src/blocks/genesis_block.rs | 7 + .../src/blocks/new_blockheader_template.rs | 3 + .../chain_storage/active_validator_node.rs | 32 +++ base_layer/core/src/chain_storage/async_db.rs | 7 + .../src/chain_storage/blockchain_backend.rs | 4 + .../src/chain_storage/blockchain_database.rs | 20 ++ .../core/src/chain_storage/db_transaction.rs | 13 +- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 192 +++++++++++++++++- base_layer/core/src/chain_storage/mod.rs | 3 + .../tests/blockchain_database.rs | 11 +- .../core/src/consensus/consensus_constants.rs | 13 ++ base_layer/core/src/lib.rs | 8 + .../src/proof_of_work/monero_rx/helpers.rs | 15 ++ base_layer/core/src/proto/block.proto | 4 + base_layer/core/src/proto/block.rs | 2 + base_layer/core/src/proto/block_header.rs | 2 + base_layer/core/src/proto/transaction.proto | 2 + base_layer/core/src/proto/transaction.rs | 12 +- .../core/src/test_helpers/blockchain.rs | 18 +- base_layer/core/src/test_helpers/mod.rs | 3 +- .../transaction_components/error.rs | 2 + .../transaction_components/kernel_features.rs | 6 + .../transaction_components/output_features.rs | 30 ++- .../transaction_output.rs | 16 ++ .../block_validators/async_validator.rs | 24 +++ .../src/validation/block_validators/test.rs | 1 + base_layer/core/src/validation/error.rs | 2 + base_layer/core/src/validation/test.rs | 9 +- .../chain_storage_tests/chain_backend.rs | 11 +- .../chain_storage_tests/chain_storage.rs | 21 +- .../core/tests/helpers/block_builders.rs | 13 +- base_layer/core/tests/mempool.rs | 2 + .../wallet/src/transaction_service/handle.rs | 36 +++- .../wallet/src/transaction_service/service.rs | 53 ++++- base_layer/wallet_ffi/src/lib.rs | 10 +- 58 files changed, 959 insertions(+), 55 deletions(-) create mode 100644 applications/tari_app_grpc/src/conversions/active_validator_node.rs create mode 100644 base_layer/core/src/chain_storage/active_validator_node.rs diff --git a/applications/tari_app_grpc/proto/base_node.proto b/applications/tari_app_grpc/proto/base_node.proto index 0df29e5b98..d5cb26a707 100644 --- a/applications/tari_app_grpc/proto/base_node.proto +++ b/applications/tari_app_grpc/proto/base_node.proto @@ -88,6 +88,9 @@ service BaseNode { rpc ListConnectedPeers(Empty) returns (ListConnectedPeersResponse); // Get mempool stats rpc GetMempoolStats(Empty) returns (MempoolStatsResponse); + // Get VNs + rpc GetActiveValidatorNodes(GetActiveValidatorNodesRequest) returns (stream ActiveValidatorNode); + rpc GetCommittee(GetCommitteeRequest) returns (GetCommitteeResponse); } @@ -434,3 +437,22 @@ message MempoolStatsResponse { uint64 unconfirmed_weight = 4; } +message GetActiveValidatorNodesRequest { + uint64 height = 1; +} + +message ActiveValidatorNode { + bytes shard_key = 1; + uint64 from_height = 2; + uint64 to_height = 3; + bytes public_key = 4; +} + +message GetCommitteeRequest { + uint64 height = 1; + bytes shard_key = 2; +} + +message GetCommitteeResponse { + repeated bytes public_key = 1; +} \ No newline at end of file diff --git a/applications/tari_app_grpc/proto/block.proto b/applications/tari_app_grpc/proto/block.proto index 821487e224..04904a91a8 100644 --- a/applications/tari_app_grpc/proto/block.proto +++ b/applications/tari_app_grpc/proto/block.proto @@ -61,6 +61,8 @@ message BlockHeader { uint64 output_mmr_size = 14; // Sum of script offsets for all kernels in this block. bytes total_script_offset = 15; + // Merkle root of validator nodes + bytes validator_node_merkle_root = 16; } // Metadata required for validating the Proof of Work calculation @@ -117,6 +119,8 @@ message NewBlockHeaderTemplate { // uint64 target_difficulty = 6; // Sum of script offsets for all kernels in this block. bytes total_script_offset = 7; + // Merkle root of validator nodes + bytes validator_node_merkle_root = 8; } // The new block template is used constructing a new partial block, allowing a miner to added the coinbase utxo and as a final step the Base node to add the MMR roots to the header. diff --git a/applications/tari_app_grpc/proto/transaction.proto b/applications/tari_app_grpc/proto/transaction.proto index a713f7f7f8..4e78b71378 100644 --- a/applications/tari_app_grpc/proto/transaction.proto +++ b/applications/tari_app_grpc/proto/transaction.proto @@ -124,6 +124,8 @@ message OutputFeatures { uint64 maturity = 3; bytes metadata = 4; SideChainFeatures sidechain_features = 5; + bytes validator_node_public_key = 6; + Signature validator_node_signature = 7; } diff --git a/applications/tari_app_grpc/proto/wallet.proto b/applications/tari_app_grpc/proto/wallet.proto index a873130a71..7e46740fb0 100644 --- a/applications/tari_app_grpc/proto/wallet.proto +++ b/applications/tari_app_grpc/proto/wallet.proto @@ -78,6 +78,8 @@ service Wallet { rpc StreamTransactionEvents(TransactionEventRequest) returns (stream TransactionEventResponse); rpc SeedWords(Empty) returns (SeedWordsResponse); rpc DeleteSeedWordsFile(Empty) returns (FileDeletedResponse); + + rpc RegisterValidatorNode(RegisterValidatorNodeRequest) returns (RegisterValidatorNodeResponse); } message GetVersionRequest { } @@ -326,4 +328,17 @@ message SeedWordsResponse { message FileDeletedResponse { +} + +message RegisterValidatorNodeRequest { + string validator_node_public_key = 1; + Signature validator_node_signature = 2; + uint64 fee_per_gram = 3; + string message = 4; +} + +message RegisterValidatorNodeResponse { + uint64 transaction_id = 1; + bool is_success = 2; + string failure_message = 3; } \ No newline at end of file diff --git a/applications/tari_app_grpc/src/conversions/active_validator_node.rs b/applications/tari_app_grpc/src/conversions/active_validator_node.rs new file mode 100644 index 0000000000..4c22cd8116 --- /dev/null +++ b/applications/tari_app_grpc/src/conversions/active_validator_node.rs @@ -0,0 +1,59 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::convert::{TryFrom, TryInto}; + +use tari_common_types::types::PublicKey; +use tari_core::chain_storage::ActiveValidatorNode; +use tari_utilities::ByteArray; + +use crate::tari_rpc as grpc; + +impl TryFrom for grpc::ActiveValidatorNode { + type Error = String; + + fn try_from(active_validator_node: ActiveValidatorNode) -> Result { + Ok(Self { + shard_key: active_validator_node.shard_key.to_vec(), + from_height: active_validator_node.from_height, + to_height: active_validator_node.to_height, + public_key: active_validator_node.public_key.to_vec(), + }) + } +} + +impl TryFrom for ActiveValidatorNode { + type Error = String; + + fn try_from(active_validator_node: grpc::ActiveValidatorNode) -> Result { + let shard_key = active_validator_node.shard_key.try_into().unwrap(); + let public_key = + PublicKey::from_vec(&active_validator_node.public_key).map_err(|_| "Could not public key".to_string())?; + + Ok(Self { + shard_key, + from_height: active_validator_node.from_height, + to_height: active_validator_node.to_height, + public_key, + }) + } +} diff --git a/applications/tari_app_grpc/src/conversions/block_header.rs b/applications/tari_app_grpc/src/conversions/block_header.rs index f1a72173b1..18705ff909 100644 --- a/applications/tari_app_grpc/src/conversions/block_header.rs +++ b/applications/tari_app_grpc/src/conversions/block_header.rs @@ -53,6 +53,7 @@ impl From for grpc::BlockHeader { pow_algo: pow_algo.as_u64(), pow_data: h.pow.pow_data, }), + validator_node_merkle_root: h.validator_node_merkle_root, } } } @@ -91,6 +92,7 @@ impl TryFrom for BlockHeader { total_script_offset, nonce: header.nonce, pow, + validator_node_merkle_root: header.validator_node_merkle_root, }) } } diff --git a/applications/tari_app_grpc/src/conversions/mod.rs b/applications/tari_app_grpc/src/conversions/mod.rs index c08c3d0cdb..69380b8d29 100644 --- a/applications/tari_app_grpc/src/conversions/mod.rs +++ b/applications/tari_app_grpc/src/conversions/mod.rs @@ -20,6 +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. +mod active_validator_node; mod aggregate_body; mod base_node_state; mod block; diff --git a/applications/tari_app_grpc/src/conversions/new_block_template.rs b/applications/tari_app_grpc/src/conversions/new_block_template.rs index 54e5a58c03..86a176c4de 100644 --- a/applications/tari_app_grpc/src/conversions/new_block_template.rs +++ b/applications/tari_app_grpc/src/conversions/new_block_template.rs @@ -45,6 +45,7 @@ impl TryFrom for grpc::NewBlockTemplate { pow_algo: block.header.pow.pow_algo.as_u64(), pow_data: block.header.pow.pow_data, }), + validator_node_merkle_root: block.header.validator_node_merkle_root, }; Ok(Self { body: Some(grpc::AggregateBody { @@ -91,6 +92,7 @@ impl TryFrom for NewBlockTemplate { total_kernel_offset, total_script_offset, pow, + validator_node_merkle_root: header.validator_node_merkle_root, }; let body = block .body diff --git a/applications/tari_app_grpc/src/conversions/output_features.rs b/applications/tari_app_grpc/src/conversions/output_features.rs index 15e6e40325..50b4603309 100644 --- a/applications/tari_app_grpc/src/conversions/output_features.rs +++ b/applications/tari_app_grpc/src/conversions/output_features.rs @@ -22,12 +22,14 @@ use std::convert::{TryFrom, TryInto}; +use tari_common_types::types::PublicKey; use tari_core::transactions::transaction_components::{ OutputFeatures, OutputFeaturesVersion, OutputType, SideChainFeatures, }; +use tari_utilities::ByteArray; use crate::tari_rpc as grpc; @@ -46,6 +48,9 @@ impl TryFrom for OutputFeatures { .try_into() .map_err(|_| "Invalid output type: overflow")?; + let validator_node_public_key = PublicKey::from_vec(&features.validator_node_public_key).ok(); + let validator_node_signature = features.validator_node_signature.map(|s| s.try_into()).transpose()?; + Ok(OutputFeatures::new( OutputFeaturesVersion::try_from( u8::try_from(features.version).map_err(|_| "Invalid version: overflowed u8")?, @@ -54,6 +59,8 @@ impl TryFrom for OutputFeatures { features.maturity, features.metadata, sidechain_features, + validator_node_public_key, + validator_node_signature, )) } } @@ -66,6 +73,14 @@ impl From for grpc::OutputFeatures { maturity: features.maturity, metadata: features.metadata, sidechain_features: features.sidechain_features.map(Into::into), + validator_node_public_key: features + .validator_node_public_key + .map(|pk| pk.as_bytes().to_vec()) + .unwrap_or_default(), + validator_node_signature: features.validator_node_signature.map(|s| grpc::Signature { + public_nonce: Vec::from(s.get_public_nonce().as_bytes()), + signature: Vec::from(s.get_signature().as_bytes()), + }), } } } diff --git a/applications/tari_base_node/src/builder.rs b/applications/tari_base_node/src/builder.rs index 8eff0b5f0b..9ea1b0a661 100644 --- a/applications/tari_base_node/src/builder.rs +++ b/applications/tari_base_node/src/builder.rs @@ -173,9 +173,11 @@ pub async fn configure_and_initialize_node( ) -> Result { let result = match &app_config.base_node.db_type { DatabaseType::Lmdb => { + let rules = ConsensusManager::builder(app_config.base_node.network).build(); let backend = create_lmdb_database( app_config.base_node.lmdb_path.as_path(), app_config.base_node.lmdb.clone(), + rules, ) .map_err(|e| ExitError::new(ExitCode::DatabaseError, e))?; build_node_context(backend, app_config, node_identity, interrupt_signal).await? diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index 3fbe992ea6..a7b70eb5bc 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -134,6 +134,7 @@ impl BaseNodeGrpcServer {} #[tonic::async_trait] impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { type FetchMatchingUtxosStream = mpsc::Receiver>; + type GetActiveValidatorNodesStream = mpsc::Receiver>; type GetBlocksStream = mpsc::Receiver>; type GetMempoolTransactionsStream = mpsc::Receiver>; type GetNetworkDifficultyStream = mpsc::Receiver>; @@ -1576,6 +1577,101 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { Ok(Response::new(response)) } + + async fn get_committee( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let report_error_flag = self.report_error_flag(); + debug!(target: LOG_TARGET, "Incoming GRPC request for GetCommittee"); + let mut handler = self.node_service.clone(); + let response = handler + .get_committee(request.height, request.shard_key.try_into().unwrap()) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error {}", e); + report_error(report_error_flag, Status::internal(e.to_string())) + })? + .iter() + .map(|a| a.shard_key.to_vec()) + .collect(); + Ok(Response::new(tari_rpc::GetCommitteeResponse { public_key: response })) + } + + async fn get_active_validator_nodes( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let report_error_flag = self.report_error_flag(); + debug!(target: LOG_TARGET, "Incoming GRPC request for GetActiveValidatorNodes"); + + let mut handler = self.node_service.clone(); + let (mut tx, rx) = mpsc::channel(1000); + + task::spawn(async move { + let active_validator_nodes = match handler.get_active_validator_nodes(request.height).await { + Err(err) => { + warn!(target: LOG_TARGET, "Error communicating with base node: {}", err,); + return; + }, + Ok(data) => data, + }; + for active_validator_node in active_validator_nodes { + let active_validator_node = match tari_rpc::ActiveValidatorNode::try_from(active_validator_node) { + Ok(t) => t, + Err(e) => { + warn!( + target: LOG_TARGET, + "Error sending converting active validator node for GRPC: {}", e + ); + match tx + .send(Err(report_error( + report_error_flag, + Status::internal("Error converting active validator node"), + ))) + .await + { + Ok(_) => (), + Err(send_err) => { + warn!(target: LOG_TARGET, "Error sending error to GRPC client: {}", send_err) + }, + } + return; + }, + }; + + match tx.send(Ok(active_validator_node)).await { + Ok(_) => (), + Err(err) => { + warn!( + target: LOG_TARGET, + "Error sending mempool transaction via GRPC: {}", err + ); + match tx + .send(Err(report_error( + report_error_flag, + Status::unknown("Error sending data"), + ))) + .await + { + Ok(_) => (), + Err(send_err) => { + warn!(target: LOG_TARGET, "Error sending error to GRPC client: {}", send_err) + }, + } + return; + }, + } + } + }); + debug!( + target: LOG_TARGET, + "Sending GetActiveValidatorNodes response stream to client" + ); + Ok(Response::new(rx)) + } } enum BlockGroupType { diff --git a/applications/tari_base_node/src/recovery.rs b/applications/tari_base_node/src/recovery.rs index eb9eea3b4b..fd94dbf6b4 100644 --- a/applications/tari_base_node/src/recovery.rs +++ b/applications/tari_base_node/src/recovery.rs @@ -74,22 +74,23 @@ pub fn initiate_recover_db(config: &BaseNodeConfig) -> Result<(), ExitError> { pub async fn run_recovery(node_config: &BaseNodeConfig) -> Result<(), anyhow::Error> { println!("Starting recovery mode"); + let rules = ConsensusManager::builder(node_config.network).build(); let (temp_db, main_db, temp_path) = match &node_config.db_type { DatabaseType::Lmdb => { - let backend = create_lmdb_database(&node_config.lmdb_path, node_config.lmdb.clone()).map_err(|e| { - error!(target: LOG_TARGET, "Error opening db: {}", e); - anyhow!("Could not open DB: {}", e) - })?; + let backend = create_lmdb_database(&node_config.lmdb_path, node_config.lmdb.clone(), rules.clone()) + .map_err(|e| { + error!(target: LOG_TARGET, "Error opening db: {}", e); + anyhow!("Could not open DB: {}", e) + })?; let temp_path = temp_dir().join("temp_recovery"); - let temp = create_lmdb_database(&temp_path, node_config.lmdb.clone()).map_err(|e| { + let temp = create_lmdb_database(&temp_path, node_config.lmdb.clone(), rules.clone()).map_err(|e| { error!(target: LOG_TARGET, "Error opening recovery db: {}", e); anyhow!("Could not open recovery DB: {}", e) })?; (temp, backend, temp_path) }, }; - let rules = ConsensusManager::builder(node_config.network).build(); let factories = CryptoFactories::default(); let randomx_factory = RandomXFactory::new(node_config.max_randomx_vms); let validators = Validators::new( diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 4e686bb468..484de6dcec 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -41,7 +41,7 @@ use tari_app_grpc::authentication::salted_password::create_salted_hashed_passwor use tari_common_types::{ emoji::EmojiId, transaction::TxId, - types::{CommitmentFactory, FixedHash, PublicKey}, + types::{CommitmentFactory, FixedHash, PublicKey, Signature}, }; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, @@ -53,6 +53,7 @@ use tari_core::transactions::{ tari_amount::{uT, MicroTari, Tari}, transaction_components::{OutputFeatures, TransactionOutput, UnblindedOutput}, }; +use tari_crypto::ristretto::RistrettoSecretKey; use tari_utilities::{hex::Hex, ByteArray}; use tari_wallet::{ connectivity_service::WalletConnectivityInterface, @@ -176,6 +177,24 @@ pub async fn claim_htlc_refund( Ok(tx_id) } +pub async fn register_validator_node( + mut wallet_transaction_service: TransactionServiceHandle, + validator_node_public_key: PublicKey, + validator_node_signature: Signature, + fee_per_gram: MicroTari, + message: String, +) -> Result { + wallet_transaction_service + .register_validator_node( + validator_node_public_key, + validator_node_signature, + fee_per_gram, + message, + ) + .await + .map_err(CommandError::TransactionServiceError) +} + /// Send a one-sided transaction to a recipient pub async fn send_one_sided( mut wallet_transaction_service: TransactionServiceHandle, @@ -795,6 +814,21 @@ pub async fn command_runner( ); } }, + RegisterValidatorNode(args) => { + let tx_id = register_validator_node( + transaction_service.clone(), + args.validator_node_public_key.into(), + Signature::new( + args.validator_node_public_nonce.into(), + RistrettoSecretKey::from_vec(&args.validator_node_signature).unwrap(), + ), + config.fee_per_gram * uT, + args.message, + ) + .await?; + debug!(target: LOG_TARGET, "Registering VN tx_id {}", tx_id); + tx_ids.push(tx_id); + }, } } diff --git a/applications/tari_console_wallet/src/cli.rs b/applications/tari_console_wallet/src/cli.rs index 3bb56faa94..e76574267f 100644 --- a/applications/tari_console_wallet/src/cli.rs +++ b/applications/tari_console_wallet/src/cli.rs @@ -132,6 +132,7 @@ pub enum CliCommands { ClaimShaAtomicSwapRefund(ClaimShaAtomicSwapRefundArgs), RevalidateWalletDb, HashGrpcPassword(HashPasswordArgs), + RegisterValidatorNode(RegisterValidatorNodeArgs), } #[derive(Debug, Args, Clone)] @@ -260,3 +261,12 @@ pub struct HashPasswordArgs { /// If true, only output the hashed password and the salted password. Otherwise a usage explanation is output. pub short: bool, } + +#[derive(Debug, Args, Clone)] +pub struct RegisterValidatorNodeArgs { + pub validator_node_public_key: UniPublicKey, + pub validator_node_public_nonce: UniPublicKey, + pub validator_node_signature: Vec, + #[clap(short, long, default_value = "Registering VN")] + pub message: String, +} diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index d7acb6a44b..aeece46823 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -67,6 +67,8 @@ use tari_app_grpc::{ GetVersionResponse, ImportUtxosRequest, ImportUtxosResponse, + RegisterValidatorNodeRequest, + RegisterValidatorNodeResponse, RevalidateRequest, RevalidateResponse, SeedWordsResponse, @@ -967,6 +969,46 @@ impl wallet_server::Wallet for WalletGrpcServer { Ok(Response::new(CreateTemplateRegistrationResponse {})) } + + async fn register_validator_node( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let mut transaction_service = self.get_transaction_service(); + let validator_node_public_key = CommsPublicKey::from_hex(&request.validator_node_public_key) + .map_err(|_| Status::internal("Destination address is malformed".to_string()))?; + let validator_node_signature = request + .validator_node_signature + .ok_or_else(|| Status::invalid_argument("Validator node signature is missing!"))? + .try_into() + .unwrap(); + + let response = match transaction_service + .register_validator_node( + validator_node_public_key, + validator_node_signature, + request.fee_per_gram.into(), + request.message, + ) + .await + { + Ok(tx) => RegisterValidatorNodeResponse { + transaction_id: tx.as_u64(), + is_success: true, + failure_message: Default::default(), + }, + Err(e) => { + error!(target: LOG_TARGET, "Transaction service error: {}", e); + RegisterValidatorNodeResponse { + transaction_id: Default::default(), + is_success: false, + failure_message: e.to_string(), + } + }, + }; + Ok(Response::new(response)) + } } async fn handle_completed_tx( diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 6d287138b8..2e307cb117 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -463,6 +463,7 @@ mod test { CliCommands::ClaimShaAtomicSwapRefund(_) => {}, CliCommands::RevalidateWalletDb => {}, CliCommands::HashGrpcPassword(_) => {}, + CliCommands::RegisterValidatorNode(_) => {}, } } assert!(get_balance && send_tari && make_it_rain && coin_split && discover_peer && whois); diff --git a/base_layer/core/src/base_node/comms_interface/comms_request.rs b/base_layer/core/src/base_node/comms_interface/comms_request.rs index 60d35ff753..9c77b2bf1f 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_request.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_request.rs @@ -56,6 +56,8 @@ pub enum NodeCommsRequest { GetNewBlock(NewBlockTemplate), FetchKernelByExcessSig(Signature), FetchMempoolTransactionsByExcessSigs { excess_sigs: Vec }, + FetchValidatorNodesKeys { height: u64 }, + FetchCommittee { height: u64, shard: [u8; 32] }, } #[derive(Debug, Serialize, Deserialize)] @@ -94,6 +96,12 @@ impl Display for NodeCommsRequest { FetchMempoolTransactionsByExcessSigs { .. } => { write!(f, "FetchMempoolTransactionsByExcessSigs") }, + FetchValidatorNodesKeys { height } => { + write!(f, "FetchValidatorNodesKeys ({})", height) + }, + FetchCommittee { height, shard } => { + write!(f, "FetchCommittee height ({}), shard({:?})", height, shard) + }, } } } diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index 81c3173160..9dde44f55c 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -32,7 +32,7 @@ use tari_common_types::{ use crate::{ blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate}, - chain_storage::UtxoMinedInfo, + chain_storage::{ActiveValidatorNode, UtxoMinedInfo}, proof_of_work::Difficulty, transactions::transaction_components::{Transaction, TransactionKernel, TransactionOutput}, }; @@ -71,6 +71,8 @@ pub enum NodeCommsResponse { FetchOutputsByContractIdResponse { outputs: Vec, }, + FetchValidatorNodesKeysResponse(Vec), + FetchCommitteeResponse(Vec), } impl Display for NodeCommsResponse { @@ -109,6 +111,8 @@ impl Display for NodeCommsResponse { ), FetchOutputsForBlockResponse { .. } => write!(f, "FetchConstitutionsResponse"), FetchOutputsByContractIdResponse { .. } => write!(f, "FetchOutputsByContractIdResponse"), + FetchValidatorNodesKeysResponse(_) => write!(f, "FetchValidatorNodesKeysResponse"), + FetchCommitteeResponse(_) => write!(f, "FetchCommitteeResponse"), } } } diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index 1067179be8..c5419cc62e 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -274,8 +274,8 @@ where B: BlockchainBackend + 'static }, NodeCommsRequest::GetNewBlockTemplate(request) => { let best_block_header = self.blockchain_db.fetch_tip_header().await?; - - let mut header = BlockHeader::from_previous(best_block_header.header()); + let vns = self.blockchain_db.get_validator_nodes_mr().await?; + let mut header = BlockHeader::from_previous(best_block_header.header(), vns); let constants = self.consensus_manager.consensus_constants(header.height); header.version = constants.blockchain_version(); header.pow.pow_algo = request.algo; @@ -363,6 +363,16 @@ where B: BlockchainBackend + 'static }, )) }, + NodeCommsRequest::FetchValidatorNodesKeys { height } => { + let active_validator_nodes = self.blockchain_db.fetch_active_validator_nodes(height).await?; + Ok(NodeCommsResponse::FetchValidatorNodesKeysResponse( + active_validator_nodes, + )) + }, + NodeCommsRequest::FetchCommittee { height, shard } => { + let validator_nodes = self.blockchain_db.fetch_committee(height, shard).await?; + Ok(NodeCommsResponse::FetchCommitteeResponse(validator_nodes)) + }, } } diff --git a/base_layer/core/src/base_node/comms_interface/local_interface.rs b/base_layer/core/src/base_node/comms_interface/local_interface.rs index ee3789a663..b51373f218 100644 --- a/base_layer/core/src/base_node/comms_interface/local_interface.rs +++ b/base_layer/core/src/base_node/comms_interface/local_interface.rs @@ -38,6 +38,7 @@ use crate::{ NodeCommsResponse, }, blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate}, + chain_storage::ActiveValidatorNode, proof_of_work::PowAlgorithm, transactions::transaction_components::{TransactionKernel, TransactionOutput}, }; @@ -271,4 +272,33 @@ impl LocalNodeCommsInterface { _ => Err(CommsInterfaceError::UnexpectedApiResponse), } } + + pub async fn get_active_validator_nodes( + &mut self, + height: u64, + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::FetchValidatorNodesKeys { height }) + .await?? + { + NodeCommsResponse::FetchValidatorNodesKeysResponse(validator_node) => Ok(validator_node), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } + + pub async fn get_committee( + &mut self, + height: u64, + shard: [u8; 32], + ) -> Result, CommsInterfaceError> { + match self + .request_sender + .call(NodeCommsRequest::FetchCommittee { height, shard }) + .await?? + { + NodeCommsResponse::FetchCommitteeResponse(validator_node) => Ok(validator_node), + _ => Err(CommsInterfaceError::UnexpectedApiResponse), + } + } } diff --git a/base_layer/core/src/base_node/sync/header_sync/error.rs b/base_layer/core/src/base_node/sync/header_sync/error.rs index c744c5e4c5..accd093510 100644 --- a/base_layer/core/src/base_node/sync/header_sync/error.rs +++ b/base_layer/core/src/base_node/sync/header_sync/error.rs @@ -92,4 +92,12 @@ pub enum BlockHeaderSyncError { }, #[error("All sync peers exceeded max allowed latency")] AllSyncPeersExceedLatency, + #[error( + "Validator node MMR at height {height} is not correct. Expected {actual} to equal the computed {computed}" + )] + ValidatorNodeMmmr { + height: u64, + actual: String, + computed: String, + }, } diff --git a/base_layer/core/src/base_node/sync/header_sync/validator.rs b/base_layer/core/src/base_node/sync/header_sync/validator.rs index ce2d5e0c66..3bff458033 100644 --- a/base_layer/core/src/base_node/sync/header_sync/validator.rs +++ b/base_layer/core/src/base_node/sync/header_sync/validator.rs @@ -261,7 +261,7 @@ mod test { let (validator, db) = setup(); let mut tip = db.fetch_tip_header().await.unwrap(); for _ in 0..n { - let mut header = BlockHeader::from_previous(tip.header()); + let mut header = BlockHeader::from_previous(tip.header(), tip.header().validator_node_merkle_root.clone()); // Needed to have unique keys for the blockchain db mmr count indexes (MDB_KEY_EXIST error) header.kernel_mmr_size += 1; header.output_mmr_size += 1; @@ -316,11 +316,11 @@ mod test { let (mut validator, _, tip) = setup_with_headers(1).await; validator.initialize_state(tip.hash()).await.unwrap(); assert!(validator.valid_headers().is_empty()); - let next = BlockHeader::from_previous(tip.header()); + let next = BlockHeader::from_previous(tip.header(), tip.header().validator_node_merkle_root.clone()); validator.validate(next).unwrap(); assert_eq!(validator.valid_headers().len(), 1); let tip = validator.valid_headers().last().cloned().unwrap(); - let next = BlockHeader::from_previous(tip.header()); + let next = BlockHeader::from_previous(tip.header(), tip.header().validator_node_merkle_root.clone()); validator.validate(next).unwrap(); assert_eq!(validator.valid_headers().len(), 2); } @@ -329,7 +329,7 @@ mod test { async fn it_fails_if_height_is_not_serial() { let (mut validator, _, tip) = setup_with_headers(2).await; validator.initialize_state(tip.hash()).await.unwrap(); - let mut next = BlockHeader::from_previous(tip.header()); + let mut next = BlockHeader::from_previous(tip.header(), tip.header().validator_node_merkle_root.clone()); next.height = 10; let err = validator.validate(next).unwrap_err(); unpack_enum!(BlockHeaderSyncError::InvalidBlockHeight { expected, actual } = err); diff --git a/base_layer/core/src/blocks/block_header.rs b/base_layer/core/src/blocks/block_header.rs index 74fcf2393a..186ac3cb96 100644 --- a/base_layer/core/src/blocks/block_header.rs +++ b/base_layer/core/src/blocks/block_header.rs @@ -57,6 +57,7 @@ use crate::{ blocks::BlocksHashDomain, consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, DomainSeparatedConsensusHasher}, proof_of_work::{PowAlgorithm, PowError, ProofOfWork}, + ValidatorNodeMmr, }; #[derive(Debug, Error)] @@ -110,11 +111,14 @@ pub struct BlockHeader { pub nonce: u64, /// Proof of work summary pub pow: ProofOfWork, + // Merkle root of all active validator node. + pub validator_node_merkle_root: Vec, } impl BlockHeader { /// Create a new, default header with the given version. pub fn new(blockchain_version: u16) -> BlockHeader { + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); BlockHeader { version: blockchain_version, height: 0, @@ -130,6 +134,7 @@ impl BlockHeader { total_script_offset: BlindingFactor::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), } } @@ -145,7 +150,7 @@ impl BlockHeader { /// Create a new block header using relevant data from the previous block. The height is incremented by one, the /// previous block hash is set, the timestamp is set to the current time, and the kernel/output mmr sizes are set to /// the previous block. All other fields, including proof of work are set to defaults. - pub fn from_previous(prev: &BlockHeader) -> BlockHeader { + pub fn from_previous(prev: &BlockHeader, validator_node_merkle_root: Vec) -> BlockHeader { let prev_hash = prev.hash(); BlockHeader { version: prev.version, @@ -162,6 +167,7 @@ impl BlockHeader { total_script_offset: BlindingFactor::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root, } } @@ -263,6 +269,7 @@ impl From for BlockHeader { total_script_offset: header_template.total_script_offset, nonce: 0, pow: header_template.pow, + validator_node_merkle_root: header_template.validator_node_merkle_root, } } } @@ -362,7 +369,7 @@ mod test { h1.nonce = 7600; assert_eq!(h1.height, 0, "Default block height"); let hash1 = h1.hash(); - let h2 = BlockHeader::from_previous(&h1); + let h2 = BlockHeader::from_previous(&h1, h1.validator_node_merkle_root.clone()); assert_eq!(h2.height, h1.height + 1, "Incrementing block height"); assert!(h2.timestamp > h1.timestamp, "Timestamp"); assert_eq!(h2.prev_hash, hash1, "Previous hash"); diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 22eb542971..dad7848d8b 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -47,6 +47,7 @@ use crate::{ TransactionOutputVersion, }, }, + ValidatorNodeMmr, }; /// Returns the genesis block for the selected network. @@ -161,6 +162,7 @@ fn get_igor_genesis_block_raw() -> Block { let genesis = DateTime::parse_from_rfc2822("08 Aug 2022 10:00:00 +0200").unwrap(); #[allow(clippy::cast_sign_loss)] let timestamp = genesis.timestamp() as u64; + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); Block { header: BlockHeader { version: 0, @@ -187,6 +189,7 @@ fn get_igor_genesis_block_raw() -> Block { pow_algo: PowAlgorithm::Sha3, pow_data: vec![], }, + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }, body, } @@ -281,6 +284,8 @@ fn get_esmeralda_genesis_block_raw() -> Block { maturity: 6, metadata: Vec::new(), sidechain_features: None, + validator_node_public_key: None, + validator_node_signature: None, }, Commitment::from_hex("2afed894ae877b5e9c7450cc0e29de46aeb6b118cd3d6b0a77da8c8156a1e234").unwrap(), BulletRangeProof::from_hex("0136b44930772f85b17139dd8e83789f84ccc2134cf6b2416d908fb8403efa4d3bc0247ec4afbbb1f7f7498d129226f26199eec988bd3e5ccce2572fd7aee16f2c4a2d710fac0e3bc1d612d700af2265e230ae1c45e3b0e4d3aab43cb87534217b56dcdb6598ed859d0cd6d70fae5acaaa38db5bbae6df8339e5e3dd594388bd53cef6f2acda4ac002d8ac6e01d430bdcf8565b8b8823ff3fb7dc8b359e687dd6feab0edf86c7444c713f34d2513145049b9664aae2e3dbc8a3365baae9d26842852ec9f401112a9742560ec220e61b05f65448d75b714839a6bafc723e9a04f25c69c036775fc55b7ec2bb28ef1de25a32cac51c288ed6d43f3819b1c3356d7699ea5f10217d553e90e6c93641649bd289dedb9e5725579539df07301f15093496c8fca3ec66a43332d1be3a3f94b530e1b8ca7feaa24c4ca73e60397a786ab742ac8933ba6bd504ef3c1a53fa1ff4397aba7c42a526507f930fdf9ff00a2a07b521841574d4e2b5beece946a15fa2545c8e556e704eed0ed10c0e3cbb9f5d6147e6e2d260666c79fa04d89c8901eeb3d3793239a68218a2c105f1bcb4211631eea037102bd5c840de751d84f473bb5cf6c41b3b97ec1c978700ec3c132e09a28d0a92c7e141e9968d0d2852c339a85c052356049f6752cb57c3d2b8c03db24525aa1f7db4a4f4d7d48639e27faa8c8bc695ad6c4f7688d43feedabef4d05c20b349ebc1697b3b899038b22fa308546efff290902cdacbe9992450cc31b61fc00652cffe4335c080d8398b061add986626068e17d5982ee9f6f28b4f4579d0406").unwrap(), @@ -311,6 +316,7 @@ fn get_esmeralda_genesis_block_raw() -> Block { let genesis = DateTime::parse_from_rfc2822("24 Aug 2022 22:00:00 +0200").unwrap(); #[allow(clippy::cast_sign_loss)] let timestamp = genesis.timestamp() as u64; + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); Block { header: BlockHeader { version: 0, @@ -337,6 +343,7 @@ fn get_esmeralda_genesis_block_raw() -> Block { pow_algo: PowAlgorithm::Sha3, pow_data: vec![], }, + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }, body, } diff --git a/base_layer/core/src/blocks/new_blockheader_template.rs b/base_layer/core/src/blocks/new_blockheader_template.rs index 5864adbadb..4b2eeebec7 100644 --- a/base_layer/core/src/blocks/new_blockheader_template.rs +++ b/base_layer/core/src/blocks/new_blockheader_template.rs @@ -45,6 +45,8 @@ pub struct NewBlockHeaderTemplate { pub total_script_offset: BlindingFactor, /// Proof of work summary pub pow: ProofOfWork, + // Merkle root of all active validator node. + pub validator_node_merkle_root: Vec, } impl NewBlockHeaderTemplate { @@ -56,6 +58,7 @@ impl NewBlockHeaderTemplate { total_kernel_offset: header.total_kernel_offset, total_script_offset: header.total_script_offset, pow: header.pow, + validator_node_merkle_root: header.validator_node_merkle_root, } } } diff --git a/base_layer/core/src/chain_storage/active_validator_node.rs b/base_layer/core/src/chain_storage/active_validator_node.rs new file mode 100644 index 0000000000..fa6a4a7dfe --- /dev/null +++ b/base_layer/core/src/chain_storage/active_validator_node.rs @@ -0,0 +1,32 @@ +// 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 serde::{Deserialize, Serialize}; +use tari_common_types::types::PublicKey; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ActiveValidatorNode { + pub shard_key: [u8; 32], + pub from_height: u64, + pub to_height: u64, + pub public_key: PublicKey, +} diff --git a/base_layer/core/src/chain_storage/async_db.rs b/base_layer/core/src/chain_storage/async_db.rs index 031e6405b1..42940705f5 100644 --- a/base_layer/core/src/chain_storage/async_db.rs +++ b/base_layer/core/src/chain_storage/async_db.rs @@ -30,6 +30,7 @@ use tari_common_types::{ }; use tari_utilities::epoch_time::EpochTime; +use super::ActiveValidatorNode; use crate::{ blocks::{ Block, @@ -264,6 +265,12 @@ impl AsyncBlockchainDb { make_async_fn!(get_stats() -> DbBasicStats, "get_stats"); make_async_fn!(fetch_total_size_stats() -> DbTotalSizeStats, "fetch_total_size_stats"); + + make_async_fn!(fetch_active_validator_nodes(height: u64) -> Vec, "fetch_active_validator_nodes"); + + make_async_fn!(fetch_committee(height: u64, shard: [u8;32]) -> Vec, "fetch_committee"); + + make_async_fn!(get_validator_nodes_mr() -> Vec, "get_validator_nodes_mr"); } impl From> for AsyncBlockchainDb { diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index 25291c6765..08478a13a3 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -7,6 +7,7 @@ use tari_common_types::{ types::{Commitment, HashOutput, Signature}, }; +use super::ActiveValidatorNode; use crate::{ blocks::{ Block, @@ -191,4 +192,7 @@ pub trait BlockchainBackend: Send + Sync { /// Fetches all tracked reorgs fn fetch_all_reorgs(&self) -> Result, ChainStorageError>; + + fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError>; + fn fetch_committee(&self, height: u64, shard: [u8; 32]) -> Result, ChainStorageError>; } diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index caeb10a176..b32a21976b 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -41,6 +41,7 @@ use tari_common_types::{ use tari_mmr::pruned_hashset::PrunedHashSet; use tari_utilities::{epoch_time::EpochTime, hex::Hex, ByteArray}; +use super::ActiveValidatorNode; use crate::{ blocks::{ Block, @@ -91,6 +92,7 @@ use crate::{ PrunedInputMmr, PrunedKernelMmr, PrunedWitnessMmr, + ValidatorNodeMmr, }; const LOG_TARGET: &str = "c::cs::database"; @@ -838,6 +840,14 @@ where B: BlockchainBackend db.fetch_mmr_size(tree) } + pub fn get_validator_nodes_mr(&self) -> Result, ChainStorageError> { + let tip = self.get_height()?; + let validator_nodes = self.fetch_active_validator_nodes(tip + 1)?; + // Note: MMR is not balanced + let mmr = ValidatorNodeMmr::new(validator_nodes.iter().map(|vn| vn.shard_key.to_vec()).collect()); + Ok(mmr.get_merkle_root().unwrap()) + } + /// Tries to add a block to the longest chain. /// /// The block is added to the longest chain if and only if @@ -1154,6 +1164,16 @@ where B: BlockchainBackend txn.clear_all_reorgs(); db.write(txn) } + + pub fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.fetch_active_validator_nodes(height) + } + + pub fn fetch_committee(&self, height: u64, shard: [u8; 32]) -> Result, ChainStorageError> { + let db = self.db_read_access()?; + db.fetch_committee(height, shard) + } } fn unexpected_result(request: DbKey, response: DbValue) -> Result { diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index e1fcbbcb6a..105f217f29 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -27,9 +27,10 @@ use std::{ }; use croaring::Bitmap; -use tari_common_types::types::{BlockHash, Commitment, HashOutput}; +use tari_common_types::types::{BlockHash, Commitment, HashOutput, PublicKey}; use tari_utilities::hex::Hex; +use super::ActiveValidatorNode; use crate::{ blocks::{Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock, ChainHeader, UpdateBlockAccumulatedData}, chain_storage::{error::ChainStorageError, HorizonData, Reorg}, @@ -358,6 +359,12 @@ pub enum WriteOperation { reorg: Reorg, }, ClearAllReorgs, + InsertValidatorNode { + validator_node: ActiveValidatorNode, + }, + DeleteValidatorNode { + public_key: PublicKey, + }, } impl fmt::Display for WriteOperation { @@ -454,6 +461,10 @@ 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) + }, + DeleteValidatorNode { public_key } => write!(f, "Delete VN key {}", public_key), } } } 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 8e042220bf..6cf12d69bd 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 @@ -34,7 +34,7 @@ use log::*; use serde::{Deserialize, Serialize}; use tari_common_types::{ chain_metadata::ChainMetadata, - types::{BlockHash, Commitment, HashOutput, Signature}, + types::{BlockHash, Commitment, HashOutput, PublicKey, Signature}, }; use tari_storage::lmdb_store::{db, LMDBBuilder, LMDBConfig, LMDBStore}; use tari_utilities::{ @@ -42,6 +42,7 @@ use tari_utilities::{ ByteArray, }; +use super::{key_prefix_cursor::KeyPrefixCursor, lmdb::lmdb_get_prefix_cursor}; use crate::{ blocks::{ Block, @@ -83,6 +84,7 @@ use crate::{ }, stats::DbTotalSizeStats, utxo_mined_info::UtxoMinedInfo, + ActiveValidatorNode, BlockchainBackend, DbBasicStats, DbSize, @@ -91,9 +93,17 @@ use crate::{ PrunedOutput, Reorg, }, + consensus::{ConsensusManager, DomainSeparatedConsensusHasher}, transactions::{ aggregated_body::AggregateBody, - transaction_components::{TransactionError, TransactionInput, TransactionKernel, TransactionOutput}, + transaction_components::{ + SpentOutput, + TransactionError, + TransactionInput, + TransactionKernel, + TransactionOutput, + }, + TransactionHashDomain, }, MutablePrunedOutputMmr, PrunedKernelMmr, @@ -128,8 +138,14 @@ const LMDB_DB_ORPHAN_CHAIN_TIPS: &str = "orphan_chain_tips"; const LMDB_DB_ORPHAN_PARENT_MAP_INDEX: &str = "orphan_parent_map_index"; const LMDB_DB_BAD_BLOCK_LIST: &str = "bad_blocks"; const LMDB_DB_REORGS: &str = "reorgs"; - -pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Result { +const LMDB_DB_VALIDATOR_NODES: &str = "validator_nodes"; +const LMDB_DB_VALIDATOR_NODES_MAPPING: &str = "validator_nodes_mapping"; + +pub fn create_lmdb_database>( + path: P, + config: LMDBConfig, + consensus_manager: ConsensusManager, +) -> Result { let flags = db::CREATE; debug!(target: LOG_TARGET, "Creating LMDB database at {:?}", path.as_ref()); std::fs::create_dir_all(&path)?; @@ -166,10 +182,12 @@ pub fn create_lmdb_database>(path: P, config: LMDBConfig) -> Resu .add_database(LMDB_DB_ORPHAN_PARENT_MAP_INDEX, flags | db::DUPSORT) .add_database(LMDB_DB_BAD_BLOCK_LIST, flags) .add_database(LMDB_DB_REORGS, flags | db::INTEGERKEY) + .add_database(LMDB_DB_VALIDATOR_NODES, flags) + .add_database(LMDB_DB_VALIDATOR_NODES_MAPPING, flags | db::DUPSORT) .build() .map_err(|err| ChainStorageError::CriticalError(format!("Could not create LMDB store:{}", err)))?; debug!(target: LOG_TARGET, "LMDB database creation successful"); - LMDBDatabase::new(&lmdb_store, file_lock) + LMDBDatabase::new(&lmdb_store, file_lock, consensus_manager) } /// This is a lmdb-based blockchain database for persistent storage of the chain state. @@ -224,11 +242,20 @@ pub struct LMDBDatabase { bad_blocks: DatabaseRef, /// Stores reorgs by epochtime and Reorg reorgs: DatabaseRef, + /// Maps VN Public Key -> ActiveValidatorNode + validator_nodes: DatabaseRef, + /// Maps VN Shard Key -> VN Public Key + validator_nodes_mapping: DatabaseRef, _file_lock: Arc, + consensus_manager: ConsensusManager, } impl LMDBDatabase { - pub fn new(store: &LMDBStore, file_lock: File) -> Result { + pub fn new( + store: &LMDBStore, + file_lock: File, + consensus_manager: ConsensusManager, + ) -> Result { let env = store.env(); let db = Self { @@ -259,9 +286,12 @@ impl LMDBDatabase { orphan_parent_map_index: get_database(store, LMDB_DB_ORPHAN_PARENT_MAP_INDEX)?, bad_blocks: get_database(store, LMDB_DB_BAD_BLOCK_LIST)?, reorgs: get_database(store, LMDB_DB_REORGS)?, + validator_nodes: get_database(store, LMDB_DB_VALIDATOR_NODES)?, + validator_nodes_mapping: get_database(store, LMDB_DB_VALIDATOR_NODES_MAPPING)?, env, env_config: store.env_config(), _file_lock: Arc::new(file_lock), + consensus_manager, }; Ok(db) @@ -460,6 +490,14 @@ impl LMDBDatabase { ClearAllReorgs => { lmdb_clear(&write_txn, &self.reorgs)?; }, + InsertValidatorNode { validator_node } => { + self.insert_validator_node(&write_txn, validator_node)?; + }, + DeleteValidatorNode { public_key } => { + let txn = self.read_transaction()?; + let shard_key = self.get_vn_mapping(&txn, public_key)?; + self.delete_validator_node(&write_txn, public_key, &shard_key)?; + }, } } write_txn.commit()?; @@ -467,7 +505,7 @@ impl LMDBDatabase { Ok(()) } - fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 24] { + fn all_dbs(&self) -> [(&'static str, &DatabaseRef); 26] { [ ("metadata_db", &self.metadata_db), ("headers_db", &self.headers_db), @@ -499,6 +537,8 @@ impl LMDBDatabase { ("orphan_parent_map_index", &self.orphan_parent_map_index), ("bad_blocks", &self.bad_blocks), ("reorgs", &self.reorgs), + ("validator_nodes", &self.validator_nodes), + ("validator_nodes_mapping", &self.validator_nodes_mapping), ] } @@ -1228,6 +1268,16 @@ impl LMDBDatabase { None => return Err(ChainStorageError::UnspendableInput), }, }; + if let SpentOutput::OutputData { + version: _, features, .. + } = &input.spent_output + { + if let Some(validator_node_public_key) = &features.validator_node_public_key { + let read_txn = self.read_transaction()?; + let shard_key = self.get_vn_mapping(&read_txn, validator_node_public_key)?; + self.delete_validator_node(txn, validator_node_public_key, &shard_key)?; + } + } if !output_mmr.delete(index) { return Err(ChainStorageError::InvalidOperation(format!( "Could not delete index {} from the output MMR", @@ -1246,6 +1296,24 @@ impl LMDBDatabase { mmr_count )) })?; + if let Some(validator_node_public_key) = &output.features.validator_node_public_key { + let shard_key = DomainSeparatedConsensusHasher::::new("validator_node_root") + .chain(&validator_node_public_key.as_bytes()) + .chain(&block_hash) + .finalize(); + + 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) + .get_validator_node_timeout(), + public_key: validator_node_public_key.clone(), + }; + self.insert_validator_node(txn, &validator_node)?; + } self.insert_output( txn, &block_hash, @@ -1487,6 +1555,42 @@ impl LMDBDatabase { Ok(()) } + fn insert_validator_node( + &self, + txn: &WriteTransaction<'_>, + validator_node: &ActiveValidatorNode, + ) -> Result<(), ChainStorageError> { + lmdb_insert( + txn, + &self.validator_nodes, + &validator_node.public_key.to_vec(), + validator_node, + "validator_nodes", + )?; + lmdb_insert( + txn, + &self.validator_nodes_mapping, + &validator_node.shard_key, + &validator_node.public_key.to_vec(), + "validator_nodes_mapping", + ) + } + + fn get_vn_mapping(&self, txn: &ReadTransaction<'_>, public_key: &PublicKey) -> Result<[u8; 32], ChainStorageError> { + let x: ActiveValidatorNode = lmdb_get(txn, &self.validator_nodes, &public_key.to_vec())?.unwrap(); + Ok(x.shard_key) + } + + fn delete_validator_node( + &self, + txn: &WriteTransaction<'_>, + public_key: &PublicKey, + shard_key: &[u8; 32], + ) -> Result<(), ChainStorageError> { + lmdb_delete(txn, &self.validator_nodes, &public_key.to_vec(), "validator_nodes")?; + lmdb_delete(txn, &self.validator_nodes, shard_key, "validator_nodes_mapping") + } + fn fetch_output_in_txn( &self, txn: &ConstTransaction<'_>, @@ -2293,6 +2397,80 @@ impl BlockchainBackend for LMDBDatabase { let txn = self.read_transaction()?; lmdb_filter_map_values(&txn, &self.reorgs, Some) } + + fn fetch_active_validator_nodes(&self, height: u64) -> Result, ChainStorageError> { + let txn = self.read_transaction()?; + lmdb_filter_map_values(&txn, &self.validator_nodes, |vn: ActiveValidatorNode| { + if vn.from_height <= height && vn.to_height >= height { + Some(vn) + } else { + None + } + }) + } + + fn fetch_committee(&self, height: u64, shard: [u8; 32]) -> Result, ChainStorageError> { + // TODO: I'm not sure how effective this is compared to getting all and selecting by yourself. Also if there is + // less validator nodes than committee size this gets weird. + let txn = self.read_transaction()?; + let mut cursor: KeyPrefixCursor = + lmdb_get_prefix_cursor(&txn, &self.validator_nodes, &shard)?; + let mut result = vec![]; + let committee_half_size = 5u64; + let mut size = 0u64; + // Right side of the committee + while let Some((_, val)) = cursor.next()? { + if val.from_height <= height && height <= val.to_height { + result.push(val); + size += 1; + if size == committee_half_size { + break; + } + } + } + // Check if it wraps around + if size < committee_half_size { + let mut cursor: KeyPrefixCursor = + lmdb_get_prefix_cursor(&txn, &self.validator_nodes, &[0; 32])?; + while let Some((_, val)) = cursor.next()? { + if val.from_height <= height && height <= val.to_height { + result.push(val); + size += 1; + if size == committee_half_size { + break; + } + } + } + } + let mut cursor: KeyPrefixCursor = + lmdb_get_prefix_cursor(&txn, &self.validator_nodes, &shard)?; + let mut size = 0u64; + // Left side of the committee + while let Some((_, val)) = cursor.prev()? { + if val.from_height <= height && height <= val.to_height { + result.push(val); + size += 1; + if size == committee_half_size { + break; + } + } + } + // Check if it wraps around + if size < committee_half_size { + let mut cursor: KeyPrefixCursor = + lmdb_get_prefix_cursor(&txn, &self.validator_nodes, &[255; 32])?; + while let Some((_, val)) = cursor.prev()? { + if val.from_height <= height && height <= val.to_height { + result.push(val); + size += 1; + if size == committee_half_size { + break; + } + } + } + } + Ok(result) + } } // Fetch the chain metadata diff --git a/base_layer/core/src/chain_storage/mod.rs b/base_layer/core/src/chain_storage/mod.rs index 8dfa32d3bc..d374dccf1b 100644 --- a/base_layer/core/src/chain_storage/mod.rs +++ b/base_layer/core/src/chain_storage/mod.rs @@ -79,3 +79,6 @@ mod target_difficulties; mod utxo_mined_info; pub use target_difficulties::TargetDifficulties; pub use utxo_mined_info::*; + +mod active_validator_node; +pub use active_validator_node::ActiveValidatorNode; diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index 69ec71043e..0846620edf 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -496,7 +496,8 @@ mod prepare_new_block { fn it_errors_for_non_tip_template() { let db = setup(); let genesis = db.fetch_block(0).unwrap(); - let next_block = BlockHeader::from_previous(genesis.header()); + let next_block = + BlockHeader::from_previous(genesis.header(), genesis.header().validator_node_merkle_root.clone()); let mut template = NewBlockTemplate::from_block(next_block.into_builder().build(), Difficulty::min(), 5000 * T); // This would cause a panic if the sanity checks were not there template.header.height = 100; @@ -511,7 +512,8 @@ mod prepare_new_block { fn it_prepares_the_first_block() { let db = setup(); let genesis = db.fetch_block(0).unwrap(); - let next_block = BlockHeader::from_previous(genesis.header()); + let next_block = + BlockHeader::from_previous(genesis.header(), genesis.header().validator_node_merkle_root.clone()); let template = NewBlockTemplate::from_block(next_block.into_builder().build(), Difficulty::min(), 5000 * T); let block = db.prepare_new_block(template).unwrap(); assert_eq!(block.header.height, 1); @@ -631,7 +633,10 @@ mod clear_all_pending_headers { let mut prev_header = prev_block.try_into_chain_block().unwrap().to_chain_header(); let headers = (0..5) .map(|_| { - let mut header = BlockHeader::from_previous(prev_header.header()); + let mut header = BlockHeader::from_previous( + prev_header.header(), + prev_header.header().validator_node_merkle_root.clone(), + ); header.kernel_mmr_size += 1; header.output_mmr_size += 1; let accum = BlockHeaderAccumulatedData::builder(&prev_accum) diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 4692e0a403..eed45fd849 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -94,6 +94,8 @@ pub struct ConsensusConstants { kernel_version_range: RangeInclusive, /// An allowlist of output types permitted_output_types: &'static [OutputType], + /// How long does it take to timeout validator node registration + validator_node_timeout: u64, } // todo: remove this once OutputFeaturesVersion is removed in favor of just TransactionOutputVersion @@ -286,6 +288,10 @@ impl ConsensusConstants { self.permitted_output_types } + pub fn get_validator_node_timeout(&self) -> u64 { + self.validator_node_timeout + } + pub fn localnet() -> Vec { let difficulty_block_window = 90; let mut algos = HashMap::new(); @@ -323,6 +329,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: OutputType::all(), + validator_node_timeout: 0, }] } @@ -363,6 +370,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }] } @@ -406,6 +414,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }] } @@ -456,6 +465,7 @@ impl ConsensusConstants { output_version_range: output_version_range.clone(), kernel_version_range: kernel_version_range.clone(), permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }, ConsensusConstants { effective_from_height: 23000, @@ -479,6 +489,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }, ] } @@ -527,6 +538,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 50, }] } @@ -568,6 +580,7 @@ impl ConsensusConstants { output_version_range, kernel_version_range, permitted_output_types: Self::current_permitted_output_types(), + validator_node_timeout: 0, }] } diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index da8a129755..0deca2dcef 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -108,6 +108,14 @@ mod domain_hashing { ); pub type InputMmrHasherBlake256 = DomainSeparatedHasher; pub type PrunedInputMmr = MerkleMountainRange; + + hash_domain!( + ValidatorNodeMmrHashDomain, + "com.tari.tari_project.base_layer.core.validator_node_mmr", + 1 + ); + pub type ValidatorNodeMmrHasherBlake256 = DomainSeparatedHasher; + pub type ValidatorNodeMmr = MerkleMountainRange>; } #[cfg(feature = "base_node")] pub use domain_hashing::*; diff --git a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs index da95343643..78a7c650ec 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs @@ -203,6 +203,7 @@ mod test { use crate::{ consensus::ConsensusEncoding, proof_of_work::{monero_rx::fixed_array::FixedByteArray, PowAlgorithm, ProofOfWork}, + ValidatorNodeMmr, }; // This tests checks the hash of monero-rs @@ -292,6 +293,7 @@ mod test { let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let mut block = deserialize::(&bytes[..]).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -307,6 +309,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -348,6 +351,7 @@ mod test { let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let mut block = deserialize::(&bytes[..]).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -363,6 +367,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -400,6 +405,7 @@ mod test { let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let block = deserialize::(&bytes[..]).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -415,6 +421,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let count = 1 + (u16::try_from(block.tx_hashes.len()).unwrap()); let mut hashes = Vec::with_capacity(count as usize); @@ -451,6 +458,7 @@ mod test { let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let mut block = deserialize::(&bytes[..]).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -466,6 +474,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let hash = Hash::null(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -506,6 +515,7 @@ mod test { let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let mut block = deserialize::(&bytes[..]).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -521,6 +531,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -557,6 +568,7 @@ mod test { #[test] fn test_verify_header_no_data() { + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -572,6 +584,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let monero_data = MoneroPowData { header: Default::default(), @@ -599,6 +612,7 @@ mod test { let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); let mut block = deserialize::(&bytes[..]).unwrap(); + let vn_mmr = ValidatorNodeMmr::new(Vec::new()); let mut block_header = BlockHeader { version: 0, height: 0, @@ -614,6 +628,7 @@ mod test { total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), + validator_node_merkle_root: vn_mmr.get_merkle_root().unwrap(), }; let hash = block_header.mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); diff --git a/base_layer/core/src/proto/block.proto b/base_layer/core/src/proto/block.proto index d42555b2f6..badfd84e6c 100644 --- a/base_layer/core/src/proto/block.proto +++ b/base_layer/core/src/proto/block.proto @@ -51,6 +51,8 @@ message BlockHeader { uint64 output_mmr_size = 14; // Sum of script offsets for all kernels in this block. bytes total_script_offset = 15; + // Merkle root of validator nodes + bytes validator_node_merkle_root = 16; } // A Tari block. Blocks are linked together into a blockchain. @@ -108,6 +110,8 @@ message NewBlockHeaderTemplate { ProofOfWork pow = 5; // Sum of script offsets for all kernels in this block. bytes total_script_offset = 6; + // Merkle root of validator nodes + bytes validator_node_merkle_root = 7; } // The new block template is used constructing a new partial block, allowing a miner to added the coinbase utxo and as a final step the Base node to add the MMR roots to the header. diff --git a/base_layer/core/src/proto/block.rs b/base_layer/core/src/proto/block.rs index 62c79d928e..e651673cc0 100644 --- a/base_layer/core/src/proto/block.rs +++ b/base_layer/core/src/proto/block.rs @@ -221,6 +221,7 @@ impl TryFrom for NewBlockHeaderTemplate { total_kernel_offset, total_script_offset, pow, + validator_node_merkle_root: header.validator_node_merkle_root, }) } } @@ -234,6 +235,7 @@ impl From for proto::NewBlockHeaderTemplate { total_kernel_offset: header.total_kernel_offset.to_vec(), total_script_offset: header.total_script_offset.to_vec(), pow: Some(proto::ProofOfWork::from(header.pow)), + validator_node_merkle_root: header.validator_node_merkle_root, } } } diff --git a/base_layer/core/src/proto/block_header.rs b/base_layer/core/src/proto/block_header.rs index 47fa2e20c2..e221608aad 100644 --- a/base_layer/core/src/proto/block_header.rs +++ b/base_layer/core/src/proto/block_header.rs @@ -68,6 +68,7 @@ impl TryFrom for BlockHeader { total_script_offset, nonce: header.nonce, pow, + validator_node_merkle_root: header.validator_node_merkle_root, }) } } @@ -90,6 +91,7 @@ impl From for proto::BlockHeader { pow: Some(proto::ProofOfWork::from(header.pow)), kernel_mmr_size: header.kernel_mmr_size, output_mmr_size: header.output_mmr_size, + validator_node_merkle_root: header.validator_node_merkle_root, } } } diff --git a/base_layer/core/src/proto/transaction.proto b/base_layer/core/src/proto/transaction.proto index 34d3f2b77e..77aefa0d8a 100644 --- a/base_layer/core/src/proto/transaction.proto +++ b/base_layer/core/src/proto/transaction.proto @@ -99,6 +99,8 @@ message OutputFeatures { uint64 maturity = 3; bytes metadata = 4; SideChainFeatures sidechain_features = 5; + bytes validator_node_public_key = 6; + Signature validator_node_signature = 7; } // The components of the block or transaction. The same struct can be used for either, since in Mimblewimble, diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index d199623bd9..123e15c49b 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -27,7 +27,7 @@ use std::{ sync::Arc, }; -use tari_common_types::types::{BlindingFactor, BulletRangeProof, Commitment, PublicKey}; +use tari_common_types::types::{BlindingFactor, BulletRangeProof, Commitment, PublicKey, Signature}; use tari_crypto::tari_utilities::{ByteArray, ByteArrayError}; use tari_script::{ExecutionStack, TariScript}; use tari_utilities::convert::try_convert_all; @@ -300,6 +300,9 @@ impl TryFrom for OutputFeatures { .map(SideChainFeatures::try_from) .transpose()?; + let validator_node_public_key = PublicKey::from_bytes(features.validator_node_public_key.as_bytes()).ok(); + let validator_node_signature = features.validator_node_signature.map(Signature::try_from).transpose()?; + let flags = features .flags .try_into() @@ -313,6 +316,8 @@ impl TryFrom for OutputFeatures { features.maturity, features.metadata, sidechain_features, + validator_node_public_key, + validator_node_signature, )) } } @@ -325,6 +330,11 @@ impl From for proto::types::OutputFeatures { metadata: features.metadata, version: features.version as u32, sidechain_features: features.sidechain_features.map(Into::into), + validator_node_public_key: features + .validator_node_public_key + .map(|pk| pk.as_bytes().to_vec()) + .unwrap_or_default(), + validator_node_signature: features.validator_node_signature.map(Into::into), } } } diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 9572ac9ac2..92579237d4 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -51,6 +51,7 @@ use crate::{ }, chain_storage::{ create_lmdb_database, + ActiveValidatorNode, BlockAddResult, BlockchainBackend, BlockchainDatabase, @@ -159,17 +160,19 @@ pub struct TempDatabase { impl TempDatabase { pub fn new() -> Self { let temp_path = create_temporary_data_path(); + let rules = create_consensus_rules(); Self { - db: Some(create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap()), + db: Some(create_lmdb_database(&temp_path, LMDBConfig::default(), rules).unwrap()), path: temp_path, delete_on_drop: true, } } pub fn from_path>(temp_path: P) -> Self { + let rules = create_consensus_rules(); Self { - db: Some(create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap()), + db: Some(create_lmdb_database(&temp_path, LMDBConfig::default(), rules).unwrap()), path: temp_path.as_ref().to_path_buf(), delete_on_drop: true, } @@ -410,6 +413,17 @@ impl BlockchainBackend for TempDatabase { fn fetch_all_reorgs(&self) -> Result, ChainStorageError> { self.db.as_ref().unwrap().fetch_all_reorgs() } + + fn fetch_active_validator_nodes( + &self, + height: u64, + ) -> Result, ChainStorageError> { + self.db.as_ref().unwrap().fetch_active_validator_nodes(height) + } + + fn fetch_committee(&self, height: u64, shard: [u8; 32]) -> Result, ChainStorageError> { + self.db.as_ref().unwrap().fetch_committee(height, shard) + } } pub fn create_chained_blocks>( diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index aac67e4318..f77890a6db 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -63,7 +63,8 @@ pub fn create_orphan_block(block_height: u64, transactions: Vec, co } pub fn create_block(rules: &ConsensusManager, prev_block: &Block, spec: BlockSpec) -> (Block, UnblindedOutput) { - let mut header = BlockHeader::from_previous(&prev_block.header); + let mut header = + BlockHeader::from_previous(&prev_block.header, prev_block.header.validator_node_merkle_root.clone()); let block_height = spec.height_override.unwrap_or(prev_block.header.height + 1); header.height = block_height; // header.prev_hash = prev_block.hash(); diff --git a/base_layer/core/src/transactions/transaction_components/error.rs b/base_layer/core/src/transactions/transaction_components/error.rs index 1ef013fb59..43e0913ce9 100644 --- a/base_layer/core/src/transactions/transaction_components/error.rs +++ b/base_layer/core/src/transactions/transaction_components/error.rs @@ -73,6 +73,8 @@ pub enum TransactionError { ConsensusEncodingError(String), #[error("Committee contains too many members: contains {len} members but maximum is {max}")] InvalidCommitteeLength { len: usize, max: usize }, + #[error("Missing validator node signature")] + MissingValidatorNodeSignature, } impl From for TransactionError { diff --git a/base_layer/core/src/transactions/transaction_components/kernel_features.rs b/base_layer/core/src/transactions/transaction_components/kernel_features.rs index f85efad411..68856f8b60 100644 --- a/base_layer/core/src/transactions/transaction_components/kernel_features.rs +++ b/base_layer/core/src/transactions/transaction_components/kernel_features.rs @@ -38,6 +38,8 @@ bitflags! { const COINBASE_KERNEL = 1u8; /// Burned output transaction const BURN_KERNEL = 2u8; + /// Validator node registration transaction + const VALIDATOR_NODE_REGISTRATION = 3u8; } } @@ -56,6 +58,10 @@ impl KernelFeatures { pub fn is_burned(&self) -> bool { self.contains(KernelFeatures::BURN_KERNEL) } + + pub fn create_validator_node_registration() -> KernelFeatures { + KernelFeatures::VALIDATOR_NODE_REGISTRATION + } } impl Default for KernelFeatures { diff --git a/base_layer/core/src/transactions/transaction_components/output_features.rs b/base_layer/core/src/transactions/transaction_components/output_features.rs index aaa8a48b2a..fd968263a9 100644 --- a/base_layer/core/src/transactions/transaction_components/output_features.rs +++ b/base_layer/core/src/transactions/transaction_components/output_features.rs @@ -29,6 +29,7 @@ use std::{ }; use serde::{Deserialize, Serialize}; +use tari_common_types::types::{PublicKey, Signature}; use super::OutputFeaturesVersion; use crate::{ @@ -47,6 +48,8 @@ pub struct OutputFeatures { pub maturity: u64, pub metadata: Vec, pub sidechain_features: Option, + pub validator_node_public_key: Option, + pub validator_node_signature: Option, } impl OutputFeatures { @@ -56,6 +59,8 @@ impl OutputFeatures { maturity: u64, metadata: Vec, sidechain_features: Option, + validator_node_public_key: Option, + validator_node_signature: Option, ) -> OutputFeatures { OutputFeatures { version, @@ -63,6 +68,8 @@ impl OutputFeatures { maturity, metadata, sidechain_features, + validator_node_public_key, + validator_node_signature, } } @@ -71,6 +78,8 @@ impl OutputFeatures { maturity: u64, metadata: Vec, sidechain_features: Option, + validator_node_public_key: Option, + validator_node_signature: Option, ) -> OutputFeatures { OutputFeatures::new( OutputFeaturesVersion::get_current_version(), @@ -78,6 +87,8 @@ impl OutputFeatures { maturity, metadata, sidechain_features, + validator_node_public_key, + validator_node_signature, ) } @@ -106,6 +117,17 @@ impl OutputFeatures { } } + pub fn create_validator_node_registration( + validator_node_public_key: PublicKey, + validator_node_signature: Signature, + ) -> OutputFeatures { + OutputFeatures { + validator_node_public_key: Some(validator_node_public_key), + validator_node_signature: Some(validator_node_signature), + ..Default::default() + } + } + pub fn is_coinbase(&self) -> bool { matches!(self.output_type, OutputType::Coinbase) } @@ -135,19 +157,23 @@ impl ConsensusDecoding for OutputFeatures { let sidechain_features = ConsensusDecoding::consensus_decode(reader)?; const MAX_METADATA_SIZE: usize = 1024; let metadata = as ConsensusDecoding>::consensus_decode(reader)?; + let validator_node_public_key = None; + let validator_node_signature = None; Ok(Self { version, output_type: flags, maturity, sidechain_features, metadata: metadata.into(), + validator_node_public_key, + validator_node_signature, }) } } impl Default for OutputFeatures { fn default() -> Self { - OutputFeatures::new_current_version(OutputType::default(), 0, vec![], None) + OutputFeatures::new_current_version(OutputType::default(), 0, vec![], None, None, None) } } @@ -212,6 +238,8 @@ mod test { .try_into() .unwrap(), })), + validator_node_public_key: None, + validator_node_signature: None, } } 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 d1f0004f63..bff71791b5 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -215,6 +215,22 @@ impl TransactionOutput { Ok(()) } + pub fn verify_validator_node_signature(&self) -> Result<(), TransactionError> { + if let Some(public_key) = &self.features.validator_node_public_key { + let signature = self + .features + .validator_node_signature + .clone() + .ok_or(TransactionError::MissingValidatorNodeSignature)?; + if !signature.verify_challenge(public_key, &[0]) { + return Err(TransactionError::InvalidSignatureError( + "Validator node signature is not valid!".to_string(), + )); + } + } + Ok(()) + } + /// Attempt to rewind the range proof to reveal the mask (blinding factor) pub fn recover_mask( &self, diff --git a/base_layer/core/src/validation/block_validators/async_validator.rs b/base_layer/core/src/validation/block_validators/async_validator.rs index ab9988f9a0..696b90ca8f 100644 --- a/base_layer/core/src/validation/block_validators/async_validator.rs +++ b/base_layer/core/src/validation/block_validators/async_validator.rs @@ -54,6 +54,7 @@ use crate::{ BlockSyncBodyValidation, ValidationError, }, + ValidatorNodeMmr, }; /// This validator checks whether a block satisfies consensus rules. @@ -100,6 +101,8 @@ impl BlockValidator { let inputs_task = self.start_input_validation(&valid_header, outputs.iter().map(|o| o.hash()).collect(), inputs); + let validator_node_mmr_task = self.start_validator_node_mmr_validation(&valid_header); + // Output order cannot be checked concurrently so it is checked here first if !helpers::is_all_unique_and_sorted(&outputs) { return Err(ValidationError::UnsortedOrDuplicateOutput); @@ -110,6 +113,7 @@ impl BlockValidator { let outputs_result = outputs_task.await??; let inputs_result = inputs_task.await??; let kernels_result = kernels_task.await??; + validator_node_mmr_task.await??; // Perform final checks using validation outputs helpers::check_coinbase_maturity(&self.rules, valid_header.height, outputs_result.coinbase())?; @@ -405,6 +409,7 @@ impl BlockValidator { helpers::check_permitted_output_types(&constants, output)?; helpers::check_tari_script_byte_size(&output.script, max_script_size)?; output.verify_metadata_signature()?; + output.verify_validator_node_signature()?; helpers::check_not_duplicate_txo(&*db, output)?; commitment_sum = &commitment_sum + &output.commitment; } @@ -465,6 +470,25 @@ impl BlockValidator { }) .into() } + + fn start_validator_node_mmr_validation( + &self, + header: &BlockHeader, + ) -> AbortOnDropJoinHandle> { + let vn_root = header.validator_node_merkle_root.clone(); + let height = header.height; + let db = self.db.inner().clone(); + task::spawn(async move { + let vns = db.fetch_active_validator_nodes(height)?; + let mmr = ValidatorNodeMmr::new(vns.iter().map(|vn| vn.shard_key.to_vec()).collect()); + if mmr.get_merkle_root().unwrap() == vn_root { + Ok(()) + } else { + Err(ValidationError::ValidatorNodeMmmrError) + } + }) + .into() + } } #[async_trait] diff --git a/base_layer/core/src/validation/block_validators/test.rs b/base_layer/core/src/validation/block_validators/test.rs index 0be1c4527b..2a5df07937 100644 --- a/base_layer/core/src/validation/block_validators/test.rs +++ b/base_layer/core/src/validation/block_validators/test.rs @@ -88,6 +88,7 @@ async fn it_checks_the_coinbase_reward() { let (block, _) = blockchain.create_chained_block(block_spec!("A", parent: "GB", reward: 10 * T, )); let err = validator.validate_block_body(block.block().clone()).await.unwrap_err(); + println!("err {:?}", err); assert!(matches!( err, ValidationError::TransactionError(TransactionError::InvalidCoinbase) diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index ae34950a65..7fa41e1a9d 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -132,6 +132,8 @@ pub enum ValidationError { OutputTypeNotPermitted { output_type: OutputType }, #[error("FixedHash size error: {0}")] FixedHashSizeError(#[from] FixedHashSizeError), + #[error("Validator node MMR is not correct")] + ValidatorNodeMmmrError, } // ChainStorageError has a ValidationError variant, so to prevent a cyclic dependency we use a string representation in diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 71f41083ff..90723ccc68 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -113,7 +113,8 @@ mod header_validators { let genesis = db.fetch_chain_header(0).unwrap(); - let mut header = BlockHeader::from_previous(genesis.header()); + let mut header = + BlockHeader::from_previous(genesis.header(), genesis.header().validator_node_merkle_root.clone()); header.version = u16::MAX; let validator = HeaderValidator::new(consensus_manager.clone()); @@ -201,7 +202,7 @@ fn chain_balance_validation() { .build() .unwrap(); - let mut header1 = BlockHeader::from_previous(genesis.header()); + let mut header1 = BlockHeader::from_previous(genesis.header(), genesis.header().validator_node_merkle_root.clone()); header1.kernel_mmr_size += 1; header1.output_mmr_size += 1; let achieved_difficulty = AchievedTargetDifficulty::try_construct( @@ -253,7 +254,7 @@ fn chain_balance_validation() { .build() .unwrap(); - let mut header2 = BlockHeader::from_previous(header1.header()); + let mut header2 = BlockHeader::from_previous(header1.header(), header1.header().validator_node_merkle_root.clone()); header2.kernel_mmr_size += 1; header2.output_mmr_size += 1; let achieved_difficulty = AchievedTargetDifficulty::try_construct( @@ -375,7 +376,7 @@ fn chain_balance_validation_burned() { .build() .unwrap(); burned_sum = &burned_sum + kernel2.get_burn_commitment().unwrap(); - let mut header1 = BlockHeader::from_previous(genesis.header()); + let mut header1 = BlockHeader::from_previous(genesis.header(), genesis.header().validator_node_merkle_root.clone()); header1.kernel_mmr_size += 2; header1.output_mmr_size += 2; let achieved_difficulty = AchievedTargetDifficulty::try_construct( diff --git a/base_layer/core/tests/chain_storage_tests/chain_backend.rs b/base_layer/core/tests/chain_storage_tests/chain_backend.rs index 6ace36eb98..fcdc74b6d7 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_backend.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_backend.rs @@ -23,7 +23,7 @@ use tari_common::configuration::Network; use tari_core::{ chain_storage::{create_lmdb_database, BlockchainBackend, ChainStorageError, DbKey, DbTransaction, DbValue}, - consensus::ConsensusManagerBuilder, + consensus::{ConsensusManager, ConsensusManagerBuilder}, test_helpers::blockchain::create_test_db, tx, }; @@ -69,17 +69,18 @@ fn lmdb_file_lock() { // Perform test { - let db = create_lmdb_database(&temp_path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let db = create_lmdb_database(&temp_path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); - match create_lmdb_database(&temp_path, LMDBConfig::default()) { + match create_lmdb_database(&temp_path, LMDBConfig::default(), consensus_manager.clone()) { Err(ChainStorageError::CannotAcquireFileLock) => {}, _ => panic!("Should not be able to make this db"), } drop(db); - let _db2 = - create_lmdb_database(&temp_path, LMDBConfig::default()).expect("Should be able to make a new lmdb now"); + let _db2 = create_lmdb_database(&temp_path, LMDBConfig::default(), consensus_manager) + .expect("Should be able to make a new lmdb now"); } // Cleanup test data - in Windows the LMBD `set_mapsize` sets file size equals to map size; Linux use sparse files diff --git a/base_layer/core/tests/chain_storage_tests/chain_storage.rs b/base_layer/core/tests/chain_storage_tests/chain_storage.rs index 91653f5590..313a2fa43b 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_storage.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_storage.rs @@ -36,7 +36,7 @@ use tari_core::{ MmrTree, Validators, }, - consensus::{emission::Emission, ConsensusConstantsBuilder, ConsensusManagerBuilder}, + consensus::{emission::Emission, ConsensusConstantsBuilder, ConsensusManager, ConsensusManagerBuilder}, proof_of_work::Difficulty, test_helpers::blockchain::{ create_store_with_consensus, @@ -89,7 +89,10 @@ fn insert_and_fetch_header() { let _consensus_manager = ConsensusManagerBuilder::new(network).build(); let store = create_test_blockchain_db(); let genesis_block = store.fetch_tip_header().unwrap(); - let mut header1 = BlockHeader::from_previous(genesis_block.header()); + let mut header1 = BlockHeader::from_previous( + genesis_block.header(), + genesis_block.header().validator_node_merkle_root.clone(), + ); header1.kernel_mmr_size += 1; header1.output_mmr_size += 1; @@ -97,7 +100,7 @@ fn insert_and_fetch_header() { let chain1 = create_chain_header(header1.clone(), genesis_block.accumulated_data()); store.insert_valid_headers(vec![chain1.clone()]).unwrap(); - let mut header2 = BlockHeader::from_previous(&header1); + let mut header2 = BlockHeader::from_previous(&header1, header1.validator_node_merkle_root.clone()); header2.kernel_mmr_size += 2; header2.output_mmr_size += 2; let chain2 = create_chain_header(header2.clone(), chain1.accumulated_data()); @@ -1529,7 +1532,6 @@ fn orphan_cleanup_on_reorg() { fn orphan_cleanup_delete_all_orphans() { let path = create_temporary_data_path(); let network = Network::LocalNet; - let consensus_manager = ConsensusManagerBuilder::new(network).build(); let validators = Validators::new( MockValidator::new(true), MockValidator::new(true), @@ -1543,7 +1545,8 @@ fn orphan_cleanup_delete_all_orphans() { }; // Test cleanup during runtime { - let db = create_lmdb_database(&path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(network).build(); + let db = create_lmdb_database(&path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); let store = BlockchainDatabase::new( db, consensus_manager.clone(), @@ -1596,13 +1599,14 @@ fn orphan_cleanup_delete_all_orphans() { // Test orphans are present on open { - let db = create_lmdb_database(&path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let db = create_lmdb_database(&path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); let store = BlockchainDatabase::new( db, consensus_manager.clone(), validators.clone(), config, - DifficultyCalculator::new(consensus_manager.clone(), Default::default()), + DifficultyCalculator::new(consensus_manager, Default::default()), ) .unwrap(); assert_eq!(store.db_read_access().unwrap().orphan_count().unwrap(), 5); @@ -1610,7 +1614,8 @@ fn orphan_cleanup_delete_all_orphans() { // Test orphans cleanup on open { - let db = create_lmdb_database(&path, LMDBConfig::default()).unwrap(); + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let db = create_lmdb_database(&path, LMDBConfig::default(), consensus_manager.clone()).unwrap(); config.cleanup_orphans_at_startup = true; let store = BlockchainDatabase::new( db, diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index 3dcffbb439..36fa4c5e8d 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -338,7 +338,8 @@ pub fn chain_block( transactions: Vec, consensus: &ConsensusManager, ) -> NewBlockTemplate { - let mut header = BlockHeader::from_previous(&prev_block.header); + let mut header = + BlockHeader::from_previous(&prev_block.header, prev_block.header.validator_node_merkle_root.clone()); header.version = consensus.consensus_constants(header.height).blockchain_version(); let height = header.height; let reward = consensus.get_block_reward_at(height); @@ -366,7 +367,10 @@ pub fn chain_block_with_coinbase( coinbase_kernel: TransactionKernel, consensus: &ConsensusManager, ) -> NewBlockTemplate { - let mut header = BlockHeader::from_previous(prev_block.header()); + let mut header = BlockHeader::from_previous( + prev_block.header(), + prev_block.header().validator_node_merkle_root.clone(), + ); header.version = consensus.consensus_constants(header.height).blockchain_version(); let height = header.height; NewBlockTemplate::from_block( @@ -397,7 +401,10 @@ pub fn chain_block_with_new_coinbase( coinbase_value, height + consensus_manager.consensus_constants(height).coinbase_lock_height(), ); - let mut header = BlockHeader::from_previous(prev_block.header()); + let mut header = BlockHeader::from_previous( + prev_block.header(), + prev_block.header().validator_node_merkle_root.clone(), + ); header.height = height; header.version = consensus_manager .consensus_constants(header.height) diff --git a/base_layer/core/tests/mempool.rs b/base_layer/core/tests/mempool.rs index 87911a2e7b..3caa202711 100644 --- a/base_layer/core/tests/mempool.rs +++ b/base_layer/core/tests/mempool.rs @@ -1075,6 +1075,8 @@ async fn consensus_validation_versions() { 0, Default::default(), None, + None, + None, ); let test_params = TestParams::new(); diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 9a68c97690..86ff23093c 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -31,7 +31,7 @@ use chacha20poly1305::XChaCha20Poly1305; use chrono::NaiveDateTime; use tari_common_types::{ transaction::{ImportStatus, TxId}, - types::PublicKey, + types::{PublicKey, Signature}, }; use tari_comms::types::CommsPublicKey; use tari_core::{ @@ -85,6 +85,12 @@ pub enum TransactionServiceRequest { fee_per_gram: MicroTari, message: String, }, + RegisterValidatorNode { + validator_node_public_key: CommsPublicKey, + validator_node_signature: Signature, + fee_per_gram: MicroTari, + message: String, + }, SendOneSidedTransaction { dest_pubkey: CommsPublicKey, amount: MicroTari, @@ -151,6 +157,12 @@ impl fmt::Display for TransactionServiceRequest { message )), Self::BurnTari { amount, message, .. } => f.write_str(&format!("Burning Tari ({}, {})", amount, message)), + Self::RegisterValidatorNode { + validator_node_public_key, + validator_node_signature: _, + fee_per_gram: _, + message, + } => f.write_str(&format!("Registering VN ({}, {})", validator_node_public_key, message)), Self::SendOneSidedTransaction { dest_pubkey, amount, @@ -448,6 +460,28 @@ impl TransactionServiceHandle { } } + pub async fn register_validator_node( + &mut self, + validator_node_public_key: PublicKey, + validator_node_signature: Signature, + fee_per_gram: MicroTari, + message: String, + ) -> Result { + match self + .handle + .call(TransactionServiceRequest::RegisterValidatorNode { + validator_node_public_key, + validator_node_signature, + fee_per_gram, + message, + }) + .await?? + { + TransactionServiceResponse::TransactionSent(tx_id) => Ok(tx_id), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + pub async fn send_one_sided_transaction( &mut self, dest_pubkey: CommsPublicKey, diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index dd621eea8c..8d9ea4e299 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -35,7 +35,7 @@ use rand::rngs::OsRng; use sha2::Sha256; use tari_common_types::{ transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, - types::{PrivateKey, PublicKey}, + types::{PrivateKey, PublicKey, Signature}, }; use tari_comms::{peer_manager::NodeIdentity, types::CommsPublicKey}; use tari_comms_dht::outbound::OutboundMessageRequester; @@ -634,6 +634,25 @@ where .burn_tari(amount, fee_per_gram, message, transaction_broadcast_join_handles) .await .map(TransactionServiceResponse::TransactionSent), + TransactionServiceRequest::RegisterValidatorNode { + validator_node_public_key, + validator_node_signature, + fee_per_gram, + message, + } => { + let rp = reply_channel.take().expect("Cannot be missing"); + self.register_validator_node( + validator_node_public_key, + validator_node_signature, + fee_per_gram, + message, + send_transaction_join_handles, + transaction_broadcast_join_handles, + rp, + ) + .await?; + return Ok(()); + }, TransactionServiceRequest::SendShaAtomicSwapTransaction(dest_pubkey, amount, fee_per_gram, message) => { Ok(TransactionServiceResponse::ShaAtomicSwapTransactionSent( self.send_sha_atomic_swap_transaction( @@ -1444,6 +1463,38 @@ where Ok(tx_id) } + pub async fn register_validator_node( + &mut self, + validator_node_public_key: CommsPublicKey, + validator_node_signature: Signature, + fee_per_gram: MicroTari, + message: String, + join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + reply_channel: oneshot::Sender>, + ) -> Result<(), TransactionServiceError> { + let output_features = + OutputFeatures::create_validator_node_registration(validator_node_public_key, validator_node_signature); + let tx_meta = + TransactionMetadata::new_with_features(0.into(), 3, KernelFeatures::create_validator_node_registration()); + self.send_transaction( + self.node_identity.public_key().clone(), + MicroTari::from(1), + output_features, + fee_per_gram, + message, + tx_meta, + join_handles, + transaction_broadcast_join_handles, + reply_channel, + ) + .await + } + /// Sends a one side payment transaction to a recipient /// # Arguments /// 'dest_pubkey': The Comms pubkey of the recipient node diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 579187881d..161a79e7c7 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -1532,7 +1532,15 @@ pub unsafe extern "C" fn output_features_create_from_bytes( let decoded_metadata = (*metadata).0.clone(); - let output_features = TariOutputFeatures::new(decoded_version, output_type, maturity, decoded_metadata, None); + let output_features = TariOutputFeatures::new( + decoded_version, + output_type, + maturity, + decoded_metadata, + None, + None, + None, + ); Box::into_raw(Box::new(output_features)) }