From dba73bbe28fca07a0e0a6d06ebdb0a0c665d27cd Mon Sep 17 00:00:00 2001 From: skoupidi Date: Sat, 8 Feb 2025 21:00:18 +0200 Subject: [PATCH] darkfid: reduce contracts execution fees --- bin/darkfid/src/rpc.rs | 2 +- bin/darkfid/src/rpc_tx.rs | 16 ++++++------ bin/drk/Cargo.toml | 2 +- bin/drk/src/money.rs | 11 ++++---- bin/drk/src/rpc.rs | 10 +++---- .../localnet/darkfid-single-node/README.md | 26 +++++++++++++++++++ doc/src/testnet/dao.md | 4 +-- src/contract/money/src/client/fee_v1.rs | 2 +- src/contract/money/tests/delayed_tx.rs | 11 +++++--- src/contract/test-harness/src/money_fee.rs | 23 ++++++++++------ src/validator/fees.rs | 8 ++++++ src/validator/mod.rs | 8 +++--- src/validator/verification.rs | 12 +++++---- 13 files changed, 91 insertions(+), 44 deletions(-) diff --git a/bin/darkfid/src/rpc.rs b/bin/darkfid/src/rpc.rs index 0637778b3..10b42980f 100644 --- a/bin/darkfid/src/rpc.rs +++ b/bin/darkfid/src/rpc.rs @@ -105,7 +105,7 @@ impl RequestHandler for DarkfiNode { "tx.broadcast" => self.tx_broadcast(req.id, req.params).await, "tx.pending" => self.tx_pending(req.id, req.params).await, "tx.clean_pending" => self.tx_pending(req.id, req.params).await, - "tx.calculate_gas" => self.tx_calculate_gas(req.id, req.params).await, + "tx.calculate_fee" => self.tx_calculate_fee(req.id, req.params).await, // ============== // Invalid method diff --git a/bin/darkfid/src/rpc_tx.rs b/bin/darkfid/src/rpc_tx.rs index 92b4955ae..0e45f9194 100644 --- a/bin/darkfid/src/rpc_tx.rs +++ b/bin/darkfid/src/rpc_tx.rs @@ -214,16 +214,16 @@ impl DarkfiNode { // Returns the gas value if the transaction is valid, otherwise, a corresponding // error. // - // --> {"jsonrpc": "2.0", "method": "tx.calculate_gas", "params": ["base64encodedTX", "include_fee"], "id": 1} + // --> {"jsonrpc": "2.0", "method": "tx.calculate_fee", "params": ["base64encodedTX", "include_fee"], "id": 1} // <-- {"jsonrpc": "2.0", "result": true, "id": 1} - pub async fn tx_calculate_gas(&self, id: u16, params: JsonValue) -> JsonResult { + pub async fn tx_calculate_fee(&self, id: u16, params: JsonValue) -> JsonResult { let params = params.get::>().unwrap(); if params.len() != 2 || !params[0].is_string() || !params[1].is_bool() { return JsonError::new(InvalidParams, None, id).into() } if !*self.validator.synced.read().await { - error!(target: "darkfid::rpc::tx_calculate_gas", "Blockchain is not synced"); + error!(target: "darkfid::rpc::tx_calculate_fee", "Blockchain is not synced"); return server_error(RpcError::NotSynced, id, None) } @@ -232,7 +232,7 @@ impl DarkfiNode { let tx_bytes = match base64::decode(tx_enc) { Some(v) => v, None => { - error!(target: "darkfid::rpc::tx_calculate_gas", "Failed decoding base64 transaction"); + error!(target: "darkfid::rpc::tx_calculate_fee", "Failed decoding base64 transaction"); return server_error(RpcError::ParseError, id, None) } }; @@ -240,7 +240,7 @@ impl DarkfiNode { let tx: Transaction = match deserialize_async(&tx_bytes).await { Ok(v) => v, Err(e) => { - error!(target: "darkfid::rpc::tx_calculate_gas", "Failed deserializing bytes into Transaction: {}", e); + error!(target: "darkfid::rpc::tx_calculate_fee", "Failed deserializing bytes into Transaction: {}", e); return server_error(RpcError::ParseError, id, None) } }; @@ -249,15 +249,15 @@ impl DarkfiNode { let include_fee = params[1].get::().unwrap(); // Simulate state transition - let result = self.validator.calculate_gas(&tx, *include_fee).await; + let result = self.validator.calculate_fee(&tx, *include_fee).await; if result.is_err() { error!( - target: "darkfid::rpc::tx_calculate_gas", "Failed to validate state transition: {}", + target: "darkfid::rpc::tx_calculate_fee", "Failed to validate state transition: {}", result.err().unwrap() ); return server_error(RpcError::TxGasCalculationFail, id, None) }; - JsonResponse::new(JsonValue::Number(result.unwrap().total_gas_used() as f64), id).into() + JsonResponse::new(JsonValue::Number(result.unwrap() as f64), id).into() } } diff --git a/bin/drk/Cargo.toml b/bin/drk/Cargo.toml index a111c0bec..e23a24654 100644 --- a/bin/drk/Cargo.toml +++ b/bin/drk/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" [dependencies] # Darkfi -darkfi = {path = "../../", features = ["async-daemonize", "bs58", "rpc"]} +darkfi = {path = "../../", features = ["async-daemonize", "bs58", "rpc", "validator"]} darkfi_money_contract = {path = "../../src/contract/money", features = ["no-entrypoint", "client"]} darkfi_dao_contract = {path = "../../src/contract/dao", features = ["no-entrypoint", "client"]} darkfi_deployooor_contract = {path = "../../src/contract/deployooor", features = ["no-entrypoint", "client"]} diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index 44165906d..beb6bdb3d 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -25,6 +25,7 @@ use rusqlite::types::Value; use darkfi::{ tx::Transaction, + validator::fees::compute_fee, zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof}, zkas::ZkBinary, Error, Result, @@ -1182,14 +1183,14 @@ impl Drk { fee_zkbin: &ZkBinary, spent_coins: Option<&[OwnCoin]>, ) -> Result<(ContractCall, Vec, Vec)> { - // First we verify the fee-less transaction to see how much gas it uses for execution + // First we verify the fee-less transaction to see how much fee it requires for execution // and verification. - let gas_used = FEE_CALL_GAS + self.get_tx_gas(tx, false).await?; + let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?; // Knowing the total gas, we can now find an OwnCoin of enough value // so that we can create a valid Money::Fee call. let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?; - available_coins.retain(|x| x.note.value > gas_used); + available_coins.retain(|x| x.note.value > required_fee); if let Some(spent_coins) = spent_coins { available_coins.retain(|x| !spent_coins.contains(x)); } @@ -1198,7 +1199,7 @@ impl Drk { } let coin = &available_coins[0]; - let change_value = coin.note.value - gas_used; + let change_value = coin.note.value - required_fee; // Input and output setup let input = FeeCallInput { @@ -1275,7 +1276,7 @@ impl Drk { // Encode the contract call let mut data = vec![MoneyFunction::FeeV1 as u8]; - gas_used.encode_async(&mut data).await?; + required_fee.encode_async(&mut data).await?; params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; diff --git a/bin/drk/src/rpc.rs b/bin/drk/src/rpc.rs index 3e34479a8..84b0bf9d3 100644 --- a/bin/drk/src/rpc.rs +++ b/bin/drk/src/rpc.rs @@ -475,17 +475,17 @@ impl Drk { Ok(ret) } - /// Queries darkfid for given transaction's gas. - pub async fn get_tx_gas(&self, tx: &Transaction, include_fee: bool) -> Result { + /// Queries darkfid for given transaction's required fee. + pub async fn get_tx_fee(&self, tx: &Transaction, include_fee: bool) -> Result { let params = JsonValue::Array(vec![ JsonValue::String(base64::encode(&serialize_async(tx).await)), JsonValue::Boolean(include_fee), ]); - let rep = self.darkfid_daemon_request("tx.calculate_gas", ¶ms).await?; + let rep = self.darkfid_daemon_request("tx.calculate_fee", ¶ms).await?; - let gas = *rep.get::().unwrap() as u64; + let fee = *rep.get::().unwrap() as u64; - Ok(gas) + Ok(fee) } /// Queries darkfid for current best fork next height. diff --git a/contrib/localnet/darkfid-single-node/README.md b/contrib/localnet/darkfid-single-node/README.md index 4b65a2030..7043b20ee 100644 --- a/contrib/localnet/darkfid-single-node/README.md +++ b/contrib/localnet/darkfid-single-node/README.md @@ -78,3 +78,29 @@ of the guide can be added for future regressions. | 38 | Transaction simulate | explorer simulate-tx | Pass | | 39 | Transaction broadcast | broadcast | Pass | | 40 | Transaction attach fee | attach-fee | Pass | + +## Transactions fees + +This table contains each executed transaction fee in `DRK`. + +| Type | Description | Fee | +|-------------|---------------------------------------------------------|------------| +| Transfer | Native token transfer with single input and output | 0.00525303 | +| Transfer | Native token transfer with single input and two outputs | 0.00557027 | +| Transfer | Native token transfer with two inputs and single output | 0.00570562 | +| Transfer | Native token transfer with two inputs and outputs | 0.00602301 | +| Token mint | Custom token mint | 0.00518391 | +| Transfer | Custom token transfer with single input and two outputs | 0.00557027 | +| OTC swap | Atomic swap between two custom tokens | 0.00601657 | +| DAO mint | Mint a generated DAO onchain | 0.00474321 | +| Transfer | Send tokens to a DAO treasury | 0.00602301 | +| DAO propose | Mint a generated DAO transfer proposal onchain | 0.00574667 | +| DAO vote | Vote for a minted DAO transfer proposal | 0.00601218 | +| DAO exec | Execute (early) a passed DAO transfer proposal | 0.00988316 | +| DAO propose | Mint a generated DAO generic proposal onchain | 0.00574445 | +| DAO vote | Vote for a minted DAO generic proposal | 0.00601218 | +| DAO exec | Execute (early) a passed DAO generic proposal | 0.00530605 | +| Token mint | Custom token mint for a DAO treasury | 0.00518391 | +| DAO propose | Mint a generated DAO to DAO transfer proposal onchain | 0.00574667 | +| DAO vote | Vote for a minted DAO to DAO transfer proposal | 0.00601218 | +| DAO exec | Execute (early) a passed DAO to DAO transfer proposal | 0.00988316 | diff --git a/doc/src/testnet/dao.md b/doc/src/testnet/dao.md index a5e51623e..a68a8b0fc 100644 --- a/doc/src/testnet/dao.md +++ b/doc/src/testnet/dao.md @@ -226,7 +226,7 @@ instead of transfering some to the DAO, we will mint them directly into it: ``` -$ ./drk token mint MLDY 20 {DAO_PUBLIC_KEY} \ +$ ./drk token mint MLDY 20 {DAO_NOTES_PUBLIC_KEY} \ {DAO_CONTRACT_SPEND_HOOK} {DAO_BULLA} > mint_dao_mldy_tx $ ./drk broadcast < mint_dao_mldy_tx ``` @@ -252,7 +252,7 @@ from the DAO treasury to the new DAO we created: ``` $ ./drk dao list WickedDAO -$ ./drk dao propose-transfer MiladyMakerDAO 1 6.9 MLDY {WICKED_DAO_PUBLIC_KEY} \ +$ ./drk dao propose-transfer MiladyMakerDAO 1 6.9 MLDY {WICKED_DAO_NOTES_PUBLIC_KEY} \ {DAO_CONTRACT_SPEND_HOOK} {WICKED_DAO_BULLA} $ ./drk dao proposal {PROPOSAL_BULLA} --mint-proposal > dao_mldy_transfer_proposal_wckd_mint_tx $ ./drk broadcast < dao_mldy_transfer_proposal_wckd_mint_tx diff --git a/src/contract/money/src/client/fee_v1.rs b/src/contract/money/src/client/fee_v1.rs index 6db7a057d..4c4928312 100644 --- a/src/contract/money/src/client/fee_v1.rs +++ b/src/contract/money/src/client/fee_v1.rs @@ -39,7 +39,7 @@ use crate::{ /// Fixed gas used by the fee call. /// This is the minimum gas any fee-paying transaction will use. -pub const FEE_CALL_GAS: u64 = 41_000_000; +pub const FEE_CALL_GAS: u64 = 42_000_000; /// Private values related to the Fee call pub struct FeeCallSecrets { diff --git a/src/contract/money/tests/delayed_tx.rs b/src/contract/money/tests/delayed_tx.rs index a520d50a3..85ee4490d 100644 --- a/src/contract/money/tests/delayed_tx.rs +++ b/src/contract/money/tests/delayed_tx.rs @@ -18,6 +18,7 @@ use darkfi::{ tx::{ContractCallLeaf, TransactionBuilder}, + validator::fees::compute_fee, zk::halo2::Field, Result, }; @@ -135,8 +136,7 @@ fn delayed_tx() -> Result<()> { // First we verify the fee-less transaction to see how much gas it uses for execution // and verification. - let mut gas_used = FEE_CALL_GAS; - gas_used += wallet + let gas_used = wallet .validator .add_test_transactions( &[tx], @@ -148,8 +148,11 @@ fn delayed_tx() -> Result<()> { .await? .0; + // Compute the required fee + let required_fee = compute_fee(&(gas_used + FEE_CALL_GAS)); + let coin = &output_coins[0]; - let change_value = coin.note.value - gas_used; + let change_value = coin.note.value - required_fee; // Input and output setup let input = FeeCallInput { @@ -227,7 +230,7 @@ fn delayed_tx() -> Result<()> { // Encode the contract call let mut data = vec![MoneyFunction::FeeV1 as u8]; - gas_used.encode_async(&mut data).await?; + required_fee.encode_async(&mut data).await?; fee_call_params.encode_async(&mut data).await?; let fee_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; diff --git a/src/contract/test-harness/src/money_fee.rs b/src/contract/test-harness/src/money_fee.rs index e057bc0cb..5f00b6178 100644 --- a/src/contract/test-harness/src/money_fee.rs +++ b/src/contract/test-harness/src/money_fee.rs @@ -20,6 +20,7 @@ use std::{collections::HashSet, hash::RandomState}; use darkfi::{ tx::{ContractCallLeaf, Transaction, TransactionBuilder}, + validator::fees::compute_fee, zk::{halo2::Field, Proof}, Result, }; @@ -56,11 +57,14 @@ impl TestHarness { ) -> Result<(Transaction, MoneyFeeParamsV1)> { let wallet = self.holders.get(holder).unwrap(); + // Compute fee call required fee + let required_fee = compute_fee(&FEE_CALL_GAS); + // Find a compatible OwnCoin let coin = wallet .unspent_money_coins .iter() - .find(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > FEE_CALL_GAS) + .find(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > required_fee) .unwrap(); // Input and output setup @@ -72,7 +76,7 @@ impl TestHarness { let output = FeeCallOutput { public_key: wallet.keypair.public, - value: coin.note.value - FEE_CALL_GAS, + value: coin.note.value - required_fee, token_id: coin.note.token_id, blind: Blind::random(&mut OsRng), spend_hook: FuncId::none(), @@ -139,7 +143,7 @@ impl TestHarness { }; let mut data = vec![MoneyFunction::FeeV1 as u8]; - FEE_CALL_GAS.encode_async(&mut data).await?; + required_fee.encode_async(&mut data).await?; params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; let mut tx_builder = @@ -217,8 +221,7 @@ impl TestHarness { // First we verify the fee-less transaction to see how much gas it uses for execution // and verification. let wallet = self.holders.get(holder).unwrap(); - let mut gas_used = FEE_CALL_GAS; - gas_used += wallet + let gas_used = wallet .validator .add_test_transactions( &[tx], @@ -230,16 +233,20 @@ impl TestHarness { .await? .0; + // Compute the required fee + let required_fee = compute_fee(&(gas_used + FEE_CALL_GAS)); + // Knowing the total gas, we can now find an OwnCoin of enough value // so that we can create a valid Money::Fee call. let spent_coins: HashSet<&OwnCoin, RandomState> = HashSet::from_iter(spent_coins); let mut available_coins = wallet.unspent_money_coins.clone(); - available_coins.retain(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > gas_used); + available_coins + .retain(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > required_fee); available_coins.retain(|x| !spent_coins.contains(x)); assert!(!available_coins.is_empty()); let coin = &available_coins[0]; - let change_value = coin.note.value - gas_used; + let change_value = coin.note.value - required_fee; // Input and output setup let input = FeeCallInput { @@ -318,7 +325,7 @@ impl TestHarness { // Encode the contract call let mut data = vec![MoneyFunction::FeeV1 as u8]; - gas_used.encode_async(&mut data).await?; + required_fee.encode_async(&mut data).await?; params.encode_async(&mut data).await?; let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data }; diff --git a/src/validator/fees.rs b/src/validator/fees.rs index e840fb781..f56f7c680 100644 --- a/src/validator/fees.rs +++ b/src/validator/fees.rs @@ -132,3 +132,11 @@ impl std::fmt::Debug for GasData { .finish() } } + +/// Auxiliary function to compute the corresponding fee value +/// for the provided gas. +/// +/// Currently we simply divide the gas value by 100. +pub fn compute_fee(gas: &u64) -> u64 { + gas / 100 +} diff --git a/src/validator/mod.rs b/src/validator/mod.rs index a1ffc9862..7fba6ce4f 100644 --- a/src/validator/mod.rs +++ b/src/validator/mod.rs @@ -31,7 +31,6 @@ use crate::{ }, error::TxVerifyFailed, tx::Transaction, - validator::fees::GasData, zk::VerifyingKey, Error, Result, }; @@ -53,6 +52,7 @@ use verification::{ /// Fee calculation helpers pub mod fees; +use fees::compute_fee; /// Helper utilities pub mod utils; @@ -130,11 +130,11 @@ impl Validator { Ok(state) } - /// Auxiliary function to compute provided transaction's total gas, + /// Auxiliary function to compute provided transaction's required fee, /// against current best fork. /// The function takes a boolean called `verify_fee` to overwrite /// the nodes configured `verify_fees` flag. - pub async fn calculate_gas(&self, tx: &Transaction, verify_fee: bool) -> Result { + pub async fn calculate_fee(&self, tx: &Transaction, verify_fee: bool) -> Result { // Grab the best fork to verify against let forks = self.consensus.forks.read().await; let fork = forks[best_fork_index(&forks)?].full_clone()?; @@ -164,7 +164,7 @@ impl Validator { // Purge new trees fork.overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?; - Ok(verify_result) + Ok(compute_fee(&verify_result.total_gas_used())) } /// The node retrieves a transaction, validates its state transition, diff --git a/src/validator/verification.rs b/src/validator/verification.rs index 4658b6d9e..d3276b2e4 100644 --- a/src/validator/verification.rs +++ b/src/validator/verification.rs @@ -40,7 +40,7 @@ use crate::{ tx::{Transaction, MAX_TX_CALLS, MIN_TX_CALLS}, validator::{ consensus::{Consensus, Fork, Proposal, GAS_LIMIT_UNPROPOSED_TXS}, - fees::{circuit_gas_use, GasData, PALLAS_SCHNORR_SIGNATURE_FEE}, + fees::{circuit_gas_use, compute_fee, GasData, PALLAS_SCHNORR_SIGNATURE_FEE}, pow::PoWModule, }, zk::VerifyingKey, @@ -738,13 +738,15 @@ pub async fn verify_transaction( } }; - // TODO: This counts 1 gas as 1 token unit. Pricing should be better specified. - // Check that enough fee has been paid for the used gas in this transaction. - if total_gas_used > fee { + // Compute the required fee for this transaction + let required_fee = compute_fee(&total_gas_used); + + // 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 {} has insufficient fee. Required: {}, Paid: {}", - tx_hash, total_gas_used, fee, + tx_hash, required_fee, fee, ); return Err(TxVerifyFailed::InsufficientFee.into()) }