diff --git a/Cargo.lock b/Cargo.lock index c8c82c179..4eebdee20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9088,6 +9088,7 @@ dependencies = [ "tari_bor", "tari_common", "tari_common_types", + "tari_consensus", "tari_core", "tari_crypto", "tari_dan_common_types", @@ -9452,6 +9453,7 @@ dependencies = [ "tari_bor", "tari_common", "tari_common_types", + "tari_consensus", "tari_crypto", "tari_dan_app_utilities", "tari_dan_common_types", @@ -9502,7 +9504,10 @@ dependencies = [ "log", "rand", "serde", + "tari_common", + "tari_consensus", "tari_dan_common_types", + "tari_dan_storage", "tari_engine_types", "tari_epoch_manager", "tari_template_lib", diff --git a/applications/tari_dan_app_utilities/Cargo.toml b/applications/tari_dan_app_utilities/Cargo.toml index 41c37e772..169e9c078 100644 --- a/applications/tari_dan_app_utilities/Cargo.toml +++ b/applications/tari_dan_app_utilities/Cargo.toml @@ -13,6 +13,7 @@ tari_core = { workspace = true, default-features = false, features = ["transacti tari_crypto = { workspace = true } tari_shutdown = { workspace = true } +tari_consensus = { workspace = true } tari_dan_common_types = { workspace = true } tari_state_store_sqlite = { workspace = true } tari_dan_engine = { workspace = true } diff --git a/applications/tari_dan_app_utilities/src/lib.rs b/applications/tari_dan_app_utilities/src/lib.rs index b3600006c..cf2ba9aa4 100644 --- a/applications/tari_dan_app_utilities/src/lib.rs +++ b/applications/tari_dan_app_utilities/src/lib.rs @@ -26,6 +26,7 @@ pub mod consensus_constants; pub mod keypair; pub mod p2p_config; pub mod seed_peer; +pub mod signature_service; pub mod substate_file_cache; pub mod template_manager; pub mod transaction_executor; diff --git a/applications/tari_dan_app_utilities/src/signature_service.rs b/applications/tari_dan_app_utilities/src/signature_service.rs new file mode 100644 index 000000000..025d00951 --- /dev/null +++ b/applications/tari_dan_app_utilities/src/signature_service.rs @@ -0,0 +1,62 @@ +// Copyright 2024. 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 rand::rngs::OsRng; +use tari_common_types::types::{FixedHash, PublicKey}; +use tari_consensus::traits::{ValidatorSignatureService, VoteSignatureService}; +use tari_dan_storage::consensus_models::{BlockId, QuorumDecision, ValidatorSchnorrSignature, ValidatorSignature}; + +use crate::keypair::RistrettoKeypair; + +#[derive(Debug, Clone)] +pub struct TariSignatureService { + keypair: RistrettoKeypair, +} + +impl TariSignatureService { + pub fn new(keypair: RistrettoKeypair) -> Self { + Self { keypair } + } +} + +impl ValidatorSignatureService for TariSignatureService { + fn sign>(&self, message: M) -> ValidatorSchnorrSignature { + ValidatorSchnorrSignature::sign(self.keypair.secret_key(), message, &mut OsRng).unwrap() + } + + fn public_key(&self) -> &PublicKey { + self.keypair.public_key() + } +} + +impl VoteSignatureService for TariSignatureService { + fn verify( + &self, + signature: &ValidatorSignature, + leaf_hash: &FixedHash, + block_id: &BlockId, + decision: &QuorumDecision, + ) -> bool { + let challenge = self.create_challenge(leaf_hash, block_id, decision); + signature.verify(challenge) + } +} diff --git a/applications/tari_indexer/Cargo.toml b/applications/tari_indexer/Cargo.toml index 8d9ed1451..badb96c86 100644 --- a/applications/tari_indexer/Cargo.toml +++ b/applications/tari_indexer/Cargo.toml @@ -14,6 +14,7 @@ tari_common_types = { workspace = true } tari_crypto = { workspace = true } tari_shutdown = { workspace = true } +tari_consensus = { workspace = true } tari_bor = { workspace = true, default-features = true } tari_dan_app_utilities = { workspace = true } tari_dan_common_types = { workspace = true } diff --git a/applications/tari_indexer/src/dry_run/processor.rs b/applications/tari_indexer/src/dry_run/processor.rs index eb377d1db..f38d3aa95 100644 --- a/applications/tari_indexer/src/dry_run/processor.rs +++ b/applications/tari_indexer/src/dry_run/processor.rs @@ -25,6 +25,7 @@ use std::{collections::HashMap, sync::Arc}; use log::info; use tari_common::configuration::Network; use tari_dan_app_utilities::{ + signature_service::TariSignatureService, template_manager::implementation::TemplateManager, transaction_executor::{TariDanTransactionProcessor, TransactionExecutor}, }; @@ -63,11 +64,21 @@ const LOG_TARGET: &str = "tari::indexer::dry_run_transaction_processor"; pub struct DryRunTransactionProcessor { epoch_manager: EpochManagerHandle, client_provider: TariValidatorNodeRpcClientFactory, - transaction_autofiller: - TransactionAutofiller, TariValidatorNodeRpcClientFactory, TSubstateCache>, + transaction_autofiller: TransactionAutofiller< + EpochManagerHandle, + TariValidatorNodeRpcClientFactory, + TSubstateCache, + TariSignatureService, + >, template_manager: TemplateManager, - substate_scanner: - Arc, TariValidatorNodeRpcClientFactory, TSubstateCache>>, + substate_scanner: Arc< + SubstateScanner< + EpochManagerHandle, + TariValidatorNodeRpcClientFactory, + TSubstateCache, + TariSignatureService, + >, + >, network: Network, } @@ -78,7 +89,12 @@ where TSubstateCache: SubstateCache + 'static epoch_manager: EpochManagerHandle, client_provider: TariValidatorNodeRpcClientFactory, substate_scanner: Arc< - SubstateScanner, TariValidatorNodeRpcClientFactory, TSubstateCache>, + SubstateScanner< + EpochManagerHandle, + TariValidatorNodeRpcClientFactory, + TSubstateCache, + TariSignatureService, + >, >, template_manager: TemplateManager, network: Network, diff --git a/applications/tari_indexer/src/json_rpc/handlers.rs b/applications/tari_indexer/src/json_rpc/handlers.rs index 1b2664b3c..68238fe1e 100644 --- a/applications/tari_indexer/src/json_rpc/handlers.rs +++ b/applications/tari_indexer/src/json_rpc/handlers.rs @@ -32,7 +32,11 @@ use libp2p::swarm::dial_opts::{DialOpts, PeerCondition}; use log::{error, warn}; use serde_json::{self as json, json, Value}; use tari_base_node_client::{grpc::GrpcBaseNodeClient, types::BaseLayerConsensusConstants, BaseNodeClient}; -use tari_dan_app_utilities::{keypair::RistrettoKeypair, substate_file_cache::SubstateFileCache}; +use tari_dan_app_utilities::{ + keypair::RistrettoKeypair, + signature_service::TariSignatureService, + substate_file_cache::SubstateFileCache, +}; use tari_dan_common_types::{optional::Optional, public_key_to_peer_id, Epoch, PeerAddress}; use tari_dan_p2p::TariMessagingSpec; use tari_dan_storage::consensus_models::Decision; @@ -90,8 +94,12 @@ pub struct JsonRpcHandlers { base_node_client: GrpcBaseNodeClient, substate_manager: Arc, epoch_manager: EpochManagerHandle, - transaction_manager: - TransactionManager, TariValidatorNodeRpcClientFactory, SubstateFileCache>, + transaction_manager: TransactionManager< + EpochManagerHandle, + TariValidatorNodeRpcClientFactory, + SubstateFileCache, + TariSignatureService, + >, dry_run_transaction_processor: DryRunTransactionProcessor, } @@ -105,6 +113,7 @@ impl JsonRpcHandlers { EpochManagerHandle, TariValidatorNodeRpcClientFactory, SubstateFileCache, + TariSignatureService, >, dry_run_transaction_processor: DryRunTransactionProcessor, ) -> Self { @@ -315,6 +324,7 @@ impl JsonRpcHandlers { id, substate, created_by_tx, + quorum_certificates: _, } => Ok(JsonRpcResponse::success(answer_id, GetSubstateResponse { address: id, version: substate.version(), diff --git a/applications/tari_indexer/src/lib.rs b/applications/tari_indexer/src/lib.rs index 6ee720283..890a87713 100644 --- a/applications/tari_indexer/src/lib.rs +++ b/applications/tari_indexer/src/lib.rs @@ -50,6 +50,7 @@ use tari_common::{ use tari_dan_app_utilities::{ consensus_constants::ConsensusConstants, keypair::setup_keypair_prompt, + signature_service::TariSignatureService, substate_file_cache::SubstateFileCache, }; use tari_dan_storage::global::DbFactory; @@ -104,10 +105,13 @@ pub async fn run_indexer(config: ApplicationConfig, mut shutdown_signal: Shutdow let substate_cache = SubstateFileCache::new(substate_cache_dir) .map_err(|e| ExitError::new(ExitCode::ConfigError, format!("Substate cache error: {}", e)))?; + let signing_service = TariSignatureService::new(keypair.clone()); let dan_layer_scanner = Arc::new(SubstateScanner::new( services.epoch_manager.clone(), services.validator_node_client_factory.clone(), substate_cache, + signing_service, + config.network, )); let substate_manager = Arc::new(SubstateManager::new( diff --git a/applications/tari_indexer/src/substate_manager.rs b/applications/tari_indexer/src/substate_manager.rs index b31c52c3c..dfb5e9a8e 100644 --- a/applications/tari_indexer/src/substate_manager.rs +++ b/applications/tari_indexer/src/substate_manager.rs @@ -27,7 +27,7 @@ use log::info; use serde::{Deserialize, Serialize}; use tari_common_types::types::FixedHash; use tari_crypto::tari_utilities::message_format::MessageFormat; -use tari_dan_app_utilities::substate_file_cache::SubstateFileCache; +use tari_dan_app_utilities::{signature_service::TariSignatureService, substate_file_cache::SubstateFileCache}; use tari_dan_common_types::PeerAddress; use tari_engine_types::{ events::Event, @@ -81,15 +81,26 @@ pub struct EventResponse { } pub struct SubstateManager { - substate_scanner: - Arc, TariValidatorNodeRpcClientFactory, SubstateFileCache>>, + substate_scanner: Arc< + SubstateScanner< + EpochManagerHandle, + TariValidatorNodeRpcClientFactory, + SubstateFileCache, + TariSignatureService, + >, + >, substate_store: SqliteSubstateStore, } impl SubstateManager { pub fn new( dan_layer_scanner: Arc< - SubstateScanner, TariValidatorNodeRpcClientFactory, SubstateFileCache>, + SubstateScanner< + EpochManagerHandle, + TariValidatorNodeRpcClientFactory, + SubstateFileCache, + TariSignatureService, + >, >, substate_store: SqliteSubstateStore, ) -> Self { @@ -229,6 +240,7 @@ impl SubstateManager { id, substate, created_by_tx, + quorum_certificates: _, } => Ok(Some(SubstateResponse { address: id, version: substate.version(), diff --git a/applications/tari_indexer/src/transaction_manager/mod.rs b/applications/tari_indexer/src/transaction_manager/mod.rs index d80557b8d..d7e71de07 100644 --- a/applications/tari_indexer/src/transaction_manager/mod.rs +++ b/applications/tari_indexer/src/transaction_manager/mod.rs @@ -25,9 +25,10 @@ pub(crate) mod error; use std::{collections::HashSet, fmt::Display, future::Future, iter, sync::Arc}; use log::*; +use tari_consensus::traits::VoteSignatureService; use tari_dan_common_types::{ optional::{IsNotFoundError, Optional}, - NodeAddressable, + DerivableFromPublicKey, SubstateAddress, }; use tari_engine_types::substate::SubstateId; @@ -49,25 +50,26 @@ use crate::transaction_manager::error::TransactionManagerError; const LOG_TARGET: &str = "tari::indexer::transaction_manager"; -pub struct TransactionManager { +pub struct TransactionManager { epoch_manager: TEpochManager, client_provider: TClientFactory, - transaction_autofiller: TransactionAutofiller, + transaction_autofiller: TransactionAutofiller, } -impl - TransactionManager +impl + TransactionManager where - TAddr: NodeAddressable + 'static, + TAddr: DerivableFromPublicKey + 'static, TEpochManager: EpochManagerReader + 'static, TClientFactory: ValidatorNodeClientFactory + 'static, ::Error: IsNotFoundError + 'static, TSubstateCache: SubstateCache + 'static, + TSignatureService: VoteSignatureService + Send + Sync + Clone + 'static, { pub fn new( epoch_manager: TEpochManager, client_provider: TClientFactory, - substate_scanner: Arc>, + substate_scanner: Arc>, ) -> Self { Self { epoch_manager, diff --git a/applications/tari_validator_node/src/bootstrap.rs b/applications/tari_validator_node/src/bootstrap.rs index e946610e3..0cb6ac777 100644 --- a/applications/tari_validator_node/src/bootstrap.rs +++ b/applications/tari_validator_node/src/bootstrap.rs @@ -45,6 +45,7 @@ use tari_dan_app_utilities::{ consensus_constants::ConsensusConstants, keypair::RistrettoKeypair, seed_peer::SeedPeer, + signature_service::TariSignatureService, substate_file_cache::SubstateFileCache, template_manager, template_manager::{implementation::TemplateManager, interface::TemplateManagerHandle}, @@ -260,12 +261,17 @@ pub async fn spawn_services( let substate_cache = SubstateFileCache::new(substate_cache_dir) .map_err(|e| ExitError::new(ExitCode::ConfigError, format!("Substate cache error: {}", e)))?; + // Signature service + let signing_service = TariSignatureService::new(keypair.clone()); + // Mempool let virtual_substate_manager = VirtualSubstateManager::new(state_store.clone(), epoch_manager.clone()); let scanner = SubstateScanner::new( epoch_manager.clone(), validator_node_client_factory.clone(), substate_cache, + signing_service, + config.network, ); let substate_resolver = TariSubstateResolver::new( state_store.clone(), diff --git a/applications/tari_validator_node/src/consensus/mod.rs b/applications/tari_validator_node/src/consensus/mod.rs index d2e4e64a0..3f8e812bd 100644 --- a/applications/tari_validator_node/src/consensus/mod.rs +++ b/applications/tari_validator_node/src/consensus/mod.rs @@ -15,24 +15,19 @@ use tokio::{ }; use crate::{ - consensus::{ - leader_selection::RoundRobinLeaderStrategy, - signature_service::TariSignatureService, - spec::TariConsensusSpec, - state_manager::TariStateManager, - }, + consensus::{leader_selection::RoundRobinLeaderStrategy, state_manager::TariStateManager}, event_subscription::EventSubscription, }; mod handle; mod leader_selection; -mod signature_service; mod spec; +pub use spec::TariConsensusSpec; mod state_manager; pub use handle::*; use sqlite_message_logger::SqliteMessageLogger; -use tari_dan_app_utilities::keypair::RistrettoKeypair; +use tari_dan_app_utilities::{keypair::RistrettoKeypair, signature_service::TariSignatureService}; use tari_dan_common_types::PeerAddress; use tari_rpc_state_sync::RpcStateSyncManager; diff --git a/applications/tari_validator_node/src/consensus/signature_service.rs b/applications/tari_validator_node/src/consensus/signature_service.rs deleted file mode 100644 index 87bcb2937..000000000 --- a/applications/tari_validator_node/src/consensus/signature_service.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2023 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -use rand::rngs::OsRng; -use tari_common_types::types::{FixedHash, PublicKey}; -use tari_consensus::traits::{ValidatorSignatureService, VoteSignatureService}; -use tari_dan_app_utilities::keypair::RistrettoKeypair; -use tari_dan_storage::consensus_models::{BlockId, QuorumDecision, ValidatorSchnorrSignature, ValidatorSignature}; - -#[derive(Debug, Clone)] -pub struct TariSignatureService { - keypair: RistrettoKeypair, -} - -impl TariSignatureService { - pub fn new(keypair: RistrettoKeypair) -> Self { - Self { keypair } - } -} - -impl ValidatorSignatureService for TariSignatureService { - fn sign>(&self, message: M) -> ValidatorSchnorrSignature { - ValidatorSchnorrSignature::sign(self.keypair.secret_key(), message, &mut OsRng).unwrap() - } - - fn public_key(&self) -> &PublicKey { - self.keypair.public_key() - } -} - -impl VoteSignatureService for TariSignatureService { - fn verify( - &self, - signature: &ValidatorSignature, - leaf_hash: &FixedHash, - block_id: &BlockId, - decision: &QuorumDecision, - ) -> bool { - let challenge = self.create_challenge(leaf_hash, block_id, decision); - signature.verify(challenge) - } -} diff --git a/applications/tari_validator_node/src/consensus/spec.rs b/applications/tari_validator_node/src/consensus/spec.rs index 9b2a08a58..94cd049ad 100644 --- a/applications/tari_validator_node/src/consensus/spec.rs +++ b/applications/tari_validator_node/src/consensus/spec.rs @@ -3,17 +3,14 @@ use sqlite_message_logger::SqliteMessageLogger; use tari_consensus::traits::ConsensusSpec; +use tari_dan_app_utilities::signature_service::TariSignatureService; use tari_dan_common_types::PeerAddress; use tari_epoch_manager::base_layer::EpochManagerHandle; use tari_rpc_state_sync::RpcStateSyncManager; use tari_state_store_sqlite::SqliteStateStore; use crate::{ - consensus::{ - leader_selection::RoundRobinLeaderStrategy, - signature_service::TariSignatureService, - state_manager::TariStateManager, - }, + consensus::{leader_selection::RoundRobinLeaderStrategy, state_manager::TariStateManager}, p2p::services::messaging::{ConsensusInboundMessaging, ConsensusOutboundMessaging}, }; diff --git a/applications/tari_validator_node/src/dry_run_transaction_processor.rs b/applications/tari_validator_node/src/dry_run_transaction_processor.rs index 57c9c2142..1ad6190c4 100644 --- a/applications/tari_validator_node/src/dry_run_transaction_processor.rs +++ b/applications/tari_validator_node/src/dry_run_transaction_processor.rs @@ -22,6 +22,7 @@ use log::info; use tari_dan_app_utilities::{ + signature_service::TariSignatureService, substate_file_cache::SubstateFileCache, template_manager::implementation::TemplateManager, transaction_executor::{TariDanTransactionProcessor, TransactionExecutor, TransactionProcessorError}, @@ -70,13 +71,14 @@ pub enum DryRunTransactionProcessorError { VirtualSubstateError(#[from] VirtualSubstateError), } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct DryRunTransactionProcessor { substate_resolver: TariSubstateResolver< SqliteStateStore, EpochManagerHandle, TariValidatorNodeRpcClientFactory, SubstateFileCache, + TariSignatureService, >, epoch_manager: EpochManagerHandle, payload_processor: TariDanTransactionProcessor>, @@ -91,6 +93,7 @@ impl DryRunTransactionProcessor { EpochManagerHandle, TariValidatorNodeRpcClientFactory, SubstateFileCache, + TariSignatureService, >, ) -> Self { Self { diff --git a/applications/tari_validator_node/src/json_rpc/handlers.rs b/applications/tari_validator_node/src/json_rpc/handlers.rs index edaaba24f..15d22dfe5 100644 --- a/applications/tari_validator_node/src/json_rpc/handlers.rs +++ b/applications/tari_validator_node/src/json_rpc/handlers.rs @@ -340,32 +340,47 @@ impl JsonRpcHandlers { pub async fn get_substate(&self, value: JsonRpcExtractor) -> JrpcResult { let answer_id = value.get_answer_id(); let data: GetSubstateRequest = value.parse_params()?; + let address = SubstateAddress::from_address(&data.address, data.version); - let maybe_substate = self - .state_store - .with_read_tx(|tx| { - let address = SubstateAddress::from_address(&data.address, data.version); - SubstateRecord::get(tx, &address).optional() - }) + let mut tx = self.state_store.create_read_tx().map_err(internal_error(answer_id))?; + + let maybe_substate = SubstateRecord::get(&mut tx, &address) + .optional() + .map_err(internal_error(answer_id))?; + + let Some(substate) = maybe_substate else { + return Ok(JsonRpcResponse::success(answer_id, GetSubstateResponse { + status: SubstateStatus::DoesNotExist, + created_by_tx: None, + value: None, + quorum_certificates: vec![], + })); + }; + + let created_qc = substate + .get_created_quorum_certificate(&mut tx) .map_err(internal_error(answer_id))?; - match maybe_substate { - Some(substate) if substate.is_destroyed() => Ok(JsonRpcResponse::success(answer_id, GetSubstateResponse { + let resp = if substate.is_destroyed() { + let destroyed_qc = substate + .get_destroyed_quorum_certificate(&mut tx) + .map_err(internal_error(answer_id))?; + GetSubstateResponse { status: SubstateStatus::Down, created_by_tx: Some(substate.created_by_transaction), value: None, - })), - Some(substate) => Ok(JsonRpcResponse::success(answer_id, GetSubstateResponse { + quorum_certificates: Some(created_qc).into_iter().chain(destroyed_qc).collect(), + } + } else { + GetSubstateResponse { status: SubstateStatus::Up, created_by_tx: Some(substate.created_by_transaction), value: Some(substate.into_substate_value()), - })), - None => Ok(JsonRpcResponse::success(answer_id, GetSubstateResponse { - status: SubstateStatus::DoesNotExist, - created_by_tx: None, - value: None, - })), - } + quorum_certificates: vec![created_qc], + } + }; + + Ok(JsonRpcResponse::success(answer_id, resp)) } pub async fn get_substates_created_by_transaction(&self, value: JsonRpcExtractor) -> JrpcResult { diff --git a/applications/tari_validator_node/src/substate_resolver.rs b/applications/tari_validator_node/src/substate_resolver.rs index c63960ec6..f47ea966d 100644 --- a/applications/tari_validator_node/src/substate_resolver.rs +++ b/applications/tari_validator_node/src/substate_resolver.rs @@ -6,7 +6,8 @@ use std::{collections::HashSet, time::Instant}; use async_trait::async_trait; use log::*; use tari_common_types::types::PublicKey; -use tari_dan_common_types::{Epoch, SubstateAddress}; +use tari_consensus::traits::VoteSignatureService; +use tari_dan_common_types::{DerivableFromPublicKey, Epoch, SubstateAddress}; use tari_dan_engine::{runtime::VirtualSubstates, state_store::memory::MemoryStateStore}; use tari_dan_storage::{consensus_models::SubstateRecord, StateStore, StorageError}; use tari_engine_types::{ @@ -27,24 +28,32 @@ use crate::{ const LOG_TARGET: &str = "tari::dan::substate_resolver"; #[derive(Debug, Clone)] -pub struct TariSubstateResolver { +pub struct TariSubstateResolver< + TStateStore, + TEpochManager, + TValidatorNodeClientFactory, + TSubstateCache, + TSignatureService, +> { store: TStateStore, - scanner: SubstateScanner, + scanner: SubstateScanner, epoch_manager: TEpochManager, virtual_substate_manager: VirtualSubstateManager, } -impl - TariSubstateResolver +impl + TariSubstateResolver where - TStateStore: StateStore, - TEpochManager: EpochManagerReader, - TValidatorNodeClientFactory: ValidatorNodeClientFactory, + TAddr: DerivableFromPublicKey, + TStateStore: StateStore, + TEpochManager: EpochManagerReader, + TValidatorNodeClientFactory: ValidatorNodeClientFactory, TSubstateCache: SubstateCache, + TSignatureService: VoteSignatureService, { pub fn new( store: TStateStore, - scanner: SubstateScanner, + scanner: SubstateScanner, epoch_manager: TEpochManager, virtual_substate_manager: VirtualSubstateManager, ) -> Self { @@ -150,13 +159,15 @@ where } #[async_trait] -impl SubstateResolver - for TariSubstateResolver +impl SubstateResolver + for TariSubstateResolver where - TStateStore: StateStore + Sync + Send, - TEpochManager: EpochManagerReader, - TValidatorNodeClientFactory: ValidatorNodeClientFactory, + TAddr: DerivableFromPublicKey, + TStateStore: StateStore + Sync + Send, + TEpochManager: EpochManagerReader, + TValidatorNodeClientFactory: ValidatorNodeClientFactory, TSubstateCache: SubstateCache, + TSignatureService: VoteSignatureService + Send + Sync, { type Error = SubstateResolverError; diff --git a/clients/validator_node_client/src/types.rs b/clients/validator_node_client/src/types.rs index 390f133ca..a82609b0c 100644 --- a/clients/validator_node_client/src/types.rs +++ b/clients/validator_node_client/src/types.rs @@ -27,7 +27,15 @@ use serde::{Deserialize, Serialize}; use tari_common_types::{transaction::TxId, types::PublicKey}; use tari_dan_common_types::{committee::CommitteeShard, shard::Shard, Epoch, SubstateAddress}; use tari_dan_storage::{ - consensus_models::{Block, BlockId, Decision, ExecutedTransaction, QuorumDecision, SubstateRecord}, + consensus_models::{ + Block, + BlockId, + Decision, + ExecutedTransaction, + QuorumCertificate, + QuorumDecision, + SubstateRecord, + }, global::models::ValidatorNode, Ordering, }; @@ -317,6 +325,7 @@ pub struct GetSubstateResponse { pub value: Option, pub created_by_tx: Option, pub status: SubstateStatus, + pub quorum_certificates: Vec, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] diff --git a/dan_layer/consensus/src/block_validations.rs b/dan_layer/consensus/src/block_validations.rs index 1919d82a4..60dd6212f 100644 --- a/dan_layer/consensus/src/block_validations.rs +++ b/dan_layer/consensus/src/block_validations.rs @@ -8,7 +8,8 @@ use tari_epoch_manager::EpochManagerReader; use crate::{ hotstuff::{HotStuffError, ProposalValidationError}, - traits::{ConsensusSpec, LeaderStrategy, VoteSignatureService}, + quorum_certificate_validations::validate_quorum_certificate, + traits::{ConsensusSpec, LeaderStrategy}, }; pub fn check_network(candidate_block: &Block, network: Network) -> Result<(), ProposalValidationError> { @@ -98,42 +99,20 @@ pub async fn check_quorum_certificate( } .into()); } - let mut vns = vec![]; - for signature in candidate_block.justify().signatures() { - let vn = epoch_manager - .get_validator_node_by_public_key(candidate_block.justify().epoch(), signature.public_key()) - .await?; - vns.push(vn.get_node_hash(candidate_block.network())); - } - let merkle_root = epoch_manager - .get_validator_node_merkle_root(candidate_block.justify().epoch()) - .await?; - let qc = candidate_block.justify(); - let proof = qc.merged_proof().clone(); - if !proof.verify_consume(&merkle_root, vns.iter().map(|hash| hash.to_vec()).collect())? { - return Err(ProposalValidationError::QCisNotValid { qc: qc.clone() }.into()); - } - for (sign, leaf) in qc.signatures().iter().zip(vns.iter()) { - let challenge = vote_signing_service.create_challenge(leaf, qc.block_id(), &qc.decision()); - if !sign.verify(challenge) { - return Err(ProposalValidationError::QCInvalidSignature { qc: qc.clone() }.into()); - } - } let committee_shard = epoch_manager - .get_committee_shard_by_validator_public_key( - qc.epoch(), - qc.signatures() - .first() - .ok_or::(ProposalValidationError::QuorumWasNotReached { qc: qc.clone() }.into())? - .public_key(), - ) + .get_committee_shard_by_validator_public_key(candidate_block.epoch(), candidate_block.proposed_by()) .await?; - if committee_shard.quorum_threshold() > - u32::try_from(qc.signatures().len()).map_err(|_| ProposalValidationError::QCisNotValid { qc: qc.clone() })? - { - return Err(ProposalValidationError::QuorumWasNotReached { qc: qc.clone() }.into()); - } + validate_quorum_certificate( + candidate_block.justify(), + &committee_shard, + vote_signing_service, + epoch_manager, + candidate_block.network(), + ) + .await + .map_err(ProposalValidationError::QuorumCertificateValidationError)?; + Ok(()) } diff --git a/dan_layer/consensus/src/hotstuff/error.rs b/dan_layer/consensus/src/hotstuff/error.rs index f50ca271e..bddcabaf6 100644 --- a/dan_layer/consensus/src/hotstuff/error.rs +++ b/dan_layer/consensus/src/hotstuff/error.rs @@ -3,14 +3,17 @@ use tari_dan_common_types::{Epoch, NodeHeight}; use tari_dan_storage::{ - consensus_models::{BlockId, LeafBlock, LockedBlock, QuorumCertificate, TransactionPoolError}, + consensus_models::{BlockId, LeafBlock, LockedBlock, TransactionPoolError}, StorageError, }; use tari_epoch_manager::EpochManagerError; use tari_mmr::BalancedBinaryMerkleProofError; use tari_transaction::TransactionId; -use crate::traits::{InboundMessagingError, OutboundMessagingError}; +use crate::{ + quorum_certificate_validations::QuorumCertificateValidationError, + traits::{InboundMessagingError, OutboundMessagingError}, +}; #[derive(Debug, thiserror::Error)] pub enum HotStuffError { @@ -162,14 +165,10 @@ pub enum ProposalValidationError { MissingSignature { block_id: BlockId, height: NodeHeight }, #[error("Proposed block {block_id} {height} has invalid signature")] InvalidSignature { block_id: BlockId, height: NodeHeight }, - #[error("QC is not valid: {qc}")] - QCisNotValid { qc: QuorumCertificate }, - #[error("QC has invalid signature: {qc}")] - QCInvalidSignature { qc: QuorumCertificate }, - #[error("Quorum was not reached: {qc}")] - QuorumWasNotReached { qc: QuorumCertificate }, #[error("Merkle proof error: {0}")] BalancedBinaryMerkleProofError(#[from] BalancedBinaryMerkleProofError), + #[error("Quorum certificate validation error: {0}")] + QuorumCertificateValidationError(#[from] QuorumCertificateValidationError), #[error("Invalid network in block {block_id}: expected {expected_network}, given {block_network}")] InvalidNetwork { expected_network: String, diff --git a/dan_layer/consensus/src/lib.rs b/dan_layer/consensus/src/lib.rs index 7674bed00..2b9ca7dda 100644 --- a/dan_layer/consensus/src/lib.rs +++ b/dan_layer/consensus/src/lib.rs @@ -4,4 +4,5 @@ mod block_validations; pub mod hotstuff; pub mod messages; +pub mod quorum_certificate_validations; pub mod traits; diff --git a/dan_layer/consensus/src/quorum_certificate_validations.rs b/dan_layer/consensus/src/quorum_certificate_validations.rs new file mode 100644 index 000000000..44731779a --- /dev/null +++ b/dan_layer/consensus/src/quorum_certificate_validations.rs @@ -0,0 +1,102 @@ +// Copyright 2024. 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 tari_common::configuration::Network; +use tari_dan_common_types::{committee::CommitteeShard, DerivableFromPublicKey, NodeHeight}; +use tari_dan_storage::consensus_models::QuorumCertificate; +use tari_epoch_manager::{EpochManagerError, EpochManagerReader}; + +use crate::traits::VoteSignatureService; + +#[derive(Debug, thiserror::Error)] +pub enum QuorumCertificateValidationError { + #[error("QC has invalid merkle proof")] + InvalidMerkleProof, + #[error("QC has invalid signature")] + InvalidSignature, + #[error("Quorum was not reached")] + QuorumWasNotReached, + #[error("Malformed QC")] + MalformedCertificate, + #[error("Epoch manager error: {0}")] + StorageError(#[from] EpochManagerError), + #[error("Block {block_height} is not higher than justify {justify_block_height}")] + BlockNotHigherThanJustify { + justify_block_height: NodeHeight, + block_height: NodeHeight, + }, +} + +/// Validates Quorum Certificates in isolation +pub async fn validate_quorum_certificate< + TAddr: DerivableFromPublicKey, + TEpochManager: EpochManagerReader, + TSignatureService: VoteSignatureService, +>( + qc: &QuorumCertificate, + committee_shard: &CommitteeShard, + vote_signing_service: &TSignatureService, + epoch_manager: &TEpochManager, + network: Network, +) -> Result<(), QuorumCertificateValidationError> { + // ignore genesis block. + if qc.epoch().as_u64() == 0 { + return Ok(()); + } + + // fetch the committee members that should have signed the QC + let mut vns = vec![]; + for signature in qc.signatures() { + let vn = epoch_manager + .get_validator_node_by_public_key(qc.epoch(), signature.public_key()) + .await?; + vns.push(vn.get_node_hash(network)); + } + + // validate the QC's merkle proof + let merkle_root = epoch_manager.get_validator_node_merkle_root(qc.epoch()).await?; + let proof = qc.merged_proof().clone(); + let vns_bytes = vns.iter().map(|hash| hash.to_vec()).collect(); + let is_proof_valid = proof + .verify_consume(&merkle_root, vns_bytes) + .map_err(|_| QuorumCertificateValidationError::InvalidMerkleProof)?; + if !is_proof_valid { + return Err(QuorumCertificateValidationError::InvalidMerkleProof); + } + + // validate each signature in the QC + for (sign, leaf) in qc.signatures().iter().zip(vns.iter()) { + let challenge = vote_signing_service.create_challenge(leaf, qc.block_id(), &qc.decision()); + if !sign.verify(challenge) { + return Err(QuorumCertificateValidationError::InvalidSignature); + } + } + + // validate that enough committee members have signed the QC + let num_signatures_in_qc = + u32::try_from(qc.signatures().len()).map_err(|_| QuorumCertificateValidationError::MalformedCertificate)?; + if committee_shard.quorum_threshold() > num_signatures_in_qc { + return Err(QuorumCertificateValidationError::QuorumWasNotReached); + } + + Ok(()) +} diff --git a/dan_layer/indexer_lib/Cargo.toml b/dan_layer/indexer_lib/Cargo.toml index e2ff189e6..f6319f900 100644 --- a/dan_layer/indexer_lib/Cargo.toml +++ b/dan_layer/indexer_lib/Cargo.toml @@ -8,12 +8,15 @@ repository.workspace = true license.workspace = true [dependencies] +tari_common = { workspace = true } tari_dan_common_types = { workspace = true } tari_epoch_manager = { workspace = true } tari_engine_types = { workspace = true } tari_transaction = { workspace = true } tari_template_lib = { workspace = true } tari_validator_node_rpc = { workspace = true } +tari_dan_storage = { workspace = true } +tari_consensus = { workspace = true } async-trait = { workspace = true } log = { workspace = true } diff --git a/dan_layer/indexer_lib/src/error.rs b/dan_layer/indexer_lib/src/error.rs index 6969a73c1..403b55540 100644 --- a/dan_layer/indexer_lib/src/error.rs +++ b/dan_layer/indexer_lib/src/error.rs @@ -1,6 +1,7 @@ // Copyright 2023 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use tari_consensus::quorum_certificate_validations::QuorumCertificateValidationError; use tari_engine_types::substate::SubstateId; use tari_epoch_manager::EpochManagerError; @@ -26,4 +27,8 @@ pub enum IndexerError { FailedToParseTransactionHash(String), #[error("Substate cache operation failed: {0}")] SubstateCacheError(#[from] SubstateCacheError), + #[error("Quorum certificate validation error: {0}")] + QuorumCertificateValidationError(#[from] QuorumCertificateValidationError), + #[error("Missing quorum certificate")] + MissingQuorumCertificate, } diff --git a/dan_layer/indexer_lib/src/substate_scanner.rs b/dan_layer/indexer_lib/src/substate_scanner.rs index 0060063c3..b32122c7f 100644 --- a/dan_layer/indexer_lib/src/substate_scanner.rs +++ b/dan_layer/indexer_lib/src/substate_scanner.rs @@ -22,7 +22,10 @@ use log::*; use rand::{prelude::*, rngs::OsRng}; -use tari_dan_common_types::{NodeAddressable, SubstateAddress}; +use tari_common::configuration::Network; +use tari_consensus::{quorum_certificate_validations::validate_quorum_certificate, traits::VoteSignatureService}; +use tari_dan_common_types::{DerivableFromPublicKey, SubstateAddress}; +use tari_dan_storage::consensus_models::QuorumCertificate; use tari_engine_types::{ events::Event, substate::{SubstateId, SubstateValue}, @@ -45,28 +48,36 @@ use crate::{ const LOG_TARGET: &str = "tari::indexer::dan_layer_scanner"; #[derive(Debug, Clone)] -pub struct SubstateScanner { +pub struct SubstateScanner { committee_provider: TEpochManager, validator_node_client_factory: TVnClient, substate_cache: TSubstateCache, + signing_service: TSignatureService, + network: Network, } -impl SubstateScanner +impl + SubstateScanner where - TAddr: NodeAddressable, + TAddr: DerivableFromPublicKey, TEpochManager: EpochManagerReader, TVnClient: ValidatorNodeClientFactory, TSubstateCache: SubstateCache, + TSignatureService: VoteSignatureService, { pub fn new( committee_provider: TEpochManager, validator_node_client_factory: TVnClient, substate_cache: TSubstateCache, + signing_service: TSignatureService, + network: Network, ) -> Self { Self { committee_provider, validator_node_client_factory, substate_cache, + signing_service, + network, } } @@ -318,9 +329,48 @@ where .get_substate(shard) .await .map_err(|e| IndexerError::ValidatorNodeClientError(e.to_string()))?; + + // validate the qc + // TODO: currently there is no way to check that the QC validates the substate value we receive + // we are only checking that the QC is valid in isolation + match &result { + SubstateResult::DoesNotExist => (), + SubstateResult::Up { + quorum_certificates, .. + } => self.validate_substate_qcs(quorum_certificates, shard).await?, + SubstateResult::Down { + quorum_certificates, .. + } => self.validate_substate_qcs(quorum_certificates, shard).await?, + } + Ok(result) } + /// Validates Quorum Certificates associated with a substate + async fn validate_substate_qcs( + &self, + qcs: &[QuorumCertificate], + shard_id: SubstateAddress, + ) -> Result<(), IndexerError> { + let qc = qcs.last().ok_or(IndexerError::MissingQuorumCertificate)?; + + let committee_shard = self + .committee_provider + .get_committee_shard(qc.epoch(), shard_id) + .await?; + + validate_quorum_certificate( + qc, + &committee_shard, + &self.signing_service, + &self.committee_provider, + self.network, + ) + .await?; + + Ok(()) + } + /// Queries the network to obtain events emitted in a single transaction pub async fn get_events_for_transaction(&self, transaction_id: TransactionId) -> Result, IndexerError> { let substate_address = SubstateId::TransactionReceipt(transaction_id.into_array().into()); diff --git a/dan_layer/indexer_lib/src/transaction_autofiller.rs b/dan_layer/indexer_lib/src/transaction_autofiller.rs index 0b514529e..7045a0190 100644 --- a/dan_layer/indexer_lib/src/transaction_autofiller.rs +++ b/dan_layer/indexer_lib/src/transaction_autofiller.rs @@ -4,7 +4,8 @@ use std::{collections::HashMap, sync::Arc}; use log::*; -use tari_dan_common_types::{NodeAddressable, SubstateAddress}; +use tari_consensus::traits::VoteSignatureService; +use tari_dan_common_types::{DerivableFromPublicKey, SubstateAddress}; use tari_engine_types::{ indexed_value::IndexedValueError, substate::{Substate, SubstateId}, @@ -33,18 +34,22 @@ pub enum TransactionAutofillerError { JoinError(#[from] JoinError), } -pub struct TransactionAutofiller { - substate_scanner: Arc>, +pub struct TransactionAutofiller { + substate_scanner: Arc>, } -impl TransactionAutofiller +impl + TransactionAutofiller where TEpochManager: EpochManagerReader + 'static, TVnClient: ValidatorNodeClientFactory + 'static, - TAddr: NodeAddressable + 'static, + TAddr: DerivableFromPublicKey + 'static, TSubstateCache: SubstateCache + 'static, + TSignatureService: VoteSignatureService + Send + Sync + 'static, { - pub fn new(substate_scanner: Arc>) -> Self { + pub fn new( + substate_scanner: Arc>, + ) -> Self { Self { substate_scanner } } @@ -151,16 +156,17 @@ where } } -pub async fn get_substate_requirement( - substate_scanner: Arc>, +pub async fn get_substate_requirement( + substate_scanner: Arc>, transaction: Arc, req: SubstateRequirement, ) -> Result, IndexerError> where TEpochManager: EpochManagerReader, TVnClient: ValidatorNodeClientFactory, - TAddr: NodeAddressable, + TAddr: DerivableFromPublicKey, TSubstateCache: SubstateCache, + TSignatureService: VoteSignatureService, { let mut version = req.version().unwrap_or(0); loop { @@ -202,16 +208,17 @@ where } } -pub async fn get_substate( - substate_scanner: Arc>, +pub async fn get_substate( + substate_scanner: Arc>, substate_address: SubstateId, version_hint: Option, ) -> Result where TEpochManager: EpochManagerReader, TVnClient: ValidatorNodeClientFactory, - TAddr: NodeAddressable, + TAddr: DerivableFromPublicKey, TSubstateCache: SubstateCache, + TSignatureService: VoteSignatureService, { substate_scanner.get_substate(&substate_address, version_hint).await } diff --git a/dan_layer/validator_node_rpc/src/client.rs b/dan_layer/validator_node_rpc/src/client.rs index e549eeb79..555836b69 100644 --- a/dan_layer/validator_node_rpc/src/client.rs +++ b/dan_layer/validator_node_rpc/src/client.rs @@ -13,7 +13,7 @@ use tari_dan_p2p::{ proto::rpc::{GetTransactionResultRequest, PayloadResultStatus, SubmitTransactionRequest, SubstateStatus}, TariMessagingSpec, }; -use tari_dan_storage::consensus_models::Decision; +use tari_dan_storage::consensus_models::{Decision, QuorumCertificate}; use tari_engine_types::{ commit_result::ExecuteResult, substate::{Substate, SubstateId, SubstateValue}, @@ -68,12 +68,14 @@ pub enum SubstateResult { id: SubstateId, substate: Substate, created_by_tx: TransactionId, + quorum_certificates: Vec, }, Down { id: SubstateId, version: u32, created_by_tx: TransactionId, deleted_by_tx: TransactionId, + quorum_certificates: Vec, }, } @@ -137,11 +139,6 @@ impl ValidatorNodeRpcClient for TariValidatorNodeRpcClient { let tx_hash = resp.created_transaction_hash.try_into().map_err(|_| { @@ -151,11 +148,22 @@ impl ValidatorNodeRpcClient for TariValidatorNodeRpcClient>() + .map_err(|_| { + ValidatorNodeRpcClientError::InvalidResponse(anyhow!( + "Node returned invalid quorum certificates" + )) + })?; Ok(SubstateResult::Up { substate: Substate::new(resp.version, substate), id: SubstateId::from_bytes(&resp.address) .map_err(|e| ValidatorNodeRpcClientError::InvalidResponse(anyhow!(e)))?, created_by_tx: tx_hash, + quorum_certificates, }) }, SubstateStatus::Down => { @@ -169,12 +177,23 @@ impl ValidatorNodeRpcClient for TariValidatorNodeRpcClient>() + .map_err(|_| { + ValidatorNodeRpcClientError::InvalidResponse(anyhow!( + "Node returned invalid quorum certificates" + )) + })?; Ok(SubstateResult::Down { id: SubstateId::from_bytes(&resp.address) .map_err(|e| ValidatorNodeRpcClientError::InvalidResponse(anyhow!(e)))?, version: resp.version, deleted_by_tx, created_by_tx, + quorum_certificates, }) }, SubstateStatus::DoesNotExist => Ok(SubstateResult::DoesNotExist),