mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-07 22:04:03 -05:00
script/research/tx-replayer: add wasm, zkp and sig command line flags inorder to verify either wasm runtime, zkp or signature part of the transaction inorder to see resources usage for each parts of a tx verification
This commit is contained in:
@@ -12,13 +12,14 @@ edition = "2024"
|
||||
[dependencies]
|
||||
darkfi = {path = "../../../", features = ["validator"]}
|
||||
darkfi-sdk = {path = "../../../src/sdk"}
|
||||
darkfi-serial = {path = "../../../src/serial"}
|
||||
sled-overlay = {version = "0.1.10"}
|
||||
smol = {version = "2.0.2"}
|
||||
clap = {version = "4.4.11", features = ["derive"]}
|
||||
|
||||
[patch.crates-io]
|
||||
halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v031"}
|
||||
halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v031"}
|
||||
halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v032"}
|
||||
halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v032"}
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
36
script/research/tx-replayer/README.md
Normal file
36
script/research/tx-replayer/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Tx-Replayer
|
||||
|
||||
A lightweight transaction replay tool for debugging and analyzing
|
||||
transactions, as well as measuring resource usage during transaction
|
||||
verification with profiler tools such as
|
||||
[heaptrack](https://github.com/KDE/heaptrack) and
|
||||
[samply](https://github.com/mstange/samply).
|
||||
|
||||
**Disclaimer:** Use this tool only on a copy of your database.
|
||||
Running it on a live database may cause data loss or corruption.
|
||||
|
||||
## Usage
|
||||
Build
|
||||
```
|
||||
% make
|
||||
```
|
||||
To replay the whole transaction verification step.
|
||||
```
|
||||
% ./tx-replayer --database-path [DATABASE_PATH] --tx-hash [TX_HASH]
|
||||
```
|
||||
To replay only the Zk proof verification part.
|
||||
```
|
||||
% ./tx-replayer --zkp --database-path [DATABASE_PATH] --tx-hash [TX_HASH]
|
||||
```
|
||||
To replay only the wasm Runtime verification part.
|
||||
```
|
||||
% ./tx-replayer --wasm --database-path [DATABASE_PATH] --tx-hash [TX_HASH]
|
||||
```
|
||||
To replay only the signature verification part.
|
||||
```
|
||||
% ./tx-replayer --sig --database-path [DATABASE_PATH] --tx-hash [TX_HASH]
|
||||
```
|
||||
You can run `samply` to see the CPU usage of the transaction verification.
|
||||
```
|
||||
% samply record ./tx-replayer --database-path [DATABASE_PATH] --tx-hash [TX_HASH]
|
||||
```
|
||||
@@ -1,13 +1,48 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2026 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use clap::Parser;
|
||||
use darkfi::{
|
||||
blockchain::{Blockchain, BlockchainOverlay, BlockchainOverlayPtr},
|
||||
blockchain::{
|
||||
Blockchain, BlockchainOverlay, BlockchainOverlayPtr, block_store::append_tx_to_merkle_tree,
|
||||
},
|
||||
cli_desc,
|
||||
error::TxVerifyFailed,
|
||||
runtime::vm_runtime::Runtime,
|
||||
tx::{MAX_TX_CALLS, MIN_TX_CALLS, Transaction},
|
||||
util::path::expand_path,
|
||||
validator::verification::verify_transaction,
|
||||
validator::{
|
||||
fees::{GasData, PALLAS_SCHNORR_SIGNATURE_FEE, circuit_gas_use, compute_fee},
|
||||
verification::verify_transaction,
|
||||
},
|
||||
zk::VerifyingKey,
|
||||
};
|
||||
use darkfi_sdk::{crypto::MerkleTree, tx::TransactionHash};
|
||||
use std::collections::HashMap;
|
||||
use darkfi_sdk::{
|
||||
crypto::{ContractId, MerkleTree, PublicKey},
|
||||
dark_tree::dark_forest_leaf_vec_integrity_check,
|
||||
deploy::DeployParamsV1,
|
||||
pasta::pallas,
|
||||
tx::TransactionHash,
|
||||
};
|
||||
use darkfi_serial::{AsyncDecodable, AsyncEncodable, deserialize_async, serialize_async};
|
||||
use smol::io::Cursor;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(about = cli_desc!())]
|
||||
@@ -16,6 +51,12 @@ struct Args {
|
||||
database_path: String,
|
||||
#[arg(short, long)]
|
||||
tx_hash: String,
|
||||
#[arg(long, conflicts_with_all = ["zkp", "sig"])]
|
||||
wasm: bool,
|
||||
#[arg(long, conflicts_with_all = ["wasm", "sig"])]
|
||||
zkp: bool,
|
||||
#[arg(long, conflicts_with_all = ["wasm", "zkp"])]
|
||||
sig: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -31,19 +72,64 @@ async fn replay_tx(args: Args) {
|
||||
|
||||
let blockchain = Blockchain::new(&sled_db).unwrap();
|
||||
let txh: TransactionHash = args.tx_hash.parse().unwrap();
|
||||
let tx = blockchain.transactions.get(&[txh], true).unwrap().first().unwrap().clone().unwrap();
|
||||
|
||||
let (tx_height, _) =
|
||||
blockchain.transactions.get_location(&[txh], true).unwrap().first().unwrap().unwrap();
|
||||
let block_header_hash =
|
||||
blockchain.blocks.get_order(&[tx_height], true).unwrap().first().unwrap().unwrap();
|
||||
// Get all the transactions in the block of our target tx
|
||||
let block = blockchain
|
||||
.blocks
|
||||
.get(&[block_header_hash], true)
|
||||
.unwrap()
|
||||
.first()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.unwrap();
|
||||
let txs: Vec<Transaction> = blockchain
|
||||
.transactions
|
||||
.get(&block.txs, true)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|t| t.unwrap())
|
||||
.collect();
|
||||
|
||||
let (overlay, new_height) = rollback_database(&blockchain, txh).await;
|
||||
|
||||
// Apply all transactions upto and including our target tx
|
||||
let mut tree = MerkleTree::new(1);
|
||||
for tx in txs {
|
||||
perform_tx_verification(&tx, new_height, &overlay, &mut tree, &args).await;
|
||||
// We have applied our target tx so let's bail out
|
||||
if tx.hash() == txh {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn perform_tx_verification(
|
||||
tx: &Transaction,
|
||||
new_height: u32,
|
||||
overlay: &BlockchainOverlayPtr,
|
||||
tree: &mut MerkleTree,
|
||||
args: &Args,
|
||||
) {
|
||||
let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
|
||||
for call in &tx.calls {
|
||||
vks.insert(call.data.contract_id.to_bytes(), HashMap::new());
|
||||
}
|
||||
|
||||
let result =
|
||||
verify_transaction(&overlay, new_height, 2, &tx, &mut MerkleTree::new(1), &mut vks, true)
|
||||
let result = if args.wasm {
|
||||
verify_transaction_wasm(overlay, new_height, 2, tx, tree, &mut vks, true).await.unwrap()
|
||||
} else if args.zkp {
|
||||
verify_transaction_zkps(overlay, new_height, 2, tx, tree, &mut vks, true).await.unwrap()
|
||||
} else if args.sig {
|
||||
verify_transaction_signatures(overlay, new_height, 2, tx, tree, &mut vks, true)
|
||||
.await
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
} else {
|
||||
verify_transaction(overlay, new_height, 2, tx, tree, &mut vks, true).await.unwrap()
|
||||
};
|
||||
|
||||
println!("Verify Transaction Result: {:?}", result);
|
||||
}
|
||||
@@ -76,3 +162,488 @@ async fn rollback_database(
|
||||
|
||||
(overlay, new_height)
|
||||
}
|
||||
|
||||
async fn verify_transaction_wasm(
|
||||
overlay: &BlockchainOverlayPtr,
|
||||
verifying_block_height: u32,
|
||||
block_target: u32,
|
||||
tx: &Transaction,
|
||||
tree: &mut MerkleTree,
|
||||
_verifying_keys: &mut HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
|
||||
verify_fee: bool,
|
||||
) -> darkfi::Result<GasData> {
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
// Create a FeeData instance to hold the calculated fee data
|
||||
let mut gas_data = GasData::default();
|
||||
|
||||
// Verify calls indexes integrity
|
||||
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))?;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return Err(TxVerifyFailed::InvalidFee.into())
|
||||
}
|
||||
|
||||
found_fee = true;
|
||||
fee_call_idx = call_idx;
|
||||
}
|
||||
|
||||
if !found_fee {
|
||||
return Err(TxVerifyFailed::InvalidFee.into())
|
||||
}
|
||||
}
|
||||
|
||||
// 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![];
|
||||
|
||||
// Iterate over all calls to get the metadata
|
||||
for (idx, call) in tx.calls.iter().enumerate() {
|
||||
// Transaction must not contain a Pow reward call
|
||||
if call.data.is_money_pow_reward() {
|
||||
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)
|
||||
};
|
||||
|
||||
let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
|
||||
let mut runtime = Runtime::new(
|
||||
&wasm,
|
||||
overlay.clone(),
|
||||
call.data.contract_id,
|
||||
verifying_block_height,
|
||||
block_target,
|
||||
tx_hash,
|
||||
call_idx,
|
||||
)?;
|
||||
|
||||
// After getting the metadata, we run the "exec" function with the same runtime
|
||||
// and the same payload. We keep the returned state update in a buffer, prefixed
|
||||
// by the call function ID, enforcing the state update function in the contract.
|
||||
let mut state_update = vec![call.data.data[0]];
|
||||
state_update.append(&mut runtime.exec(call_payload)?);
|
||||
|
||||
// If that was successful, we apply the state update in the ephemeral overlay.
|
||||
runtime.apply(&state_update)?;
|
||||
|
||||
// If this call is supposed to deploy a new contract, we have to instantiate
|
||||
// a new `Runtime` and run its deploy function.
|
||||
if call.data.is_deployment()
|
||||
/* DeployV1 */
|
||||
{
|
||||
// Deserialize the deployment parameters
|
||||
let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
|
||||
let deploy_cid = ContractId::derive_public(deploy_params.public_key);
|
||||
|
||||
// Instantiate the new deployment runtime
|
||||
let mut deploy_runtime = Runtime::new(
|
||||
&deploy_params.wasm_bincode,
|
||||
overlay.clone(),
|
||||
deploy_cid,
|
||||
verifying_block_height,
|
||||
block_target,
|
||||
tx_hash,
|
||||
call_idx,
|
||||
)?;
|
||||
|
||||
deploy_runtime.deploy(&deploy_params.ix)?;
|
||||
|
||||
let deploy_gas_used = deploy_runtime.gas_used();
|
||||
gas_data.deployments += deploy_gas_used;
|
||||
}
|
||||
|
||||
// At this point we're done with the call and move on to the next one.
|
||||
// Accumulate the WASM gas used.
|
||||
let wasm_gas_used = runtime.gas_used();
|
||||
|
||||
// Append the used wasm gas
|
||||
gas_data.wasm += wasm_gas_used;
|
||||
}
|
||||
|
||||
// Store the calculated total gas used to avoid recalculating it for subsequent uses
|
||||
let total_gas_used = gas_data.total_gas_used();
|
||||
|
||||
if verify_fee {
|
||||
// Deserialize the fee call to find the paid fee
|
||||
let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Err(TxVerifyFailed::InvalidFee.into()),
|
||||
};
|
||||
|
||||
// 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 {
|
||||
return Err(TxVerifyFailed::InsufficientFee.into())
|
||||
}
|
||||
|
||||
// Store paid fee
|
||||
gas_data.paid = fee;
|
||||
}
|
||||
|
||||
// Append hash to merkle tree
|
||||
append_tx_to_merkle_tree(tree, tx);
|
||||
|
||||
Ok(gas_data)
|
||||
}
|
||||
|
||||
async fn verify_transaction_zkps(
|
||||
overlay: &BlockchainOverlayPtr,
|
||||
verifying_block_height: u32,
|
||||
block_target: u32,
|
||||
tx: &Transaction,
|
||||
tree: &mut MerkleTree,
|
||||
verifying_keys: &mut HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
|
||||
verify_fee: bool,
|
||||
) -> darkfi::Result<GasData> {
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
// Create a FeeData instance to hold the calculated fee data
|
||||
let mut gas_data = GasData::default();
|
||||
|
||||
// Verify calls indexes integrity
|
||||
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))?;
|
||||
}
|
||||
|
||||
// Table of public inputs used for ZK proof verification
|
||||
let mut zkp_table = vec![];
|
||||
// Table of public keys used for signature verification
|
||||
let mut sig_table = vec![];
|
||||
|
||||
// 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 {
|
||||
return Err(TxVerifyFailed::InvalidFee.into())
|
||||
}
|
||||
|
||||
found_fee = true;
|
||||
fee_call_idx = call_idx;
|
||||
}
|
||||
|
||||
if !found_fee {
|
||||
return Err(TxVerifyFailed::InvalidFee.into())
|
||||
}
|
||||
}
|
||||
|
||||
// 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![];
|
||||
|
||||
// Iterate over all calls to get the metadata
|
||||
for (idx, call) in tx.calls.iter().enumerate() {
|
||||
// Transaction must not contain a Pow reward call
|
||||
if call.data.is_money_pow_reward() {
|
||||
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)
|
||||
};
|
||||
|
||||
let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
|
||||
let mut runtime = Runtime::new(
|
||||
&wasm,
|
||||
overlay.clone(),
|
||||
call.data.contract_id,
|
||||
verifying_block_height,
|
||||
block_target,
|
||||
tx_hash,
|
||||
call_idx,
|
||||
)?;
|
||||
|
||||
let metadata = runtime.metadata(call_payload)?;
|
||||
|
||||
// Decode the metadata retrieved from the execution
|
||||
let mut decoder = Cursor::new(&metadata);
|
||||
|
||||
// The tuple is (zkas_ns, public_inputs)
|
||||
let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
|
||||
AsyncDecodable::decode_async(&mut decoder).await?;
|
||||
let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
|
||||
|
||||
if decoder.position() != metadata.len() as u64 {
|
||||
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
||||
}
|
||||
|
||||
// Here we'll look up verifying keys and insert them into the per-contract map.
|
||||
// TODO: This vk map can potentially use a lot of RAM. Perhaps load keys on-demand at verification time?
|
||||
for (zkas_ns, _) in &zkp_pub {
|
||||
let inner_vk_map = verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
|
||||
|
||||
// TODO: This will be a problem in case of ::deploy, unless we force a different
|
||||
// namespace and disable updating 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)?;
|
||||
|
||||
inner_vk_map.insert(zkas_ns.to_string(), vk);
|
||||
circuits_to_verify.push(zkbin);
|
||||
}
|
||||
|
||||
zkp_table.push(zkp_pub);
|
||||
sig_table.push(sig_pub);
|
||||
|
||||
// At this point we're done with the call and move on to the next one.
|
||||
// Accumulate the WASM gas used.
|
||||
let wasm_gas_used = runtime.gas_used();
|
||||
|
||||
// Append the used wasm gas
|
||||
gas_data.wasm += wasm_gas_used;
|
||||
}
|
||||
|
||||
// The ZK circuit fee is calculated using a function in validator/fees.rs
|
||||
for zkbin in circuits_to_verify.iter() {
|
||||
let zk_circuit_gas_used = circuit_gas_use(zkbin);
|
||||
// Append the used zk circuit gas
|
||||
gas_data.zk_circuits += zk_circuit_gas_used;
|
||||
}
|
||||
|
||||
// Store the calculated total gas used to avoid recalculating it for subsequent uses
|
||||
let total_gas_used = gas_data.total_gas_used();
|
||||
|
||||
if verify_fee {
|
||||
// Deserialize the fee call to find the paid fee
|
||||
let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Err(TxVerifyFailed::InvalidFee.into()),
|
||||
};
|
||||
|
||||
// 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 {
|
||||
return Err(TxVerifyFailed::InsufficientFee.into())
|
||||
}
|
||||
|
||||
// Store paid fee
|
||||
gas_data.paid = fee;
|
||||
}
|
||||
|
||||
if tx.verify_zkps(verifying_keys, zkp_table).await.is_err() {
|
||||
return Err(TxVerifyFailed::InvalidZkProof.into())
|
||||
}
|
||||
|
||||
// Append hash to merkle tree
|
||||
append_tx_to_merkle_tree(tree, tx);
|
||||
|
||||
Ok(gas_data)
|
||||
}
|
||||
|
||||
async fn verify_transaction_signatures(
|
||||
overlay: &BlockchainOverlayPtr,
|
||||
verifying_block_height: u32,
|
||||
block_target: u32,
|
||||
tx: &Transaction,
|
||||
tree: &mut MerkleTree,
|
||||
_verifying_keys: &mut HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
|
||||
verify_fee: bool,
|
||||
) -> darkfi::Result<GasData> {
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
// Create a FeeData instance to hold the calculated fee data
|
||||
let mut gas_data = GasData::default();
|
||||
|
||||
// Verify calls indexes integrity
|
||||
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))?;
|
||||
}
|
||||
|
||||
// Table of public inputs used for ZK proof verification
|
||||
let mut zkp_table = vec![];
|
||||
// Table of public keys used for signature verification
|
||||
let mut sig_table = vec![];
|
||||
|
||||
// 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 {
|
||||
return Err(TxVerifyFailed::InvalidFee.into())
|
||||
}
|
||||
|
||||
found_fee = true;
|
||||
fee_call_idx = call_idx;
|
||||
}
|
||||
|
||||
if !found_fee {
|
||||
return Err(TxVerifyFailed::InvalidFee.into())
|
||||
}
|
||||
}
|
||||
|
||||
// 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![];
|
||||
|
||||
// Iterate over all calls to get the metadata
|
||||
for (idx, call) in tx.calls.iter().enumerate() {
|
||||
// Transaction must not contain a Pow reward call
|
||||
if call.data.is_money_pow_reward() {
|
||||
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)
|
||||
};
|
||||
|
||||
let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
|
||||
let mut runtime = Runtime::new(
|
||||
&wasm,
|
||||
overlay.clone(),
|
||||
call.data.contract_id,
|
||||
verifying_block_height,
|
||||
block_target,
|
||||
tx_hash,
|
||||
call_idx,
|
||||
)?;
|
||||
|
||||
let metadata = runtime.metadata(call_payload)?;
|
||||
|
||||
// Decode the metadata retrieved from the execution
|
||||
let mut decoder = Cursor::new(&metadata);
|
||||
|
||||
// The tuple is (zkas_ns, public_inputs)
|
||||
let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
|
||||
AsyncDecodable::decode_async(&mut decoder).await?;
|
||||
let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
|
||||
|
||||
if decoder.position() != metadata.len() as u64 {
|
||||
return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
||||
}
|
||||
|
||||
zkp_table.push(zkp_pub);
|
||||
sig_table.push(sig_pub);
|
||||
|
||||
// At this point we're done with the call and move on to the next one.
|
||||
// Accumulate the WASM gas used.
|
||||
let wasm_gas_used = runtime.gas_used();
|
||||
|
||||
// Append the used wasm gas
|
||||
gas_data.wasm += wasm_gas_used;
|
||||
}
|
||||
|
||||
// The signature fee is tx_size + fixed_sig_fee * n_signatures
|
||||
gas_data.signatures = (PALLAS_SCHNORR_SIGNATURE_FEE * tx.signatures.len() as u64) +
|
||||
serialize_async(tx).await.len() as u64;
|
||||
|
||||
// Store the calculated total gas used to avoid recalculating it for subsequent uses
|
||||
let total_gas_used = gas_data.total_gas_used();
|
||||
|
||||
if verify_fee {
|
||||
// Deserialize the fee call to find the paid fee
|
||||
let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
|
||||
Ok(v) => v,
|
||||
Err(_) => return Err(TxVerifyFailed::InvalidFee.into()),
|
||||
};
|
||||
|
||||
// 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 {
|
||||
return Err(TxVerifyFailed::InsufficientFee.into())
|
||||
}
|
||||
|
||||
// Store paid fee
|
||||
gas_data.paid = fee;
|
||||
}
|
||||
|
||||
// When we're done looping and executing over the tx's contract calls and
|
||||
// (optionally) made sure that enough fee was paid, we now move on with
|
||||
// verification. First we verify the transaction signatures and then we
|
||||
// verify any accompanying ZK proofs.
|
||||
if sig_table.len() != tx.signatures.len() {
|
||||
return Err(TxVerifyFailed::MissingSignatures.into())
|
||||
}
|
||||
|
||||
if tx.verify_sigs(sig_table).is_err() {
|
||||
return Err(TxVerifyFailed::InvalidSignature.into())
|
||||
}
|
||||
|
||||
// Append hash to merkle tree
|
||||
append_tx_to_merkle_tree(tree, tx);
|
||||
|
||||
Ok(gas_data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user