Преглед на файлове

runtime/import: add hash compute and storage gas for trees

Charge for the per-level hash computation:

* merkle_add: coins_len * MERKLE_DEPTH * SINSEMILLA_HASH_GAS

* sparse_merkle_insert_batch: nullifiers.len() * SMT_FP_DEPTH *
  POSEIDON_HASH_GAS

Apply WRITE_GAS_PER_BYTE to the storage footprint (appended leaves,
value data, and latest root data) for on-chain writes. Local merkle
writes are priced per raw byte.
darkfi преди 1 месец
родител
ревизия
9a4a824ad3
променени са 2 файла, в които са добавени 42 реда и са изтрити 17 реда
  1. 23 9
      src/runtime/import/merkle.rs
  2. 19 8
      src/runtime/import/smt.rs

+ 23 - 9
src/runtime/import/merkle.rs

@@ -19,7 +19,7 @@
 use std::io::Cursor;
 
 use darkfi_sdk::{
-    crypto::{pasta_prelude::Field, MerkleNode, MerkleTree},
+    crypto::{constants::MERKLE_DEPTH_ORCHARD, pasta_prelude::Field, MerkleNode, MerkleTree},
     hex::AsHex,
     pasta::pallas,
     wasm,
@@ -28,9 +28,12 @@ use darkfi_serial::{serialize, Decodable, Encodable, WriteExt};
 use tracing::{debug, error};
 use wasmer::{FunctionEnvMut, WasmPtr};
 
-use crate::runtime::{
-    import::{acl::acl_allow, util::wasm_mem_read},
-    vm_runtime::{ContractSection, Env},
+use crate::{
+    runtime::{
+        import::{acl::acl_allow, util::wasm_mem_read},
+        vm_runtime::{ContractSection, Env},
+    },
+    validator::fees::{MIN_GAS, SINSEMILLA_HASH_GAS, WRITE_GAS_PER_BYTE},
 };
 
 /// Add data to an on-chain Merkle tree.
@@ -75,6 +78,9 @@ pub(crate) fn merkle_add_internal(
     let (env, mut store) = ctx.data_and_store_mut();
     let cid = env.contract_id;
 
+    // Subtract base gas before the ACL check.
+    env.subtract_gas(&mut store, MIN_GAS);
+
     // Enforce function ACL
     if let Err(e) = acl_allow(env, &[ContractSection::Update]) {
         error!(
@@ -84,9 +90,6 @@ pub(crate) fn merkle_add_internal(
         return darkfi_sdk::error::CALLER_ACCESS_DENIED
     }
 
-    // Subtract used gas. 1 for opcode, 33 for value_data.len().
-    env.subtract_gas(&mut store, 34);
-
     // Get the wasm memory reader
     let mut buf_reader = match wasm_mem_read(env, &store, ptr, ptr_len) {
         Ok(v) => v,
@@ -337,6 +340,10 @@ pub(crate) fn merkle_add_internal(
     env.call_idx.encode(&mut value_data).expect("Unable to serialize call_idx");
     assert_eq!(value_data.len(), 32 + 1);
 
+    // Capture lengths before the writes below move the data.
+    let value_data_len = value_data.len();
+    let latest_root_data_len = latest_root_data.len();
+
     if local {
         // We unwrap here because we already know the databases exist
         // from when we fetched the tree.
@@ -370,8 +377,15 @@ pub(crate) fn merkle_add_internal(
     drop(overlay);
     drop(blockchain);
     drop(db_handles);
-    let spent_gas = coins_len * 32;
-    env.subtract_gas(&mut store, spent_gas as u64);
+
+    // Compute cost: one Sinsemilla hash per tree level per coin appended.
+    let compute_gas = coins_len as u64 * MERKLE_DEPTH_ORCHARD as u64 * SINSEMILLA_HASH_GAS;
+
+    // Storage cost: appended coins, value data, and root data.
+    let storage_bytes = coins_len as u64 * 32 + value_data_len as u64 + latest_root_data_len as u64;
+    let storage_gas = if local { storage_bytes } else { storage_bytes * WRITE_GAS_PER_BYTE };
+
+    env.subtract_gas(&mut store, compute_gas.saturating_add(storage_gas));
 
     wasm::entrypoint::SUCCESS
 }

+ 19 - 8
src/runtime/import/smt.rs

@@ -33,7 +33,10 @@ use tracing::{debug, error};
 use wasmer::{FunctionEnvMut, WasmPtr};
 
 use super::acl::acl_allow;
-use crate::runtime::vm_runtime::{ContractSection, Env};
+use crate::{
+    runtime::vm_runtime::{ContractSection, Env},
+    validator::fees::{MIN_GAS, POSEIDON_HASH_GAS, WRITE_GAS_PER_BYTE},
+};
 
 /// An SMT adapter for sled overlay storage. Compatible with the WasmDb SMT adapter
 pub struct SledStorage<'a> {
@@ -106,6 +109,9 @@ pub(crate) fn sparse_merkle_insert_batch(
     let (env, mut store) = ctx.data_and_store_mut();
     let cid = env.contract_id;
 
+    // Subtract base gas before the ACL check.
+    env.subtract_gas(&mut store, MIN_GAS);
+
     // Enforce function ACL
     if let Err(e) = acl_allow(env, &[ContractSection::Update]) {
         error!(
@@ -115,10 +121,6 @@ pub(crate) fn sparse_merkle_insert_batch(
         return darkfi_sdk::error::CALLER_ACCESS_DENIED
     }
 
-    // Subtract used gas.
-    // This makes calling the function which returns early have some (small) cost.
-    env.subtract_gas(&mut store, 1);
-
     let memory_view = env.memory_view(&store);
     let Ok(mem_slice) = ptr.slice(&memory_view, len) else {
         error!(
@@ -301,6 +303,9 @@ pub(crate) fn sparse_merkle_insert_batch(
         return darkfi_sdk::error::INTERNAL_ERROR
     }
 
+    // Capture length before the writes below move the data.
+    let new_value_data_len = new_value_data.len();
+
     // Retrieve snapshot root data set
     let root_value_data_set = match overlay.get(&db_roots.tree, &latest_root_data) {
         Ok(data) => data,
@@ -365,12 +370,18 @@ pub(crate) fn sparse_merkle_insert_batch(
     }
 
     // Subtract used gas.
-    // Here we count:
-    // * The number of nullifiers we inserted into the DB
     drop(overlay);
     drop(lock);
     drop(db_handles);
-    env.subtract_gas(&mut store, inserted_nullifiers as u64);
+
+    // Compute cost: one Poseidon hash per tree level per nullifier.
+    let compute_gas = nullifiers.len() as u64 * SMT_FP_DEPTH as u64 * POSEIDON_HASH_GAS;
+
+    // Storage cost: inserted nullifiers, new value data, and root data.
+    let storage_bytes = inserted_nullifiers as u64 + new_value_data_len as u64 + 32;
+    let storage_gas = storage_bytes * WRITE_GAS_PER_BYTE;
+
+    env.subtract_gas(&mut store, compute_gas.saturating_add(storage_gas));
 
     wasm::entrypoint::SUCCESS
 }