2024-05-31 00:52:12 +00:00
//! # Transaction Verifier Service.
//!
//! This module contains the [`TxVerifierService`] which handles consensus validation of transactions.
//!
2023-10-23 18:14:40 +00:00
use std ::{
collections ::HashSet ,
future ::Future ,
2023-11-18 14:00:33 +00:00
ops ::Deref ,
2023-10-23 18:14:40 +00:00
pin ::Pin ,
2024-05-31 00:52:12 +00:00
sync ::{ Arc , Mutex as StdMutex } ,
2023-10-23 18:14:40 +00:00
task ::{ Context , Poll } ,
} ;
use futures ::FutureExt ;
2024-05-31 00:52:12 +00:00
use monero_serai ::{
ringct ::RctType ,
transaction ::{ Input , Timelock , Transaction } ,
} ;
2023-10-23 18:14:40 +00:00
use rayon ::prelude ::* ;
2023-11-05 18:44:41 +00:00
use tower ::{ Service , ServiceExt } ;
2023-10-23 18:14:40 +00:00
use tracing ::instrument ;
2023-10-20 00:04:26 +00:00
2024-05-31 00:52:12 +00:00
use cuprate_consensus_rules ::{
2023-12-16 23:03:02 +00:00
transactions ::{
2024-05-31 00:52:12 +00:00
check_decoy_info , check_transaction_contextual , check_transaction_semantic ,
output_unlocked , TransactionError ,
2023-12-16 23:03:02 +00:00
} ,
ConsensusError , HardFork , TxVersion ,
} ;
2024-05-31 00:52:12 +00:00
use cuprate_helper ::asynch ::rayon_spawn_async ;
2024-06-04 17:19:35 +00:00
use cuprate_types ::blockchain ::{ BCReadRequest , BCResponse } ;
2023-12-16 23:03:02 +00:00
2023-11-05 18:44:41 +00:00
use crate ::{
2024-05-31 00:52:12 +00:00
batch_verifier ::MultiThreadedBatchVerifier ,
transactions ::contextual_data ::{ batch_get_decoy_info , batch_get_ring_member_info } ,
2024-06-04 17:19:35 +00:00
Database , ExtendedConsensusError ,
2023-11-05 18:44:41 +00:00
} ;
2023-10-20 00:04:26 +00:00
2024-02-13 00:51:11 +00:00
pub mod contextual_data ;
2024-05-31 00:52:12 +00:00
/// A struct representing the type of validation that needs to be completed for this transaction.
#[ derive(Debug, Copy, Clone, Eq, PartialEq) ]
enum VerificationNeeded {
/// Both semantic validation and contextual validation are needed.
SemanticAndContextual ,
/// Only contextual validation is needed.
Contextual ,
}
2024-02-13 00:51:11 +00:00
2024-05-31 00:52:12 +00:00
/// Represents if a transaction has been fully validated and under what conditions
/// the transaction is valid in the future.
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
pub enum CachedVerificationState {
/// The transaction has not been validated.
NotVerified ,
/// The transaction is valid* if the block represented by this hash is in the blockchain and the [`HardFork`]
/// is the same.
///
/// *V1 transactions require checks on their ring-length even if this hash is in the blockchain.
ValidAtHashAndHF ( [ u8 ; 32 ] , HardFork ) ,
/// The transaction is valid* if the block represented by this hash is in the blockchain _and_ this
/// given time lock is unlocked. The time lock here will represent the youngest used time based lock
/// (If the transaction uses any time based time locks). This is because time locks are not monotonic
/// so unlocked outputs could become re-locked.
///
/// *V1 transactions require checks on their ring-length even if this hash is in the blockchain.
ValidAtHashAndHFWithTimeBasedLock ( [ u8 ; 32 ] , HardFork , Timelock ) ,
}
impl CachedVerificationState {
/// Returns the block hash this is valid for if in state [`CachedVerificationState::ValidAtHashAndHF`] or [`CachedVerificationState::ValidAtHashAndHFWithTimeBasedLock`].
fn verified_at_block_hash ( & self ) -> Option < [ u8 ; 32 ] > {
match self {
CachedVerificationState ::NotVerified = > None ,
CachedVerificationState ::ValidAtHashAndHF ( hash , _ )
| CachedVerificationState ::ValidAtHashAndHFWithTimeBasedLock ( hash , _ , _ ) = > Some ( * hash ) ,
}
}
2024-02-13 00:51:11 +00:00
}
2023-10-20 00:04:26 +00:00
/// Data needed to verify a transaction.
2023-10-23 18:14:40 +00:00
#[ derive(Debug) ]
2023-10-20 00:04:26 +00:00
pub struct TransactionVerificationData {
2024-05-31 00:52:12 +00:00
/// The transaction we are verifying
2023-10-23 18:14:40 +00:00
pub tx : Transaction ,
2024-05-31 00:52:12 +00:00
/// The [`TxVersion`] of this tx.
2023-10-23 18:14:40 +00:00
pub version : TxVersion ,
2024-05-31 00:52:12 +00:00
/// The serialised transaction.
2023-10-23 18:14:40 +00:00
pub tx_blob : Vec < u8 > ,
2024-05-31 00:52:12 +00:00
/// The weight of the transaction.
2023-10-23 18:14:40 +00:00
pub tx_weight : usize ,
2024-05-31 00:52:12 +00:00
/// The fee this transaction has paid.
2023-10-23 18:14:40 +00:00
pub fee : u64 ,
2024-05-31 00:52:12 +00:00
/// The hash of this transaction.
2023-10-23 18:14:40 +00:00
pub tx_hash : [ u8 ; 32 ] ,
2024-05-31 00:52:12 +00:00
/// The verification state of this transaction.
pub cached_verification_state : StdMutex < CachedVerificationState > ,
2023-10-20 00:04:26 +00:00
}
impl TransactionVerificationData {
2024-05-31 00:52:12 +00:00
/// Creates a new [`TransactionVerificationData`] from the given [`Transaction`].
pub fn new ( tx : Transaction ) -> Result < TransactionVerificationData , ConsensusError > {
2023-12-27 23:50:18 +00:00
let tx_hash = tx . hash ( ) ;
2024-01-08 01:26:44 +00:00
let tx_blob = tx . serialize ( ) ;
// the tx weight is only different from the blobs length for bp(+) txs.
let tx_weight = match tx . rct_signatures . rct_type ( ) {
RctType ::Bulletproofs
| RctType ::BulletproofsCompactAmount
| RctType ::Clsag
| RctType ::BulletproofsPlus = > tx . weight ( ) ,
_ = > tx_blob . len ( ) ,
} ;
2023-12-27 23:50:18 +00:00
2023-10-20 00:04:26 +00:00
Ok ( TransactionVerificationData {
2023-12-27 23:50:18 +00:00
tx_hash ,
2024-01-08 01:26:44 +00:00
tx_blob ,
tx_weight ,
2024-05-31 00:52:12 +00:00
fee : tx . rct_signatures . base . fee ,
cached_verification_state : StdMutex ::new ( CachedVerificationState ::NotVerified ) ,
2023-12-16 23:03:02 +00:00
version : TxVersion ::from_raw ( tx . prefix . version )
. ok_or ( TransactionError ::TransactionVersionInvalid ) ? ,
2023-10-20 00:04:26 +00:00
tx ,
} )
}
2023-10-23 18:14:40 +00:00
}
2023-10-20 00:04:26 +00:00
2024-05-31 00:52:12 +00:00
/// A request to verify a transaction.
2023-10-23 18:14:40 +00:00
pub enum VerifyTxRequest {
2024-05-31 00:52:12 +00:00
/// Verifies a batch of prepared txs.
Prepped {
/// The transactions to verify.
2024-06-04 17:19:35 +00:00
// TODO: Can we use references to remove the Vec? wont play nicely with Service though
txs : Vec < Arc < TransactionVerificationData > > ,
2024-05-31 00:52:12 +00:00
/// The current chain height.
2023-10-23 18:14:40 +00:00
current_chain_height : u64 ,
2024-05-31 00:52:12 +00:00
/// The top block hash.
top_hash : [ u8 ; 32 ] ,
/// The value for time to use to check time locked outputs.
2023-10-24 19:17:16 +00:00
time_for_time_lock : u64 ,
2024-05-31 00:52:12 +00:00
/// The current [`HardFork`]
hf : HardFork ,
} ,
/// Verifies a batch of new txs.
/// Returning [`VerifyTxResponse::OkPrepped`]
New {
/// The transactions to verify.
txs : Vec < Transaction > ,
/// The current chain height.
current_chain_height : u64 ,
/// The top block hash.
top_hash : [ u8 ; 32 ] ,
/// The value for time to use to check time locked outputs.
time_for_time_lock : u64 ,
/// The current [`HardFork`]
2023-10-23 18:14:40 +00:00
hf : HardFork ,
} ,
}
2024-05-31 00:52:12 +00:00
/// A response from a verify transaction request.
#[ derive(Debug) ]
2023-10-23 18:14:40 +00:00
pub enum VerifyTxResponse {
2024-06-04 17:19:35 +00:00
OkPrepped ( Vec < Arc < TransactionVerificationData > > ) ,
2023-10-23 18:14:40 +00:00
Ok ,
}
2024-05-31 00:52:12 +00:00
/// The transaction verifier service.
2023-10-23 18:14:40 +00:00
#[ derive(Clone) ]
2024-05-31 00:52:12 +00:00
pub struct TxVerifierService < D > {
/// The database.
2023-10-23 18:14:40 +00:00
database : D ,
}
impl < D > TxVerifierService < D >
where
D : Database + Clone + Send + 'static ,
D ::Future : Send + 'static ,
{
2024-05-31 00:52:12 +00:00
/// Creates a new [`TxVerifierService`].
2023-10-23 18:14:40 +00:00
pub fn new ( database : D ) -> TxVerifierService < D > {
TxVerifierService { database }
}
}
impl < D > Service < VerifyTxRequest > for TxVerifierService < D >
where
D : Database + Clone + Send + Sync + 'static ,
D ::Future : Send + 'static ,
{
type Response = VerifyTxResponse ;
2023-12-16 23:03:02 +00:00
type Error = ExtendedConsensusError ;
2023-10-23 18:14:40 +00:00
type Future =
Pin < Box < dyn Future < Output = Result < Self ::Response , Self ::Error > > + Send + 'static > > ;
fn poll_ready ( & mut self , cx : & mut Context < '_ > ) -> Poll < Result < ( ) , Self ::Error > > {
self . database . poll_ready ( cx ) . map_err ( Into ::into )
}
fn call ( & mut self , req : VerifyTxRequest ) -> Self ::Future {
let database = self . database . clone ( ) ;
2024-02-13 00:51:11 +00:00
async move {
match req {
2024-05-31 00:52:12 +00:00
VerifyTxRequest ::New {
txs ,
current_chain_height ,
top_hash ,
time_for_time_lock ,
hf ,
} = > {
prep_and_verify_transactions (
database ,
txs ,
current_chain_height ,
top_hash ,
time_for_time_lock ,
hf ,
)
. await
}
VerifyTxRequest ::Prepped {
2024-02-13 00:51:11 +00:00
txs ,
current_chain_height ,
2024-05-31 00:52:12 +00:00
top_hash ,
2024-02-13 00:51:11 +00:00
time_for_time_lock ,
hf ,
} = > {
2024-05-31 00:52:12 +00:00
verify_prepped_transactions (
2024-02-13 00:51:11 +00:00
database ,
2024-06-04 17:19:35 +00:00
& txs ,
2024-02-13 00:51:11 +00:00
current_chain_height ,
2024-05-31 00:52:12 +00:00
top_hash ,
2024-02-13 00:51:11 +00:00
time_for_time_lock ,
hf ,
)
. await
}
}
2023-10-23 18:14:40 +00:00
}
2024-02-13 00:51:11 +00:00
. boxed ( )
2023-10-23 18:14:40 +00:00
}
}
2024-05-31 00:52:12 +00:00
/// Prepares transactions for verification, then verifies them.
async fn prep_and_verify_transactions < D > (
2023-10-23 18:14:40 +00:00
database : D ,
2024-05-31 00:52:12 +00:00
txs : Vec < Transaction > ,
2023-10-23 18:14:40 +00:00
current_chain_height : u64 ,
2024-05-31 00:52:12 +00:00
top_hash : [ u8 ; 32 ] ,
2023-10-24 19:17:16 +00:00
time_for_time_lock : u64 ,
2023-10-23 18:14:40 +00:00
hf : HardFork ,
2023-12-16 23:03:02 +00:00
) -> Result < VerifyTxResponse , ExtendedConsensusError >
2023-10-23 18:14:40 +00:00
where
D : Database + Clone + Sync + Send + 'static ,
{
2024-05-31 00:52:12 +00:00
let span = tracing ::info_span! ( " prep_txs " , amt = txs . len ( ) ) ;
tracing ::debug! ( parent : & span , " prepping transactions for verification. " ) ;
let txs = rayon_spawn_async ( | | {
txs . into_par_iter ( )
. map ( | tx | TransactionVerificationData ::new ( tx ) . map ( Arc ::new ) )
2024-06-04 17:19:35 +00:00
. collect ::< Result < Vec < _ > , _ > > ( )
2024-05-31 00:52:12 +00:00
} )
. await ? ;
verify_prepped_transactions (
database ,
2024-06-04 17:19:35 +00:00
& txs ,
2024-05-31 00:52:12 +00:00
current_chain_height ,
top_hash ,
time_for_time_lock ,
hf ,
2024-02-13 00:51:11 +00:00
)
. await ? ;
2023-10-23 18:14:40 +00:00
2024-05-31 00:52:12 +00:00
Ok ( VerifyTxResponse ::OkPrepped ( txs ) )
}
2023-10-23 18:14:40 +00:00
2024-05-31 00:52:12 +00:00
#[ instrument(name = " verify_txs " , skip_all, fields(amt = txs.len()) level = " info " ) ]
async fn verify_prepped_transactions < D > (
mut database : D ,
2024-06-04 17:19:35 +00:00
txs : & [ Arc < TransactionVerificationData > ] ,
2024-05-31 00:52:12 +00:00
current_chain_height : u64 ,
top_hash : [ u8 ; 32 ] ,
time_for_time_lock : u64 ,
hf : HardFork ,
) -> Result < VerifyTxResponse , ExtendedConsensusError >
where
D : Database + Clone + Sync + Send + 'static ,
{
tracing ::debug! ( " Verifying transactions " ) ;
2023-11-07 23:52:56 +00:00
2024-05-31 00:52:12 +00:00
tracing ::trace! ( " Checking for duplicate key images " ) ;
let mut spent_kis = HashSet ::with_capacity ( txs . len ( ) ) ;
txs . iter ( ) . try_for_each ( | tx | {
tx . tx . prefix . inputs . iter ( ) . try_for_each ( | input | {
if let Input ::ToKey { key_image , .. } = input {
if ! spent_kis . insert ( key_image . compress ( ) . 0 ) {
tracing ::debug! ( " Duplicate key image found in batch. " ) ;
return Err ( ConsensusError ::Transaction ( TransactionError ::KeyImageSpent ) ) ;
}
}
Ok ( ( ) )
2023-10-23 18:14:40 +00:00
} )
2024-05-31 00:52:12 +00:00
} ) ? ;
2023-10-23 18:14:40 +00:00
2024-06-04 17:19:35 +00:00
let BCResponse ::KeyImagesSpent ( kis_spent ) = database
2024-05-31 00:52:12 +00:00
. ready ( )
. await ?
2024-06-04 17:19:35 +00:00
. call ( BCReadRequest ::KeyImagesSpent ( spent_kis ) )
2023-11-05 18:44:41 +00:00
. await ?
else {
panic! ( " Database sent incorrect response! " ) ;
} ;
if kis_spent {
2024-05-31 00:52:12 +00:00
tracing ::debug! ( " One or more key images in batch already spent. " ) ;
2023-12-16 23:03:02 +00:00
Err ( ConsensusError ::Transaction ( TransactionError ::KeyImageSpent ) ) ? ;
2023-11-05 18:44:41 +00:00
}
2024-05-31 00:52:12 +00:00
let mut verified_at_block_hashes = txs
. iter ( )
. filter_map ( | txs | {
txs . cached_verification_state
. lock ( )
. unwrap ( )
. verified_at_block_hash ( )
} )
. collect ::< HashSet < _ > > ( ) ;
tracing ::trace! (
" Verified at hashes len: {}. " ,
verified_at_block_hashes . len ( )
) ;
if ! verified_at_block_hashes . is_empty ( ) {
tracing ::trace! ( " Filtering block hashes not in the main chain. " ) ;
2024-06-04 17:19:35 +00:00
let BCResponse ::FilterUnknownHashes ( known_hashes ) = database
2024-05-31 00:52:12 +00:00
. ready ( )
. await ?
2024-06-04 17:19:35 +00:00
. call ( BCReadRequest ::FilterUnknownHashes ( verified_at_block_hashes ) )
2024-05-31 00:52:12 +00:00
. await ?
else {
panic! ( " Database returned wrong response! " ) ;
} ;
verified_at_block_hashes = known_hashes ;
}
let ( txs_needing_full_verification , txs_needing_partial_verification ) =
transactions_needing_verification (
txs ,
verified_at_block_hashes ,
& hf ,
current_chain_height ,
time_for_time_lock ,
) ? ;
futures ::try_join! (
verify_transactions_decoy_info ( txs_needing_partial_verification , hf , database . clone ( ) ) ,
verify_transactions (
txs_needing_full_verification ,
current_chain_height ,
top_hash ,
time_for_time_lock ,
hf ,
database
)
) ? ;
2023-10-23 18:14:40 +00:00
Ok ( VerifyTxResponse ::Ok )
}
2024-05-31 00:52:12 +00:00
#[ allow(clippy::type_complexity) ] // I don't think the return is too complex
fn transactions_needing_verification (
2024-06-04 17:19:35 +00:00
txs : & [ Arc < TransactionVerificationData > ] ,
2024-05-31 00:52:12 +00:00
hashes_in_main_chain : HashSet < [ u8 ; 32 ] > ,
current_hf : & HardFork ,
2023-10-23 18:14:40 +00:00
current_chain_height : u64 ,
2023-10-24 19:17:16 +00:00
time_for_time_lock : u64 ,
2024-05-31 00:52:12 +00:00
) -> Result <
(
Vec < ( Arc < TransactionVerificationData > , VerificationNeeded ) > ,
Vec < Arc < TransactionVerificationData > > ,
) ,
ConsensusError ,
> {
// txs needing full validation: semantic and/or contextual
let mut full_validation_transactions = Vec ::new ( ) ;
// txs needing partial _contextual_ validation, not semantic.
let mut partial_validation_transactions = Vec ::new ( ) ;
for tx in txs . iter ( ) {
let guard = tx . cached_verification_state . lock ( ) . unwrap ( ) ;
match guard . deref ( ) {
CachedVerificationState ::NotVerified = > {
drop ( guard ) ;
full_validation_transactions
. push ( ( tx . clone ( ) , VerificationNeeded ::SemanticAndContextual ) ) ;
continue ;
}
CachedVerificationState ::ValidAtHashAndHF ( hash , hf ) = > {
if current_hf ! = hf {
drop ( guard ) ;
full_validation_transactions
. push ( ( tx . clone ( ) , VerificationNeeded ::SemanticAndContextual ) ) ;
continue ;
}
if ! hashes_in_main_chain . contains ( hash ) {
drop ( guard ) ;
full_validation_transactions . push ( ( tx . clone ( ) , VerificationNeeded ::Contextual ) ) ;
continue ;
}
}
CachedVerificationState ::ValidAtHashAndHFWithTimeBasedLock ( hash , hf , lock ) = > {
if current_hf ! = hf {
drop ( guard ) ;
full_validation_transactions
. push ( ( tx . clone ( ) , VerificationNeeded ::SemanticAndContextual ) ) ;
continue ;
}
if ! hashes_in_main_chain . contains ( hash ) {
drop ( guard ) ;
full_validation_transactions . push ( ( tx . clone ( ) , VerificationNeeded ::Contextual ) ) ;
continue ;
}
// If the time lock is still locked then the transaction is invalid.
if ! output_unlocked ( lock , current_chain_height , time_for_time_lock , hf ) {
return Err ( ConsensusError ::Transaction (
TransactionError ::OneOrMoreRingMembersLocked ,
) ) ;
}
}
}
if tx . version = = TxVersion ::RingSignatures {
drop ( guard ) ;
partial_validation_transactions . push ( tx . clone ( ) ) ;
continue ;
}
}
Ok ( (
full_validation_transactions ,
partial_validation_transactions ,
) )
}
async fn verify_transactions_decoy_info < D > (
txs : Vec < Arc < TransactionVerificationData > > ,
2023-10-23 18:14:40 +00:00
hf : HardFork ,
2024-05-31 00:52:12 +00:00
database : D ,
) -> Result < ( ) , ExtendedConsensusError >
where
D : Database + Clone + Sync + Send + 'static ,
{
batch_get_decoy_info ( & txs , hf , database )
. await ?
. try_for_each ( | decoy_info | decoy_info . and_then ( | di | Ok ( check_decoy_info ( & di , & hf ) ? ) ) ) ? ;
2023-10-23 18:14:40 +00:00
2024-05-31 00:52:12 +00:00
Ok ( ( ) )
}
2023-10-23 18:14:40 +00:00
2024-05-31 00:52:12 +00:00
async fn verify_transactions < D > (
txs : Vec < ( Arc < TransactionVerificationData > , VerificationNeeded ) > ,
current_chain_height : u64 ,
top_hash : [ u8 ; 32 ] ,
current_time_lock_timestamp : u64 ,
hf : HardFork ,
database : D ,
) -> Result < ( ) , ExtendedConsensusError >
where
D : Database + Clone + Sync + Send + 'static ,
{
let txs_ring_member_info =
batch_get_ring_member_info ( txs . iter ( ) . map ( | ( tx , _ ) | tx ) , & hf , database ) . await ? ;
rayon_spawn_async ( move | | {
2024-06-07 12:34:53 +00:00
let batch_verifier = MultiThreadedBatchVerifier ::new ( rayon ::current_num_threads ( ) ) ;
2024-05-31 00:52:12 +00:00
txs . par_iter ( )
. zip ( txs_ring_member_info . par_iter ( ) )
. try_for_each ( | ( ( tx , verification_needed ) , ring ) | {
// do semantic validation if needed.
if * verification_needed = = VerificationNeeded ::SemanticAndContextual {
2024-06-07 12:34:53 +00:00
let fee = check_transaction_semantic (
& tx . tx ,
tx . tx_blob . len ( ) ,
tx . tx_weight ,
& tx . tx_hash ,
& hf ,
& batch_verifier ,
) ? ;
// make sure monero-serai calculated the same fee.
assert_eq! ( fee , tx . fee ) ;
2024-05-31 00:52:12 +00:00
}
// Both variants of `VerificationNeeded` require contextual validation.
check_transaction_contextual (
& tx . tx ,
ring ,
current_chain_height ,
current_time_lock_timestamp ,
& hf ,
) ? ;
Ok ::< _ , ConsensusError > ( ( ) )
} ) ? ;
2024-06-07 12:34:53 +00:00
if ! batch_verifier . verify ( ) {
2024-05-31 00:52:12 +00:00
return Err ( ExtendedConsensusError ::OneOrMoreBatchVerificationStatementsInvalid ) ;
}
txs . iter ( )
. zip ( txs_ring_member_info )
. for_each ( | ( ( tx , _ ) , ring ) | {
if ring . time_locked_outs . is_empty ( ) {
* tx . cached_verification_state . lock ( ) . unwrap ( ) =
CachedVerificationState ::ValidAtHashAndHF ( top_hash , hf ) ;
} else {
let youngest_timebased_lock = ring
. time_locked_outs
. iter ( )
. filter_map ( | lock | match lock {
Timelock ::Time ( time ) = > Some ( * time ) ,
_ = > None ,
} )
. min ( ) ;
* tx . cached_verification_state . lock ( ) . unwrap ( ) =
if let Some ( time ) = youngest_timebased_lock {
CachedVerificationState ::ValidAtHashAndHFWithTimeBasedLock (
top_hash ,
hf ,
Timelock ::Time ( time ) ,
)
} else {
CachedVerificationState ::ValidAtHashAndHF ( top_hash , hf )
} ;
}
} ) ;
Ok ( ( ) )
} )
. await ? ;
2023-10-23 18:14:40 +00:00
Ok ( ( ) )
}