diff --git a/consensus/rules/src/lib.rs b/consensus/rules/src/lib.rs index 6c7d3246..25a9fe7f 100644 --- a/consensus/rules/src/lib.rs +++ b/consensus/rules/src/lib.rs @@ -5,7 +5,6 @@ mod decomposed_amount; pub mod genesis; pub mod hard_forks; pub mod miner_tx; -pub mod signatures; pub mod transactions; pub use decomposed_amount::is_decomposed_amount; @@ -18,8 +17,6 @@ pub enum ConsensusError { Block(#[from] blocks::BlockError), #[error("Transaction error: {0}")] Transaction(#[from] transactions::TransactionError), - #[error("Signatures error: {0}")] - Signatures(#[from] signatures::SignatureError), } /// Checks that a point is canonically encoded. diff --git a/consensus/rules/src/signatures.rs b/consensus/rules/src/signatures.rs deleted file mode 100644 index ed54e55e..00000000 --- a/consensus/rules/src/signatures.rs +++ /dev/null @@ -1,25 +0,0 @@ -use monero_serai::transaction::Transaction; - -use crate::transactions::Rings; - -mod ring_signatures; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] -pub enum SignatureError { - #[error("Number of signatures is different to the amount required.")] - MismatchSignatureSize, - #[error("The signature is incorrect.")] - IncorrectSignature, -} - -pub fn verify_contextual_signatures(tx: &Transaction, rings: &Rings) -> Result<(), SignatureError> { - match rings { - Rings::Legacy(_) => ring_signatures::verify_inputs_signatures( - &tx.prefix.inputs, - &tx.signatures, - rings, - &tx.signature_hash(), - ), - _ => panic!("TODO: RCT"), - } -} diff --git a/consensus/rules/src/transactions.rs b/consensus/rules/src/transactions.rs index 418f0b97..4ae1b53d 100644 --- a/consensus/rules/src/transactions.rs +++ b/consensus/rules/src/transactions.rs @@ -1,12 +1,16 @@ use std::{cmp::Ordering, collections::HashSet, sync::Arc}; -use monero_serai::transaction::{Input, Output, Timelock}; +use monero_serai::transaction::{Input, Output, Timelock, Transaction}; +use multiexp::BatchVerifier; use crate::{check_point_canonically_encoded, is_decomposed_amount, HardFork}; mod contextual_data; mod ring_ct; +mod ring_signatures; + pub use contextual_data::*; +pub use ring_ct::RingCTError; #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] pub enum TransactionError { @@ -48,6 +52,12 @@ pub enum TransactionError { NoInputs, #[error("Ring member not in database")] RingMemberNotFound, + //-------------------------------------------------------- Ring Signatures + #[error("Ring signature incorrect.")] + RingSignatureIncorrect, + //-------------------------------------------------------- RingCT + #[error("RingCT Error: {0}.")] + RingCTError(#[from] ring_ct::RingCTError), } #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] @@ -148,7 +158,7 @@ fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result sum_outputs_v1(outputs, hf), - _ => todo!("RingCT"), + TxVersion::RingCT => Ok(0), // RCT outputs are checked to be zero in RCT checks. } } //----------------------------------------------------------------------------------------------------------- TIME LOCKS -/// Checks all the time locks are unlocked. -/// -/// `current_time_lock_timestamp` must be: https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time -/// -/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time -pub fn check_all_time_locks( - time_locks: &[Timelock], - current_chain_height: u64, - current_time_lock_timestamp: u64, - hf: &HardFork, -) -> Result<(), TransactionError> { - time_locks.iter().try_for_each(|time_lock| { - if !output_unlocked( - time_lock, - current_chain_height, - current_time_lock_timestamp, - hf, - ) { - Err(TransactionError::OneOrMoreDecoysLocked) - } else { - Ok(()) - } - }) -} - /// Checks if an outputs unlock time has passed. /// /// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time @@ -229,6 +214,31 @@ fn check_timestamp_time_lock( current_time_lock_timestamp + hf.block_time().as_secs() >= unlock_timestamp } +/// Checks all the time locks are unlocked. +/// +/// `current_time_lock_timestamp` must be: https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time +/// +/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time +fn check_all_time_locks( + time_locks: &[Timelock], + current_chain_height: u64, + current_time_lock_timestamp: u64, + hf: &HardFork, +) -> Result<(), TransactionError> { + time_locks.iter().try_for_each(|time_lock| { + if !output_unlocked( + time_lock, + current_chain_height, + current_time_lock_timestamp, + hf, + ) { + Err(TransactionError::OneOrMoreDecoysLocked) + } else { + Ok(()) + } + }) +} + //----------------------------------------------------------------------------------------------------------- INPUTS /// Checks the decoys are allowed. @@ -273,7 +283,7 @@ fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), Transac /// /// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-key-image /// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#torsion-free-key-image -pub(crate) fn check_key_images( +fn check_key_images( input: &Input, spent_kis: &mut HashSet<[u8; 32]>, ) -> Result<(), TransactionError> { @@ -351,7 +361,7 @@ fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), Transactio if hf >= &HardFork::V7 { for inps in inputs.windows(2) { match get_ki(&inps[0])?.cmp(&get_ki(&inps[1])?) { - Ordering::Less => (), + Ordering::Greater => (), _ => return Err(TransactionError::InputsAreNotOrdered), } } @@ -383,7 +393,7 @@ fn check_10_block_lock( /// Sums the inputs checking for overflow. /// /// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow -fn sum_inputs_v1(inputs: &[Input]) -> Result { +fn sum_inputs_check_overflow(inputs: &[Input]) -> Result { let mut sum: u64 = 0; for inp in inputs { match inp { @@ -399,22 +409,30 @@ fn sum_inputs_v1(inputs: &[Input]) -> Result { Ok(sum) } -/// Checks all input consensus rules. -/// -/// TODO: list rules. -/// -pub fn check_inputs( - inputs: &[Input], - tx_ring_members_info: &TxRingMembersInfo, - current_chain_height: u64, - hf: &HardFork, - tx_version: &TxVersion, - spent_kis: Arc>>, -) -> Result { +fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result { if inputs.is_empty() { return Err(TransactionError::NoInputs); } + for input in inputs { + check_input_type(input)?; + check_input_has_decoys(input)?; + + check_ring_members_unique(input, hf)?; + } + + check_inputs_sorted(inputs, hf)?; + + sum_inputs_check_overflow(inputs) +} + +fn check_inputs_contextual( + inputs: &[Input], + tx_ring_members_info: &TxRingMembersInfo, + current_chain_height: u64, + hf: &HardFork, + spent_kis: Arc>>, +) -> Result<(), TransactionError> { check_10_block_lock( tx_ring_members_info.youngest_used_out_height, current_chain_height, @@ -427,32 +445,20 @@ pub fn check_inputs( assert_eq!(hf, &HardFork::V1); } + let mut spent_kis_lock = spent_kis.lock().unwrap(); for input in inputs { - check_input_type(input)?; - check_input_has_decoys(input)?; - - check_ring_members_unique(input, hf)?; - - let mut spent_kis_lock = spent_kis.lock().unwrap(); check_key_images(input, &mut spent_kis_lock)?; - // Adding this here for clarity so we don't add more work here while the mutex guard is still - // in scope. - drop(spent_kis_lock); } + drop(spent_kis_lock); - check_inputs_sorted(inputs, hf)?; - - match tx_version { - TxVersion::RingSignatures => sum_inputs_v1(inputs), - _ => panic!("TODO: RCT"), - } + Ok(()) } /// Checks the version is in the allowed range. /// /// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#version -pub fn check_tx_version( - decoy_info: &Option, +fn check_tx_version( + decoy_info: &Option, version: &TxVersion, hf: &HardFork, ) -> Result<(), TransactionError> { @@ -492,3 +498,75 @@ fn min_tx_version(hf: &HardFork) -> TxVersion { TxVersion::RingSignatures } } + +pub fn check_transaction_semantic( + tx: &Transaction, + tx_hash: &[u8; 32], + hf: &HardFork, + verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>, +) -> Result { + let tx_version = TxVersion::from_raw(tx.prefix.version) + .ok_or(TransactionError::TransactionVersionInvalid)?; + + let outputs_sum = check_outputs_semantics(&tx.prefix.outputs, hf, &tx_version)?; + let inputs_sum = check_inputs_semantics(&tx.prefix.inputs, hf)?; + + let fee = match tx_version { + TxVersion::RingSignatures => { + if outputs_sum >= inputs_sum { + Err(TransactionError::OutputsTooHigh)?; + } + inputs_sum - outputs_sum + } + TxVersion::RingCT => { + ring_ct::ring_ct_semantic_checks(tx, tx_hash, verifier, hf)?; + + tx.rct_signatures.base.fee + } + }; + + Ok(fee) +} + +pub fn check_transaction_contextual( + tx: &Transaction, + tx_ring_members_info: &TxRingMembersInfo, + current_chain_height: u64, + current_time_lock_timestamp: u64, + hf: &HardFork, + spent_kis: Arc>>, +) -> Result<(), TransactionError> { + let tx_version = TxVersion::from_raw(tx.prefix.version) + .ok_or(TransactionError::TransactionVersionInvalid)?; + + check_inputs_contextual( + &tx.prefix.inputs, + tx_ring_members_info, + current_chain_height, + hf, + spent_kis, + )?; + check_tx_version(&tx_ring_members_info.decoy_info, &tx_version, hf)?; + + check_all_time_locks( + &tx_ring_members_info.time_locked_outs, + current_chain_height, + current_time_lock_timestamp, + hf, + )?; + + match tx_version { + TxVersion::RingSignatures => ring_signatures::verify_inputs_signatures( + &tx.prefix.inputs, + &tx.signatures, + &tx_ring_members_info.rings, + &tx.signature_hash(), + ), + TxVersion::RingCT => Ok(ring_ct::check_input_signatures( + &tx.signature_hash(), + &tx.prefix.inputs, + &tx.rct_signatures, + &tx_ring_members_info.rings, + )?), + } +} diff --git a/consensus/rules/src/transactions/contextual_data.rs b/consensus/rules/src/transactions/contextual_data.rs index 5d0fd70c..2d50a19a 100644 --- a/consensus/rules/src/transactions/contextual_data.rs +++ b/consensus/rules/src/transactions/contextual_data.rs @@ -278,7 +278,7 @@ impl DecoyInfo { /// **There are exceptions to this always being the minimum decoys** /// /// https://cuprate.github.io/monero-book/consensus_rules/transactions/decoys.html#minimum-amount-of-decoys -pub fn minimum_decoys(hf: &HardFork) -> usize { +pub(crate) fn minimum_decoys(hf: &HardFork) -> usize { use HardFork as HF; match hf { HF::V1 => panic!("hard-fork 1 does not use these rules!"), diff --git a/consensus/rules/src/transactions/ring_ct.rs b/consensus/rules/src/transactions/ring_ct.rs index c6d93691..56d26869 100644 --- a/consensus/rules/src/transactions/ring_ct.rs +++ b/consensus/rules/src/transactions/ring_ct.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use curve25519_dalek::{EdwardsPoint, Scalar}; use hex_literal::hex; use monero_serai::{ @@ -8,7 +6,7 @@ use monero_serai::{ mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix}, RctPrunable, RctSignatures, RctType, }, - transaction::{Input, Output}, + transaction::{Input, Output, Transaction}, H, }; use multiexp::BatchVerifier; @@ -135,11 +133,28 @@ fn check_output_range_proofs( } } +pub fn ring_ct_semantic_checks( + tx: &Transaction, + tx_hash: &[u8; 32], + verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>, + hf: &HardFork, +) -> Result<(), RingCTError> { + check_output_amount(&tx.prefix.outputs)?; + check_rct_type(&tx.rct_signatures.rct_type(), *hf, tx_hash)?; + check_output_range_proofs(&tx.rct_signatures, verifier)?; + + if tx.rct_signatures.rct_type() != RctType::MlsagAggregate { + simple_type_balances(&tx.rct_signatures)?; + } + + Ok(()) +} + /// Check the input signatures, MLSAG, CLSAG. /// /// https://monero-book.cuprate.org/consensus_rules/ring_ct/mlsag.html /// https://monero-book.cuprate.org/consensus_rules/ring_ct/clsag.html -fn check_input_signatures( +pub fn check_input_signatures( msg: &[u8; 32], inputs: &[Input], rct_sig: &RctSignatures, diff --git a/consensus/rules/src/signatures/ring_signatures.rs b/consensus/rules/src/transactions/ring_signatures.rs similarity index 89% rename from consensus/rules/src/signatures/ring_signatures.rs rename to consensus/rules/src/transactions/ring_signatures.rs index 1a1fb1c4..f0e4a34b 100644 --- a/consensus/rules/src/signatures/ring_signatures.rs +++ b/consensus/rules/src/transactions/ring_signatures.rs @@ -11,7 +11,7 @@ use monero_serai::{ring_signatures::RingSignature, transaction::Input}; #[cfg(feature = "rayon")] use rayon::prelude::*; -use super::{Rings, SignatureError}; +use super::{Rings, TransactionError}; use crate::try_par_iter; /// Verifies the ring signature. @@ -23,12 +23,12 @@ pub fn verify_inputs_signatures( signatures: &[RingSignature], rings: &Rings, tx_sig_hash: &[u8; 32], -) -> Result<(), SignatureError> { +) -> Result<(), TransactionError> { match rings { Rings::Legacy(rings) => { // rings.len() != inputs.len() can't happen but check any way. if signatures.len() != inputs.len() || rings.len() != inputs.len() { - return Err(SignatureError::MismatchSignatureSize); + return Err(TransactionError::RingSignatureIncorrect); } try_par_iter(inputs) @@ -40,7 +40,7 @@ pub fn verify_inputs_signatures( }; if !sig.verify(tx_sig_hash, ring, key_image) { - return Err(SignatureError::IncorrectSignature); + return Err(TransactionError::RingSignatureIncorrect); } Ok(()) })?; diff --git a/consensus/src/batch_verifier.rs b/consensus/src/batch_verifier.rs index 0d4067be..57fb997d 100644 --- a/consensus/src/batch_verifier.rs +++ b/consensus/src/batch_verifier.rs @@ -8,7 +8,7 @@ use crate::ConsensusError; /// A multi threaded batch verifier. pub struct MultiThreadedBatchVerifier { - internal: ThreadLocal>>, + internal: ThreadLocal>>, } impl MultiThreadedBatchVerifier { @@ -19,12 +19,12 @@ impl MultiThreadedBatchVerifier { } } - pub fn queue_statement( + pub fn queue_statement( &self, stmt: impl FnOnce( - &mut InternalBatchVerifier, - ) -> Result<(), ConsensusError>, - ) -> Result<(), ConsensusError> { + &mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>, + ) -> Result, + ) -> Result { let verifier_cell = self .internal .get_or(|| UnsafeCell::new(InternalBatchVerifier::new(0))); diff --git a/consensus/src/bin/scan_chain.rs b/consensus/src/bin/scan_chain.rs index 174b2c21..980a947e 100644 --- a/consensus/src/bin/scan_chain.rs +++ b/consensus/src/bin/scan_chain.rs @@ -8,6 +8,7 @@ use futures::{ SinkExt, StreamExt, }; use monero_serai::block::Block; +use rayon::prelude::*; use tokio::sync::RwLock; use tower::{Service, ServiceExt}; use tracing::level_filters::LevelFilter; @@ -21,13 +22,13 @@ use cuprate_consensus::{ }, initialize_blockchain_context, initialize_verifier, rpc::{cache::ScanningCache, init_rpc_load_balancer, RpcConfig}, - Database, DatabaseRequest, DatabaseResponse, VerifiedBlockInformation, VerifyBlockRequest, - VerifyBlockResponse, + Database, DatabaseRequest, DatabaseResponse, PrePreparedBlock, VerifiedBlockInformation, + VerifyBlockRequest, VerifyBlockResponse, }; mod tx_pool; -const MAX_BLOCKS_IN_RANGE: u64 = 1000; +const MAX_BLOCKS_IN_RANGE: u64 = 500; const MAX_BLOCKS_HEADERS_IN_RANGE: u64 = 1000; /// Calls for a batch of blocks, returning the response and the time it took. @@ -166,21 +167,33 @@ where let (block_tx, mut incoming_blocks) = mpsc::channel(3); + let (mut prepped_blocks_tx, mut prepped_blocks_rx) = mpsc::channel(3); + tokio::spawn(async move { call_blocks(new_tx_chan, block_tx, start_height, chain_height, database).await }); - while let Some(incoming_blocks) = incoming_blocks.next().await { + tokio::spawn(async move { + while let Some(blocks) = incoming_blocks.next().await { + let blocks = rayon_spawn_async(|| { + blocks + .into_par_iter() + .map(|block| PrePreparedBlock::new(block).unwrap()) + .collect::>() + }) + .await; + prepped_blocks_tx.send(blocks).await.unwrap(); + } + }); + + while let Some(incoming_blocks) = prepped_blocks_rx.next().await { let mut height; for block in incoming_blocks { let VerifyBlockResponse::MainChain(verified_block_info) = block_verifier .ready() .await? - .call(VerifyBlockRequest::MainChain(block)) - .await? - else { - panic!("Block verifier sent incorrect response!"); - }; + .call(VerifyBlockRequest::MainChainPrepared(block)) + .await?; height = verified_block_info.height; @@ -316,3 +329,15 @@ async fn main() { .await .unwrap(); } + +async fn rayon_spawn_async(f: F) -> R +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + let (tx, rx) = tokio::sync::oneshot::channel(); + rayon::spawn(|| { + let _ = tx.send(f()); + }); + rx.await.expect("The sender must not be dropped") +} diff --git a/consensus/src/block.rs b/consensus/src/block.rs index 067bac88..fb96690e 100644 --- a/consensus/src/block.rs +++ b/consensus/src/block.rs @@ -7,8 +7,11 @@ use std::{ use futures::FutureExt; use monero_serai::block::Block; +use monero_serai::transaction::Input; use tower::{Service, ServiceExt}; +use monero_consensus::blocks::BlockError; +use monero_consensus::miner_tx::MinerTxError; use monero_consensus::{ blocks::{calculate_pow_hash, check_block, check_block_pow}, ConsensusError, HardFork, @@ -35,6 +38,31 @@ pub struct PrePreparedBlock { pub miner_tx_weight: usize, } +impl PrePreparedBlock { + pub fn new(block: Block) -> Result { + let (hf_version, hf_vote) = + HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?; + + let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else { + Err(ConsensusError::Block(BlockError::MinerTxError( + MinerTxError::InputNotOfTypeGen, + )))? + }; + + Ok(PrePreparedBlock { + block_blob: block.serialize(), + hf_vote, + hf_version, + + block_hash: block.hash(), + pow_hash: calculate_pow_hash(&block.serialize_hashable(), *height, &hf_vote)?, + + miner_tx_weight: block.miner_tx.weight(), + block, + }) + } +} + #[derive(Debug)] pub struct VerifiedBlockInformation { pub block: Block, @@ -51,12 +79,11 @@ pub struct VerifiedBlockInformation { pub enum VerifyBlockRequest { MainChain(Block), + MainChainPrepared(PrePreparedBlock), } pub enum VerifyBlockResponse { MainChain(VerifiedBlockInformation), - - BatchSetup(Vec), } // TODO: it is probably a bad idea for this to derive clone, if 2 places (RPC, P2P) receive valid but different blocks @@ -138,12 +165,98 @@ where VerifyBlockRequest::MainChain(block) => { verify_main_chain_block(block, context_svc, tx_verifier_svc, tx_pool).await } + VerifyBlockRequest::MainChainPrepared(prepped_block) => { + verify_main_chain_block_prepared( + prepped_block, + context_svc, + tx_verifier_svc, + tx_pool, + ) + .await + } } } .boxed() } } +async fn verify_main_chain_block_prepared( + prepped_block: PrePreparedBlock, + context_svc: C, + tx_verifier_svc: TxV, + tx_pool: TxP, +) -> Result +where + C: Service< + BlockChainContextRequest, + Response = BlockChainContextResponse, + Error = tower::BoxError, + > + Send + + 'static, + C::Future: Send + 'static, + TxV: Service, + TxP: Service + + Clone + + Send + + 'static, +{ + tracing::debug!("getting blockchain context"); + let BlockChainContextResponse::Context(checked_context) = context_svc + .oneshot(BlockChainContextRequest::Get) + .await + .map_err(Into::::into)? + else { + panic!("Context service returned wrong response!"); + }; + + let context = checked_context.unchecked_blockchain_context().clone(); + + tracing::debug!("got blockchain context: {:?}", context); + + let TxPoolResponse::Transactions(txs) = tx_pool + .oneshot(TxPoolRequest::Transactions(prepped_block.block.txs.clone())) + .await?; + + tx_verifier_svc + .oneshot(VerifyTxRequest::Block { + txs: txs.clone(), + current_chain_height: context.chain_height, + time_for_time_lock: context.current_adjusted_timestamp_for_time_lock(), + hf: context.current_hf, + re_org_token: context.re_org_token.clone(), + }) + .await?; + + let block_weight = + prepped_block.miner_tx_weight + txs.iter().map(|tx| tx.tx_weight).sum::(); + let total_fees = txs.iter().map(|tx| tx.fee).sum::(); + + let (hf_vote, generated_coins) = check_block( + &prepped_block.block, + total_fees, + block_weight, + prepped_block.block_blob.len(), + &context.context_to_verify_block, + ) + .map_err(ConsensusError::Block)?; + + check_block_pow(&prepped_block.pow_hash, context.next_difficulty) + .map_err(ConsensusError::Block)?; + + Ok(VerifyBlockResponse::MainChain(VerifiedBlockInformation { + block_hash: prepped_block.block_hash, + block: prepped_block.block, + txs, + pow_hash: prepped_block.pow_hash, + generated_coins, + weight: block_weight, + height: context.chain_height, + long_term_weight: context.next_block_long_term_weight(block_weight), + hf_vote, + cumulative_difficulty: context.cumulative_difficulty + context.next_difficulty, + })) +} + async fn verify_main_chain_block( block: Block, context_svc: C, diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 0bc7cc2b..b41f7edd 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -7,6 +7,7 @@ use std::{ use monero_consensus::{transactions::OutputOnChain, ConsensusError, HardFork}; //mod batch_verifier; +mod batch_verifier; pub mod block; pub mod context; mod helper; diff --git a/consensus/src/rpc/cache.rs b/consensus/src/rpc/cache.rs index c226fc35..e808e08f 100644 --- a/consensus/src/rpc/cache.rs +++ b/consensus/src/rpc/cache.rs @@ -45,7 +45,8 @@ impl ScanningCache { pub fn load(file: &Path) -> Result { let mut file = std::fs::OpenOptions::new().read(true).open(file)?; - Ok(borsh::from_reader(&mut file)?) + let data: ScanningCache = borsh::from_reader(&mut file)?; + Ok(data) } pub fn add_new_block_data( diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs index 917294d1..d31d0471 100644 --- a/consensus/src/transactions.rs +++ b/consensus/src/transactions.rs @@ -14,17 +14,16 @@ use tower::{Service, ServiceExt}; use tracing::instrument; use monero_consensus::{ - signatures::verify_contextual_signatures, transactions::{ - check_all_time_locks, check_inputs, check_outputs, check_tx_version, TransactionError, + check_transaction_contextual, check_transaction_semantic, RingCTError, TransactionError, TxRingMembersInfo, }, ConsensusError, HardFork, TxVersion, }; use crate::{ - context::ReOrgToken, helper::rayon_spawn_async, Database, DatabaseRequest, DatabaseResponse, - ExtendedConsensusError, + batch_verifier::MultiThreadedBatchVerifier, context::ReOrgToken, helper::rayon_spawn_async, + Database, DatabaseRequest, DatabaseResponse, ExtendedConsensusError, }; mod contextual_data; @@ -45,12 +44,23 @@ pub struct TransactionVerificationData { } impl TransactionVerificationData { - pub fn new(tx: Transaction) -> Result { + pub fn new( + tx: Transaction, + hf: &HardFork, + verifier: Arc, + ) -> Result { + let tx_hash = tx.hash(); + + let fee = verifier.queue_statement(|verifier| { + check_transaction_semantic(&tx, &tx_hash, hf, verifier) + .map_err(ConsensusError::Transaction) + })?; + Ok(TransactionVerificationData { - tx_hash: tx.hash(), + tx_hash, tx_blob: tx.serialize(), tx_weight: tx.weight(), - fee: tx.rct_signatures.base.fee, + fee, rings_member_info: std::sync::Mutex::new(None), version: TxVersion::from_raw(tx.prefix.version) .ok_or(TransactionError::TransactionVersionInvalid)?, @@ -68,7 +78,7 @@ pub enum VerifyTxRequest { hf: HardFork, re_org_token: ReOrgToken, }, - /// Batches the setup of [`TransactionVerificationData`], does *minimal* verification, you need to call [`VerifyTxRequest::Block`] + /// Batches the setup of [`TransactionVerificationData`], does *some* verification, you need to call [`VerifyTxRequest::Block`] /// with the returned data. BatchSetup { txs: Vec, @@ -148,14 +158,29 @@ async fn batch_setup_transactions( where D: Database + Clone + Sync + Send + 'static, { + let batch_verifier = Arc::new(MultiThreadedBatchVerifier::new(rayon::current_num_threads())); + + let cloned_verifier = batch_verifier.clone(); // Move out of the async runtime and use rayon to parallelize the serialisation and hashing of the txs. - let txs = rayon_spawn_async(|| { + let txs = rayon_spawn_async(move || { txs.into_par_iter() - .map(|tx| Ok(Arc::new(TransactionVerificationData::new(tx)?))) + .map(|tx| { + Ok(Arc::new(TransactionVerificationData::new( + tx, + &hf, + cloned_verifier.clone(), + )?)) + }) .collect::, ConsensusError>>() }) .await?; + if !Arc::into_inner(batch_verifier).unwrap().verify() { + Err(ConsensusError::Transaction(TransactionError::RingCTError( + RingCTError::BulletproofsRangeInvalid, + )))? + } + contextual_data::batch_fill_ring_member_info(&txs, &hf, re_org_token, database).await?; Ok(VerifyTxResponse::BatchSetupOk(txs)) @@ -223,44 +248,20 @@ fn verify_transaction_for_block( hex::encode(tx_verification_data.tx_hash) ); - let tx_version = &tx_verification_data.version; - let rings_member_info_lock = tx_verification_data.rings_member_info.lock().unwrap(); let rings_member_info = match rings_member_info_lock.deref() { Some(rings_member_info) => rings_member_info, None => panic!("rings_member_info needs to be set to be able to verify!"), }; - check_tx_version(&rings_member_info.0.decoy_info, tx_version, &hf)?; - - check_all_time_locks( - &rings_member_info.0.time_locked_outs, + check_transaction_contextual( + &tx_verification_data.tx, + &rings_member_info.0, current_chain_height, time_for_time_lock, &hf, - )?; - - let sum_outputs = check_outputs(&tx_verification_data.tx.prefix.outputs, &hf, tx_version)?; - - let sum_inputs = check_inputs( - tx_verification_data.tx.prefix.inputs.as_slice(), - &rings_member_info.0, - current_chain_height, - &hf, - tx_version, spent_kis, )?; - if tx_version == &TxVersion::RingSignatures { - if sum_outputs >= sum_inputs { - Err(TransactionError::OutputsTooHigh)?; - } - // check that monero-serai is calculating the correct value here, why can't we just use this - // value? because we don't have this when we create the object. - assert_eq!(tx_verification_data.fee, sum_inputs - sum_outputs); - } - - verify_contextual_signatures(&tx_verification_data.tx, &rings_member_info.0.rings)?; - Ok(()) }