From 64ef7c96342d2833aeb025a9bef621beeaf28336 Mon Sep 17 00:00:00 2001 From: parazyd Date: Tue, 19 Dec 2023 18:01:44 +0100 Subject: [PATCH] validator: Implement basic tx fee validation --- src/error.rs | 10 +++++-- src/validator/verification.rs | 54 +++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index eb5167c54..b0a449dfc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -620,11 +620,17 @@ pub enum TxVerifyFailed { #[error("Missing contract calls in transaction")] MissingCalls, + #[error("Invalid ZK proof in transaction")] + InvalidZkProof, + #[error("Missing Money::Fee call in transaction")] MissingFee, - #[error("Invalid ZK proof in transaction")] - InvalidZkProof, + #[error("Invalid Money::Fee call in transaction")] + InvalidFee, + + #[error("Insufficient fee paid")] + InsufficientFee, #[error("Erroneous transactions found")] ErroneousTxs(Vec), diff --git a/src/validator/verification.rs b/src/validator/verification.rs index 65b41c4f9..a6e00596b 100644 --- a/src/validator/verification.rs +++ b/src/validator/verification.rs @@ -24,7 +24,7 @@ use darkfi_sdk::{ dark_tree::dark_leaf_vec_integrity_check, pasta::pallas, }; -use darkfi_serial::{Decodable, Encodable, WriteExt}; +use darkfi_serial::{deserialize_async, Decodable, Encodable, WriteExt}; use log::{debug, error, warn}; use crate::{ @@ -336,6 +336,7 @@ pub async fn verify_transaction( time_keeper: &TimeKeeper, tx: &Transaction, verifying_keys: &mut HashMap<[u8; 32], HashMap>, + verify_fee: bool, // FIXME: Remove this bool ) -> Result { let tx_hash = tx.hash()?; debug!(target: "validator::verification::verify_transaction", "Validating transaction {}", tx_hash); @@ -351,6 +352,25 @@ pub async fn verify_transaction( // Table of public keys used for signature verification let mut sig_table = vec![]; + // Verify that the first call is the transaction fee and that it has no parents or children. + if verify_fee { + if tx.calls[0].data.contract_id != *MONEY_CONTRACT_ID || tx.calls[0].data.data[0] != 0x00 { + error!( + target: "validator::verification::verify_transaction", + "[VALIDATOR] First tx call is not Money::Fee", + ); + return Err(TxVerifyFailed::MissingFee.into()) + } + + if tx.calls[0].parent_index.is_some() || !tx.calls[0].children_indexes.is_empty() { + error!( + target: "validator::verification::verify_transaction", + "[VALIDATOR] Fee call invalid (has parent/children)", + ); + return Err(TxVerifyFailed::InvalidFee.into()) + } + } + // Iterate over all calls to get the metadata for (idx, call) in tx.calls.iter().enumerate() { // Transaction must not contain a reward call, Money::PoWReward(0x08) or Consensus::Proposal(0x02) @@ -447,6 +467,34 @@ pub async fn verify_transaction( return Err(TxVerifyFailed::InvalidZkProof.into()) } + if verify_fee { + // TODO: This counts 1 gas as 1 token unit. Pricing should be better specified. + // TODO: Currently this doesn't account for signatures or ZK proofs + // TODO: Currently this doesn't account for WASM host functions + + // Deserialize the first call to find the paid fee + let fee: u64 = match deserialize_async(&tx.calls[0].data.data[1..9]).await { + Ok(v) => v, + Err(e) => { + error!( + target: "validator::verification::verify_transaction", + "[VALIDATOR] Failed deserializing tx {} fee call: {}", tx_hash, e, + ); + return Err(TxVerifyFailed::InvalidFee.into()) + } + }; + + // Check that enough fee has been paid for the used gas in this transaction. + if gas_used > fee { + error!( + target: "validator::verification::verify_transaction", + "[VALIDATOR] tx {} has insufficient fee. Required: {}, Paid: {}", + tx_hash, gas_used, fee, + ); + return Err(TxVerifyFailed::InsufficientFee.into()) + } + } + debug!(target: "validator::verification::verify_transaction", "ZK proof verification successful"); debug!(target: "validator::verification::verify_transaction", "Transaction {} verified successfully", tx_hash); @@ -484,7 +532,7 @@ pub async fn verify_transactions( // Iterate over transactions and attempt to verify them for tx in txs { overlay.lock().unwrap().checkpoint(); - match verify_transaction(overlay, time_keeper, tx, &mut vks).await { + match verify_transaction(overlay, time_keeper, tx, &mut vks, verify_fees).await { Ok(gas) => _gas_used += gas, Err(e) => { warn!(target: "validator::verification::verify_transactions", "Transaction verification failed: {}", e); @@ -495,7 +543,7 @@ pub async fn verify_transactions( } if verify_fees { - // Enforce that enough fee is paid + // Enforce that enough fee is paid. todo!() } }