mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-07 22:04:03 -05:00
darkfid: reduce contracts execution fees
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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", ¶ms).await?;
|
||||
let rep = self.darkfid_daemon_request("tx.calculate_fee", ¶ms).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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user