darkfid: reduce contracts execution fees

This commit is contained in:
skoupidi
2025-02-08 21:00:18 +02:00
parent 008a7520c3
commit dba73bbe28
13 changed files with 91 additions and 44 deletions

View File

@@ -105,7 +105,7 @@ impl RequestHandler<DefaultRpcHandler> 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

View File

@@ -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::<Vec<JsonValue>>().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::<bool>().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()
}
}

View File

@@ -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"]}

View File

@@ -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<Proof>, Vec<SecretKey>)> {
// 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 };

View File

@@ -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<u64> {
/// Queries darkfid for given transaction's required fee.
pub async fn get_tx_fee(&self, tx: &Transaction, include_fee: bool) -> Result<u64> {
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", &params).await?;
let rep = self.darkfid_daemon_request("tx.calculate_fee", &params).await?;
let gas = *rep.get::<f64>().unwrap() as u64;
let fee = *rep.get::<f64>().unwrap() as u64;
Ok(gas)
Ok(fee)
}
/// Queries darkfid for current best fork next height.

View File

@@ -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 |

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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
}

View File

@@ -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<GasData> {
pub async fn calculate_fee(&self, tx: &Transaction, verify_fee: bool) -> Result<u64> {
// 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,

View File

@@ -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())
}