4 Commity 1dfd8b94a0 ... cd9692bb90

Autor SHA1 Wiadomość Data
  brid 1dfd8b94a0 money: test burned fees excluded from reward 1 tydzień temu
  brid 58ffd3756c darkfid: use claimable fees in reward reporting 1 tydzień temu
  brid 060655d4ec validator: apply miner-claimable fee accounting 1 tydzień temu
  brid b077af3e46 money: stop fee call accumulator updates 1 tydzień temu

+ 17 - 10
bin/darkfid/src/registry/model.rs

@@ -45,12 +45,12 @@ use darkfi_money_contract::{
     MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
-    blockchain::expected_reward,
     crypto::{
         keypair::{Address, Keypair, Network, SecretKey},
         pasta_prelude::PrimeField,
         FuncId, MerkleTree, MONEY_CONTRACT_ID,
     },
+    fee::accumulate_fee,
     pasta::pallas,
     ContractCall,
 };
@@ -215,14 +215,24 @@ impl BlockTemplate {
         Ok(params.input.value)
     }
 
-    /// Return block miner-claimable fees.
+    /// Return block fees.
     ///
     /// Note: always check if block contains transactions before
     /// calling this function.
     pub async fn fees(&self) -> Result<u64> {
-        let reward = self.reward().await?;
-        let expected = expected_reward(self.block.header.height);
-        reward.checked_sub(expected).ok_or(Error::SubtractionUnderflow)
+        let mut fees = 0;
+        'outer: for tx in &self.block.txs[..self.block.txs.len() - 1] {
+            for call in &tx.calls {
+                if !call.data.is_money_fee() {
+                    continue
+                }
+
+                fees = accumulate_fee(fees, call.data.money_fee_value()?)?;
+                continue 'outer
+            }
+        }
+
+        Ok(fees)
     }
 
     /// Return block reward excluding fees and fees values.
@@ -233,11 +243,8 @@ impl BlockTemplate {
             ))
         }
 
-        let reward = self.reward().await?;
-        let fees = reward
-            .checked_sub(expected_reward(self.block.header.height))
-            .ok_or(Error::SubtractionUnderflow)?;
-        let reward = reward.checked_sub(fees).ok_or(Error::SubtractionUnderflow)?;
+        let fees = self.fees().await?;
+        let reward = self.reward().await?.checked_sub(fees).ok_or(Error::SubtractionUnderflow)?;
 
         Ok((reward, fees))
     }

+ 1 - 1
script/research/gg/src/main.rs

@@ -156,7 +156,7 @@ fn main() -> Result<()> {
                     let file = file?;
                     let bytes = base64::decode(read_to_string(file.path())?.trim()).unwrap();
                     let tx = deserialize_async(&bytes).await?;
-                    apply_transaction(&overlay, 0, pow_target, &tx, &mut tree, false).await?;
+                    apply_transaction(&overlay, 0, pow_target, &tx, &mut tree).await?;
                     genesis_block.txs.push(tx);
                 }
 

+ 4 - 7
src/contract/money/src/client/pow_reward_v1.rs

@@ -19,7 +19,7 @@
 use darkfi::{
     zk::{Proof, ProvingKey},
     zkas::ZkBinary,
-    Error, Result,
+    Result,
 };
 use darkfi_sdk::{
     blockchain::expected_reward,
@@ -65,7 +65,7 @@ pub struct PoWRewardCallBuilder {
     pub signature_keypair: Keypair,
     /// Rewarded block height
     pub block_height: u32,
-    /// Rewarded block transactions miner-claimable fees
+    /// Rewarded block transactions paid fees
     pub fees: u64,
     /// Optional recipient's public key, in case we want to mint to a different address
     pub recipient: Option<PublicKey>,
@@ -152,15 +152,12 @@ impl PoWRewardCallBuilder {
     }
 
     pub fn build(&self) -> Result<PoWRewardCallDebris> {
-        let reward = expected_reward(self.block_height)
-            .checked_add(self.fees)
-            .ok_or(Error::AdditionOverflow)?;
+        let reward = expected_reward(self.block_height) + self.fees;
         self._build(reward)
     }
 
     /// This function should only be used for testing, as PoW reward values are predefined
     pub fn build_with_custom_reward(&self, reward: u64) -> Result<PoWRewardCallDebris> {
-        let reward = reward.checked_add(self.fees).ok_or(Error::AdditionOverflow)?;
-        self._build(reward)
+        self._build(reward + self.fees)
     }
 }

+ 21 - 2
src/contract/money/src/entrypoint/fee_v1.rs

@@ -33,7 +33,7 @@ use darkfi_sdk::{
     wasm::{
         self,
         db::{
-            db_contains_key, db_contains_key_local, db_lookup, db_lookup_local, db_set,
+            db_contains_key, db_contains_key_local, db_get, db_lookup, db_lookup_local, db_set,
             db_set_local,
         },
     },
@@ -45,7 +45,7 @@ use crate::{
     error::MoneyError,
     model::{MoneyFeeParamsV1, MoneyFeeUpdateV1, DARK_TOKEN_ID},
     MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE,
-    MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
+    MONEY_CONTRACT_FEES_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
     MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
     MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
 };
@@ -131,6 +131,7 @@ pub(crate) fn money_fee_process_instruction_v1(
     let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
     let coin_roots_db_local = db_lookup_local(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
+    let fees_db = db_lookup(cid, MONEY_CONTRACT_FEES_TREE)?;
     let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
 
     // Fees can only be paid using the native token, so we'll compare
@@ -204,11 +205,25 @@ pub(crate) fn money_fee_process_instruction_v1(
         return Err(MoneyError::ValueMismatch.into())
     }
 
+    // Accumulate the height paid fee
+    let verifying_block_height = wasm::util::get_verifying_block_height()?;
+    let Some(paid_fee) = db_get(fees_db, &serialize(&verifying_block_height))? else {
+        msg!("[FeeV1] Error: Block height fees accumulator not found");
+        return Err(MoneyError::PoWRewardCallMissingFeesAccumulator.into())
+    };
+    let paid_fee: u64 = deserialize(&paid_fee)?;
+    let Some(paid_fee) = paid_fee.checked_add(fee) else {
+        msg!("[FeeV1] Error: Could not compute paid fee");
+        return Err(MoneyError::ValueMismatch.into())
+    };
+
     // At this point the state transition has passed, so we create a state update.
     let update = MoneyFeeUpdateV1 {
         nullifier: params.input.nullifier,
         coin: params.output.coin,
         tx_local: params.output.tx_local,
+        height: verifying_block_height,
+        fee: paid_fee,
     };
     // and return it
     Ok(serialize(&update))
@@ -232,6 +247,10 @@ pub(crate) fn money_fee_process_update_v1(
     let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
     let coin_roots_db_local = db_lookup_local(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
 
+    let fees_db = db_lookup(cid, MONEY_CONTRACT_FEES_TREE)?;
+
+    db_set(fees_db, &serialize(&update.height), &serialize(&update.fee))?;
+
     wasm::merkle::sparse_merkle_insert_batch(
         info_db,
         nullifiers_db,

+ 4 - 0
src/contract/money/src/model/mod.rs

@@ -192,6 +192,10 @@ pub struct MoneyFeeUpdateV1 {
     pub coin: Coin,
     /// Marker whether the output will be used tx-local
     pub tx_local: bool,
+    /// Block height the fee was verified against
+    pub height: u32,
+    /// Height accumulated fee paid
+    pub fee: u64,
 }
 
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]

+ 0 - 55
src/contract/money/tests/integration.rs

@@ -60,58 +60,3 @@ fn money_integration() -> Result<()> {
         Ok(())
     })
 }
-
-#[test]
-fn money_fee_burn_is_excluded_from_reward() -> Result<()> {
-    smol::block_on(async {
-        init_logger();
-
-        use Holder::{Alice, Bob};
-
-        let mut th = TestHarness::new(&[Alice, Bob], true).await?;
-
-        th.generate_block_all(&Alice).await?;
-        th.generate_block_all(&Alice).await?;
-
-        let block_height = 3;
-        let native_token = th.coins(&Alice)[0].note.token_id;
-        let owncoins = vec![th.coins_by_token(&Alice, native_token)[0].clone()];
-        let transfer_amount = 100_000_000;
-        let (tx, _, _) = th
-            .transfer(transfer_amount, &Alice, &Bob, &owncoins, native_token, block_height, false)
-            .await?;
-
-        let paid_fee = tx
-            .calls
-            .iter()
-            .find(|call| call.data.is_money_fee())
-            .unwrap()
-            .data
-            .money_fee_value()
-            .unwrap();
-
-        let validator = th.wallet(&Alice).validator().read().await;
-        let (_, miner_claimable_fee) = validator
-            .add_test_transactions(
-                std::slice::from_ref(&tx),
-                block_height,
-                validator.consensus.module.target,
-                false,
-                true,
-            )
-            .await?;
-        drop(validator);
-
-        assert!(miner_claimable_fee < paid_fee);
-
-        let reward_coins = th
-            .generate_block_with_txs(&Alice, &[Alice, Bob], vec![tx], miner_claimable_fee)
-            .await?;
-
-        assert_eq!(reward_coins.len(), 1);
-        assert_eq!(reward_coins[0].note.value, expected_reward(block_height) + miner_claimable_fee);
-        assert_ne!(reward_coins[0].note.value, expected_reward(block_height) + paid_fee);
-
-        Ok(())
-    })
-}

+ 1 - 69
src/contract/test-harness/src/money_pow_reward.rs

@@ -21,9 +21,7 @@ use std::slice;
 use darkfi::{
     blockchain::{BlockInfo, BlockchainOverlay, Header},
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
-    validator::verification::{
-        apply_producer_transaction, verify_producer_transaction, verify_transactions,
-    },
+    validator::verification::apply_producer_transaction,
     Result,
 };
 use darkfi_money_contract::{
@@ -159,70 +157,4 @@ impl TestHarness {
 
         Ok(found_owncoins)
     }
-
-    /// Generate and add a block containing non-producer transactions.
-    ///
-    /// The caller must provide the block's accumulated miner-claimable fees.
-    /// Returns any found miner reward [`OwnCoin`]s.
-    pub async fn generate_block_with_txs(
-        &mut self,
-        miner: &Holder,
-        holders: &[Holder],
-        txs: Vec<Transaction>,
-        fees: u64,
-    ) -> Result<Vec<OwnCoin>> {
-        info!("Building PoWReward transaction for {miner:?}");
-        let (producer_tx, params) = self.pow_reward(miner, None, None, Some(fees)).await?;
-
-        let wallet = self.wallet(miner);
-        let validator = wallet.validator.read().await;
-        let previous = validator.blockchain.last_block()?;
-        let timestamp = previous.header.timestamp.checked_add(1.into())?;
-
-        let header = Header::new(
-            previous.hash(),
-            previous.header.height + 1,
-            previous.header.nonce,
-            timestamp,
-        );
-        let mut block = BlockInfo::new_empty(header);
-
-        block.append_txs(txs);
-        block.append_txs(vec![producer_tx]);
-
-        let overlay = BlockchainOverlay::new(&validator.blockchain)?;
-        let mut tree = MerkleTree::new(1);
-        let non_producer_txs = &block.txs[..block.txs.len() - 1];
-        verify_transactions(
-            &overlay,
-            block.header.height,
-            validator.consensus.module.target,
-            non_producer_txs,
-            &mut tree,
-            validator.verify_fees,
-        )
-        .await?;
-        verify_producer_transaction(
-            &overlay,
-            block.header.height,
-            validator.consensus.module.target,
-            block.txs.last().unwrap(),
-            &mut tree,
-        )
-        .await?;
-        drop(validator);
-
-        let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(&[])?;
-        block.header.state_root = overlay.lock().unwrap().contracts.update_state_monotree(&diff)?;
-        block.sign(&wallet.keypair.secret);
-
-        let mut found_owncoins = vec![];
-        for holder in holders {
-            let wallet = self.wallet_mut(holder);
-            wallet.validator.write().await.add_test_blocks(&[block.clone()]).await?;
-            found_owncoins.extend(wallet.process_outputs(slice::from_ref(&params.output), holder));
-        }
-
-        Ok(found_owncoins)
-    }
 }

+ 5 - 6
src/validator/consensus.rs

@@ -21,7 +21,7 @@ use std::{
     str::FromStr,
 };
 
-use darkfi_sdk::{crypto::MerkleTree, fee::accumulate_fee, tx::TransactionHash};
+use darkfi_sdk::{crypto::MerkleTree, tx::TransactionHash};
 use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
 use kvdb_overlay::DatabaseOverlayStateDiff;
 use num_bigint::BigUint;
@@ -750,7 +750,7 @@ impl Fork {
     }
 
     /// Auxiliary function to retrieve unproposed valid transactions,
-    /// along with their total gas used and total miner-claimable fees. Erroneous
+    /// along with their total gas used and total paid fees. Erroneous
     /// transactions will be removed from the database.
     ///
     /// Note: Always remember to purge new trees from the database if
@@ -770,7 +770,7 @@ impl Fork {
 
         // Total gas accumulators
         let mut total_gas_used = 0_u64;
-        let mut total_miner_claimable = 0_u64;
+        let mut total_gas_paid = 0_u64;
 
         // Map of ZK proof verifying keys for the current transaction
         // batch.
@@ -834,8 +834,7 @@ impl Fork {
 
             // Update accumulated total gas
             total_gas_used = total_gas_used.saturating_add(tx_gas_used);
-            total_miner_claimable =
-                accumulate_fee(total_miner_claimable, gas_data.miner_claimable)?;
+            total_gas_paid = total_gas_paid.saturating_add(gas_data.paid);
 
             // Push the tx hash into the unproposed transactions vector
             unproposed_txs.push(tx);
@@ -844,7 +843,7 @@ impl Fork {
         // Remove erroneous transactions from mempool
         self.blockchain.remove_pending_txs_hashes(&erroneous_txs)?;
 
-        Ok((unproposed_txs, total_gas_used, total_miner_claimable))
+        Ok((unproposed_txs, total_gas_used, total_gas_paid))
     }
 
     /// Auxiliary function to create a full clone using

+ 2 - 40
src/validator/fees.rs

@@ -16,12 +16,9 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use darkfi_sdk::{crypto::MONEY_CONTRACT_ID, fee::accumulate_fee};
-use darkfi_serial::{async_trait, deserialize, serialize, SerialDecodable, SerialEncodable};
+use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
 
-use crate::{blockchain::BlockchainOverlayPtr, zkas::ZkBinary, Error, Result};
-
-const MONEY_CONTRACT_FEES_TREE: &str = "fees";
+use crate::zkas::ZkBinary;
 
 /// Fixed fee for verifying a Schnorr signature over the Pallas curve.
 pub const PALLAS_SCHNORR_VERIFY_GAS: u64 = 1850;
@@ -60,38 +57,6 @@ pub fn circuit_gas_use(zkbin: &ZkBinary) -> u64 {
     VERIFY_GAS_PER_ROW.saturating_mul(rows)
 }
 
-/// Add miner-claimable fees to the existing money contract height accumulator.
-///
-/// This is intentionally narrow: it can only update the money contract's `fees`
-/// tree at the current block height key, and it requires that the accumulator
-/// entry already exists.
-pub fn add_miner_claimable_fee(
-    overlay: &BlockchainOverlayPtr,
-    verifying_block_height: u32,
-    miner_claimable_fee: u64,
-) -> Result<()> {
-    let (overlay, fees_tree) = {
-        let blockchain = overlay.lock().unwrap();
-        let fees_tree =
-            blockchain.contracts.lookup(&MONEY_CONTRACT_ID, MONEY_CONTRACT_FEES_TREE)?;
-        (blockchain.overlay.clone(), blake3::Hash::from(fees_tree).to_string())
-    };
-
-    let key = serialize(&verifying_block_height);
-    let mut overlay = overlay.lock().unwrap();
-    let Some(existing_value) = overlay.get(&fees_tree, &key)? else {
-        return Err(Error::DatabaseError(format!(
-            "Money fee accumulator for height {verifying_block_height} not found"
-        )))
-    };
-
-    let existing_value: u64 = deserialize(&existing_value)?;
-    let updated_value = accumulate_fee(existing_value, miner_claimable_fee)?;
-
-    overlay.insert(&fees_tree, &key, &serialize(&updated_value))?;
-    Ok(())
-}
-
 /// Auxiliary struct representing the full gas usage breakdown of a
 /// transaction.
 ///
@@ -109,8 +74,6 @@ pub struct GasData {
     pub deployments: u64,
     /// Transaction paid fee
     pub paid: u64,
-    /// Transaction miner-claimable fee after burn
-    pub miner_claimable: u64,
 }
 
 impl GasData {
@@ -135,7 +98,6 @@ impl std::fmt::Debug for GasData {
             .field("signatures", &self.signatures)
             .field("deployments", &self.deployments)
             .field("paid", &self.paid)
-            .field("miner_claimable", &self.miner_claimable)
             .finish()
     }
 }

+ 3 - 10
src/validator/mod.rs

@@ -354,15 +354,8 @@ impl Validator {
         // Validate and insert each block
         for (index, block) in blocks.iter().enumerate() {
             // Verify block
-            match verify_checkpoint_block(
-                &overlay,
-                &diffs,
-                block,
-                &headers[index],
-                module.target,
-                self.verify_fees,
-            )
-            .await
+            match verify_checkpoint_block(&overlay, &diffs, block, &headers[index], module.target)
+                .await
             {
                 Ok(()) => { /* Do nothing */ }
                 // Skip already existing block
@@ -552,7 +545,7 @@ impl Validator {
     /// to the database, and a boolean called `verify_fees` to
     /// overwrite the nodes configured `verify_fees` flag.
     ///
-    /// Returns the total gas used and total miner-claimable fees for the given
+    /// Returns the total gas used and total paid fees for the given
     /// transactions.
     ///
     /// Note: This function should only be used in tests and always

+ 96 - 374
src/validator/verification.rs

@@ -26,9 +26,8 @@ use darkfi_sdk::{
     },
     dark_tree::dark_forest_leaf_vec_integrity_check,
     deploy::DeployParamsV1,
-    fee::{accumulate_fee, burn_fee, miner_claimable_fee, minimum_fee},
+    fee::minimum_fee,
     pasta::pallas,
-    tx::TransactionHash,
 };
 use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
 use kvdb_overlay::DatabaseOverlayStateDiff;
@@ -48,7 +47,7 @@ use crate::{
     util::time::Timestamp,
     validator::{
         consensus::{Consensus, Fork, Proposal, BLOCK_GAS_LIMIT},
-        fees::{add_miner_claimable_fee, circuit_gas_use, GasData, PALLAS_SCHNORR_VERIFY_GAS},
+        fees::{circuit_gas_use, GasData, PALLAS_SCHNORR_VERIFY_GAS},
         pow::PoWModule,
     },
     zk::VerifyingKey,
@@ -325,7 +324,6 @@ pub async fn verify_checkpoint_block(
     block: &BlockInfo,
     header: &HeaderHash,
     block_target: u32,
-    verify_fees: bool,
 ) -> Result<()> {
     let block_hash = block.hash();
     debug!(target: "validator::verification::verify_checkpoint_block", "Validating block {block_hash}");
@@ -351,8 +349,7 @@ pub async fn verify_checkpoint_block(
     let mut tree = MerkleTree::new(1);
     let txs = &block.txs[..block.txs.len() - 1];
     if let Err(e) =
-        apply_transactions(overlay, block.header.height, block_target, txs, &mut tree, verify_fees)
-            .await
+        apply_transactions(overlay, block.header.height, block_target, txs, &mut tree).await
     {
         warn!(
             target: "validator::verification::verify_checkpoint_block",
@@ -409,122 +406,6 @@ pub fn verify_producer_signature(block: &BlockInfo, public_key: &PublicKey) -> R
     Ok(())
 }
 
-fn extract_fee_call(
-    tx_hash: &TransactionHash,
-    tx: &Transaction,
-    verify_fee: bool,
-) -> Result<Option<(usize, u64)>> {
-    if !verify_fee {
-        return Ok(None)
-    }
-
-    let mut fee_call = None;
-
-    for (call_idx, call) in tx.calls.iter().enumerate() {
-        if !call.data.is_money_fee() {
-            continue
-        }
-
-        if fee_call.is_some() {
-            error!(
-                target: "validator::verification::extract_fee_call",
-                "[VALIDATOR] Transaction {tx_hash} contains multiple fee payment calls"
-            );
-            return Err(TxVerifyFailed::InvalidFee.into())
-        }
-
-        let fee = match call.data.money_fee_value() {
-            Ok(fee) => fee,
-            Err(e) => {
-                error!(
-                    target: "validator::verification::extract_fee_call",
-                    "[VALIDATOR] Failed parsing tx {tx_hash} fee call: {e}"
-                );
-                return Err(TxVerifyFailed::InvalidFee.into())
-            }
-        };
-
-        if fee == 0 {
-            error!(
-                target: "validator::verification::extract_fee_call",
-                "[VALIDATOR] Transaction {tx_hash} contains zero fee payment"
-            );
-            return Err(TxVerifyFailed::InvalidFee.into())
-        }
-
-        fee_call = Some((call_idx, fee));
-    }
-
-    if fee_call.is_none() {
-        error!(
-            target: "validator::verification::extract_fee_call",
-            "[VALIDATOR] Transaction {tx_hash} does not contain fee payment call"
-        );
-        return Err(TxVerifyFailed::InvalidFee.into())
-    }
-
-    Ok(fee_call)
-}
-
-fn calculate_miner_claimable_fee(
-    tx_hash: &TransactionHash,
-    total_gas_used: u64,
-    fee: u64,
-) -> Result<u64> {
-    let required_fee = match minimum_fee(total_gas_used) {
-        Ok(fee) => fee,
-        Err(e) => {
-            error!(
-                target: "validator::verification::calculate_miner_claimable_fee",
-                "[VALIDATOR] Failed calculating tx {tx_hash} fee: {e}"
-            );
-            return Err(TxVerifyFailed::InvalidFee.into())
-        }
-    };
-
-    if required_fee > fee {
-        error!(
-            target: "validator::verification::calculate_miner_claimable_fee",
-            "[VALIDATOR] Transaction {tx_hash} has insufficient fee. \
-             Required: {required_fee}, Paid: {fee}"
-        );
-        return Err(TxVerifyFailed::InsufficientFee.into())
-    }
-
-    let burned_fee = match burn_fee(required_fee) {
-        Ok(fee) => fee,
-        Err(e) => {
-            error!(
-                target: "validator::verification::calculate_miner_claimable_fee",
-                "[VALIDATOR] Failed calculating tx {tx_hash} burned fee: {e}"
-            );
-            return Err(TxVerifyFailed::InvalidFee.into())
-        }
-    };
-
-    let claimable_fee = match miner_claimable_fee(fee, burned_fee) {
-        Ok(fee) => fee,
-        Err(e) => {
-            error!(
-                target: "validator::verification::calculate_miner_claimable_fee",
-                "[VALIDATOR] Failed calculating tx {tx_hash} miner-claimable fee: {e}"
-            );
-            return Err(TxVerifyFailed::InvalidFee.into())
-        }
-    };
-
-    let tip = fee - required_fee;
-
-    debug!(target: "validator::verification::calculate_miner_claimable_fee", "The fee paid for transaction {tx_hash}: {fee}");
-    debug!(
-        target: "validator::verification::calculate_miner_claimable_fee",
-        "The fee split for transaction {tx_hash}: required={required_fee}, \
-         burned={burned_fee}, miner_claimable={claimable_fee}, tip={tip}"
-    );
-
-    Ok(claimable_fee)
-}
-
 /// Verify provided producer [`Transaction`].
 ///
 /// Verify WASM execution, signatures, and ZK proofs and apply it to
@@ -789,7 +670,38 @@ pub async fn verify_transaction(
     // Table of public keys used for signature verification
     let mut sig_table = vec![];
 
-    let fee_call = extract_fee_call(&tx_hash, tx, verify_fee)?;
+    // Index of the Fee-paying call
+    let mut fee_call_idx = 0;
+
+    if verify_fee {
+        // Verify that there is a single money fee call in the
+        // transaction.
+        let mut found_fee = false;
+        for (call_idx, call) in tx.calls.iter().enumerate() {
+            if !call.data.is_money_fee() {
+                continue
+            }
+
+            if found_fee {
+                error!(
+                    target: "validator::verification::verify_transcation",
+                    "[VALIDATOR] Transaction {tx_hash} contains multiple fee payment calls"
+                );
+                return Err(TxVerifyFailed::InvalidFee.into())
+            }
+
+            found_fee = true;
+            fee_call_idx = call_idx;
+        }
+
+        if !found_fee {
+            error!(
+                target: "validator::verification::verify_transcation",
+                "[VALIDATOR] Transaction {tx_hash} does not contain fee payment call"
+            );
+            return Err(TxVerifyFailed::InvalidFee.into())
+        }
+    }
 
     // Write the transaction calls payload data
     let mut payload = vec![];
@@ -880,13 +792,14 @@ pub async fn verify_transaction(
             // existing circuit. Might be a smart idea to do so in
             // order to have to care less about being able to verify
             // historical txs.
+            if inner_vk_map.contains_key(zkas_ns.as_str()) {
+                continue
+            }
+
             let (zkbin, vk) =
                 overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
 
-            if !inner_vk_map.contains_key(zkas_ns.as_str()) {
-                inner_vk_map.insert(zkas_ns.to_string(), vk);
-            }
-
+            inner_vk_map.insert(zkas_ns.to_string(), vk);
             circuits_to_verify.push(zkbin);
         }
 
@@ -975,14 +888,45 @@ pub async fn verify_transaction(
         return Err(TxVerifyFailed::GasLimitExceeded.into())
     }
 
-    let miner_claimable = if let Some((_, fee)) = fee_call {
-        let claimable_fee = calculate_miner_claimable_fee(&tx_hash, total_gas_used, fee)?;
+    if verify_fee {
+        // Extract the paid fee from the fee call.
+        let fee = match tx.calls[fee_call_idx].data.money_fee_value() {
+            Ok(v) => v,
+            Err(e) => {
+                error!(
+                    target: "validator::verification::verify_transaction",
+                    "[VALIDATOR] Failed parsing tx {tx_hash} fee call: {e}"
+                );
+                return Err(TxVerifyFailed::InvalidFee.into())
+            }
+        };
+
+        // Compute the required fee for this transaction
+        let required_fee = match minimum_fee(total_gas_used) {
+            Ok(fee) => fee,
+            Err(e) => {
+                error!(
+                    target: "validator::verification::verify_transaction",
+                    "[VALIDATOR] Failed calculating tx {tx_hash} fee: {e}"
+                );
+                return Err(TxVerifyFailed::InvalidFee.into())
+            }
+        };
+
+        // Check that enough fee has been paid for the used gas in this
+        // transaction.
+        if required_fee > fee {
+            error!(
+                target: "validator::verification::verify_transaction",
+                "[VALIDATOR] Transaction {tx_hash} has insufficient fee. Required: {required_fee}, Paid: {fee}"
+            );
+            return Err(TxVerifyFailed::InsufficientFee.into())
+        }
+        debug!(target: "validator::verification::verify_transaction", "The gas paid for transaction {tx_hash}: {}", gas_data.paid);
+
+        // Store paid fee
         gas_data.paid = fee;
-        gas_data.miner_claimable = claimable_fee;
-        Some(claimable_fee)
-    } else {
-        None
-    };
+    }
 
     // When we're done looping and executing over the tx's contract
     // calls and (optionally) made sure that enough fee was paid, we
@@ -1016,10 +960,6 @@ pub async fn verify_transaction(
     }
     debug!(target: "validator::verification::verify_transaction", "ZK proof verification successful");
 
-    if let Some(claimable_fee) = miner_claimable {
-        add_miner_claimable_fee(overlay, verifying_block_height, claimable_fee)?;
-    }
-
     // Append hash to merkle tree
     append_tx_to_merkle_tree(tree, tx);
 
@@ -1036,37 +976,14 @@ pub async fn apply_transaction(
     block_target: u32,
     tx: &Transaction,
     tree: &mut MerkleTree,
-    verify_fee: bool,
-) -> Result<GasData> {
+) -> Result<()> {
     let tx_hash = tx.hash();
     debug!(target: "validator::verification::apply_transaction", "Applying transaction {tx_hash}");
 
-    if verify_fee {
-        dark_forest_leaf_vec_integrity_check(
-            &tx.calls,
-            Some(MIN_TX_CALLS + 1),
-            Some(MAX_TX_CALLS),
-        )?;
-    } else {
-        dark_forest_leaf_vec_integrity_check(&tx.calls, Some(MIN_TX_CALLS), Some(MAX_TX_CALLS))?;
-    }
-
-    let fee_call = extract_fee_call(&tx_hash, tx, verify_fee)?;
-
-    let mut gas_data = GasData::default();
-
     // Write the transaction calls payload data
     let mut payload = vec![];
     tx.calls.encode_async(&mut payload).await?;
 
-    // Define a buffer in case we want to use a different payload in a
-    // specific call.
-    let mut _call_payload = vec![];
-
-    // We'll also take note of all the circuits in a Vec so we can
-    // calculate their verification cost.
-    let mut circuits_to_verify = vec![];
-
     // Create tx-local state
     let tx_local_state = Arc::new(Mutex::new(TxLocalState::new()));
 
@@ -1074,26 +991,6 @@ pub async fn apply_transaction(
     for (idx, call) in tx.calls.iter().enumerate() {
         debug!(target: "validator::verification::apply_transaction", "Executing contract call {idx}");
 
-        // Transaction call must contain a function code.
-        let Some(func) = call.data.data.first() else {
-            error!(target: "validator::verification::apply_transaction", "Call contains no data");
-            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
-        };
-
-        if call.data.is_money_pow_reward() {
-            error!(target: "validator::verification::apply_transaction", "Reward transaction detected");
-            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
-        }
-
-        // Check if its the fee call so we only pass its payload.
-        let (call_idx, call_payload) = if call.data.is_money_fee() {
-            _call_payload = vec![];
-            vec![call.clone()].encode_async(&mut _call_payload).await?;
-            (0_u8, &_call_payload)
-        } else {
-            (idx as u8, &payload)
-        };
-
         debug!(target: "validator::verification::apply_transaction", "Instantiating WASM runtime");
         let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
 
@@ -1105,38 +1002,15 @@ pub async fn apply_transaction(
             verifying_block_height,
             block_target,
             tx_hash,
-            call_idx,
+            idx as u8,
         )?;
 
-        debug!(target: "validator::verification::apply_transaction", "Executing \"metadata\" call");
-        let metadata = runtime.metadata(call_payload)?;
-
-        // Decode the metadata retrieved from the execution.
-        let mut decoder = Cursor::new(&metadata);
-        let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
-            AsyncDecodable::decode_async(&mut decoder).await?;
-        let _: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
-
-        if decoder.position() != metadata.len() as u64 {
-            error!(
-                target: "validator::verification::apply_transaction",
-                "[VALIDATOR] Failed decoding entire metadata buffer for {tx_hash}:{idx}"
-            );
-            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
-        }
-
-        for (zkas_ns, _) in &zkp_pub {
-            let (zkbin, _) =
-                overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
-            circuits_to_verify.push(zkbin);
-        }
-
         // Run the "exec" function. We keep the returned state update
         // in a buffer, prefixed by the call function ID, enforcing the
         // state update function in the contract.
         debug!(target: "validator::verification::apply_transaction", "Executing \"exec\" call");
-        let mut state_update = vec![*func];
-        state_update.append(&mut runtime.exec(call_payload)?);
+        let mut state_update = vec![call.data.data[0]];
+        state_update.append(&mut runtime.exec(&payload)?);
         debug!(target: "validator::verification::apply_transaction", "Successfully executed \"exec\" call");
 
         // If that was successful, we apply the state update in the
@@ -1164,61 +1038,24 @@ pub async fn apply_transaction(
                 verifying_block_height,
                 block_target,
                 tx_hash,
-                call_idx,
+                idx as u8,
             )?;
 
             deploy_runtime.deploy(&deploy_params.ix)?;
-
-            let deploy_gas_used = deploy_runtime.gas_used();
-            debug!(target: "validator::verification::apply_transaction", "The gas used for deployment call {call:?} of transaction {tx_hash}: {deploy_gas_used}");
-            gas_data.deployments = gas_data.deployments.saturating_add(deploy_gas_used);
         }
-
-        let wasm_gas_used = runtime.gas_used();
-        debug!(target: "validator::verification::apply_transaction", "The gas used for WASM call {call:?} of transaction {tx_hash}: {wasm_gas_used}");
-        gas_data.wasm = gas_data.wasm.saturating_add(wasm_gas_used);
-    }
-
-    gas_data.signatures = PALLAS_SCHNORR_VERIFY_GAS
-        .saturating_mul(tx.signatures.len() as u64)
-        .saturating_add(serialize_async(tx).await.len() as u64);
-    debug!(target: "validator::verification::apply_transaction", "The gas used for signature of transaction {tx_hash}: {}", gas_data.signatures);
-
-    for zkbin in circuits_to_verify.iter() {
-        let zk_circuit_gas_used = circuit_gas_use(zkbin);
-        debug!(target: "validator::verification::apply_transaction", "The gas used for ZK circuit in namespace {} of transaction {tx_hash}: {zk_circuit_gas_used}", zkbin.namespace);
-        gas_data.zk_circuits = gas_data.zk_circuits.saturating_add(zk_circuit_gas_used);
-    }
-
-    let total_gas_used = gas_data.total_gas_used();
-
-    if total_gas_used > TX_GAS_LIMIT {
-        error!(
-            target: "validator::verification::apply_transaction",
-            "[VALIDATOR] Transaction {tx_hash} exceeds TX_GAS_LIMIT: \
-             {total_gas_used} > {TX_GAS_LIMIT}"
-        );
-        return Err(TxVerifyFailed::GasLimitExceeded.into())
-    }
-
-    if let Some((_, fee)) = fee_call {
-        let claimable_fee = calculate_miner_claimable_fee(&tx_hash, total_gas_used, fee)?;
-        gas_data.paid = fee;
-        gas_data.miner_claimable = claimable_fee;
-        add_miner_claimable_fee(overlay, verifying_block_height, claimable_fee)?;
     }
 
     // Append hash to merkle tree
     append_tx_to_merkle_tree(tree, tx);
 
     debug!(target: "validator::verification::apply_transaction", "Transaction {tx_hash} applied successfully");
-    Ok(gas_data)
+    Ok(())
 }
 
 /// Verify a set of [`Transaction`] in sequence and apply them if all
 /// are valid. In case any of the transactions fail, they will be
 /// returned to the caller as an error. If all transactions are valid,
-/// the function will return the total gas used and total miner-claimable fees
+/// the function will return the total gas used and total paid fees
 /// from all the transactions. Additionally, their hash is appended to
 /// the provided Merkle tree.
 ///
@@ -1242,7 +1079,7 @@ pub async fn verify_transactions(
 
     // Total gas accumulators
     let mut total_gas_used = 0_u64;
-    let mut total_miner_claimable = 0_u64;
+    let mut total_gas_paid = 0_u64;
 
     // Map of ZK proof verifying keys for the current transaction batch
     let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
@@ -1298,14 +1135,14 @@ pub async fn verify_transactions(
 
         // Update accumulated total gas
         total_gas_used = total_gas_used.saturating_add(tx_gas_used);
-        total_miner_claimable = accumulate_fee(total_miner_claimable, gas_data.miner_claimable)?;
+        total_gas_paid = total_gas_paid.saturating_add(gas_data.paid);
     }
 
     if !erroneous_txs.is_empty() {
         return Err(TxVerifyFailed::ErroneousTxs(erroneous_txs).into())
     }
 
-    Ok((total_gas_used, total_miner_claimable))
+    Ok((total_gas_used, total_gas_paid))
 }
 
 /// Apply given set of [`Transaction`] in sequence, without formal
@@ -1318,64 +1155,34 @@ async fn apply_transactions(
     block_target: u32,
     txs: &[Transaction],
     tree: &mut MerkleTree,
-    verify_fees: bool,
-) -> Result<(u64, u64)> {
+) -> Result<()> {
     debug!(target: "validator::verification::apply_transactions", "Applying {} transactions", txs.len());
     if txs.is_empty() {
-        return Ok((0, 0))
+        return Ok(())
     }
 
     // Tracker for failed txs
     let mut erroneous_txs = vec![];
-    let mut total_gas_used = 0_u64;
-    let mut total_miner_claimable = 0_u64;
 
     // Iterate over transactions and attempt to apply them
     for tx in txs {
         overlay.lock().unwrap().checkpoint();
-        let gas_data = match apply_transaction(
-            overlay,
-            verifying_block_height,
-            block_target,
-            tx,
-            tree,
-            verify_fees,
-        )
-        .await
+        if let Err(e) =
+            apply_transaction(overlay, verifying_block_height, block_target, tx, tree).await
         {
-            Ok(gas_values) => gas_values,
-            Err(e) => {
-                warn!(target: "validator::verification::apply_transactions", "Transaction apply failed: {e}");
-                erroneous_txs.push(tx.clone());
-                overlay.lock().unwrap().revert_to_checkpoint();
-                continue
-            }
-        };
-
-        let tx_gas_used = gas_data.total_gas_used();
-        let accumulated_gas_usage = total_gas_used.saturating_add(tx_gas_used);
-        if accumulated_gas_usage > BLOCK_GAS_LIMIT {
-            warn!(
-                target: "validator::verification::apply_transactions",
-                "Transaction {} exceeds configured transaction gas limit: \
-                 {accumulated_gas_usage} - {BLOCK_GAS_LIMIT}",
-                tx.hash()
-            );
+            warn!(target: "validator::verification::apply_transactions", "Transaction apply failed: {e}");
             erroneous_txs.push(tx.clone());
             overlay.lock().unwrap().revert_to_checkpoint();
-            break
-        }
-
-        total_gas_used = total_gas_used.saturating_add(tx_gas_used);
-        total_miner_claimable = accumulate_fee(total_miner_claimable, gas_data.miner_claimable)?;
+        };
     }
 
     if !erroneous_txs.is_empty() {
         return Err(TxVerifyFailed::ErroneousTxs(erroneous_txs).into())
     }
 
-    Ok((total_gas_used, total_miner_claimable))
+    Ok(())
 }
+
 /// Verify given [`Proposal`] against provided consensus state.
 ///
 /// A proposal is considered valid when the following rules apply:
@@ -1472,88 +1279,3 @@ pub async fn verify_fork_proposal(
 
     Ok(())
 }
-
-#[cfg(test)]
-mod tests {
-    use darkfi_sdk::{
-        crypto::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
-        dark_tree::DarkLeaf,
-        fee::{burn_fee, minimum_fee},
-        tx::ContractCall,
-    };
-    use darkfi_serial::serialize;
-
-    use super::*;
-
-    fn leaf(call: ContractCall) -> DarkLeaf<ContractCall> {
-        DarkLeaf { data: call, parent_index: None, children_indexes: vec![] }
-    }
-
-    fn fee_call(fee: u64) -> DarkLeaf<ContractCall> {
-        let mut data = vec![0x00];
-        data.extend(serialize(&fee));
-        leaf(ContractCall { contract_id: *MONEY_CONTRACT_ID, data })
-    }
-
-    fn short_fee_call() -> DarkLeaf<ContractCall> {
-        leaf(ContractCall { contract_id: *MONEY_CONTRACT_ID, data: vec![0x00] })
-    }
-
-    fn non_fee_call() -> DarkLeaf<ContractCall> {
-        leaf(ContractCall { contract_id: *DAO_CONTRACT_ID, data: vec![0x00] })
-    }
-
-    fn tx(calls: Vec<DarkLeaf<ContractCall>>) -> Transaction {
-        Transaction { calls, proofs: vec![], signatures: vec![] }
-    }
-
-    #[test]
-    fn fee_call_extraction_is_disabled_when_fee_checks_are_disabled() {
-        let tx = tx(vec![]);
-        assert_eq!(extract_fee_call(&tx.hash(), &tx, false).unwrap(), None);
-    }
-
-    #[test]
-    fn fee_call_extraction_allows_one_fee_call_with_other_calls() {
-        let tx = tx(vec![non_fee_call(), fee_call(42)]);
-
-        assert_eq!(extract_fee_call(&tx.hash(), &tx, true).unwrap(), Some((1, 42)));
-    }
-
-    #[test]
-    fn fee_call_extraction_rejects_missing_duplicate_malformed_and_zero_fee() {
-        let missing = tx(vec![non_fee_call()]);
-        assert!(extract_fee_call(&missing.hash(), &missing, true).is_err());
-
-        let duplicate = tx(vec![fee_call(1), fee_call(2)]);
-        assert!(extract_fee_call(&duplicate.hash(), &duplicate, true).is_err());
-
-        let malformed = tx(vec![short_fee_call()]);
-        assert!(extract_fee_call(&malformed.hash(), &malformed, true).is_err());
-
-        let zero = tx(vec![fee_call(0)]);
-        assert!(extract_fee_call(&zero.hash(), &zero, true).is_err());
-    }
-
-    #[test]
-    fn miner_claimable_fee_split_handles_exact_fee_and_tip() {
-        let tx_hash = TransactionHash::none();
-        let gas = 10;
-        let required = minimum_fee(gas).unwrap();
-        let burned = burn_fee(required).unwrap();
-
-        assert_eq!(required, 50);
-        assert_eq!(burned, 37);
-        assert_eq!(calculate_miner_claimable_fee(&tx_hash, gas, required).unwrap(), 13);
-        assert_eq!(calculate_miner_claimable_fee(&tx_hash, gas, required + 7).unwrap(), 20);
-    }
-
-    #[test]
-    fn miner_claimable_fee_split_rejects_insufficient_fee() {
-        let tx_hash = TransactionHash::none();
-        let gas = 10;
-        let required = minimum_fee(gas).unwrap();
-
-        assert!(calculate_miner_claimable_fee(&tx_hash, gas, required - 1).is_err());
-    }
-}