darkfi/validator/
verification.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::collections::HashMap;
20
21use darkfi_sdk::{
22    blockchain::block_version,
23    crypto::{
24        schnorr::{SchnorrPublic, Signature},
25        ContractId, MerkleTree, PublicKey,
26    },
27    dark_tree::dark_forest_leaf_vec_integrity_check,
28    deploy::DeployParamsV1,
29    monotree::{self, Monotree},
30    pasta::pallas,
31};
32use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
33use num_bigint::BigUint;
34use sled_overlay::SledDbOverlayStateDiff;
35use smol::io::Cursor;
36use tracing::{debug, error, warn};
37
38use crate::{
39    blockchain::{
40        block_store::append_tx_to_merkle_tree, header_store::PowData::DarkFi, BlockInfo,
41        Blockchain, BlockchainOverlayPtr, HeaderHash,
42    },
43    error::TxVerifyFailed,
44    runtime::vm_runtime::Runtime,
45    tx::{Transaction, MAX_TX_CALLS, MIN_TX_CALLS},
46    validator::{
47        consensus::{Consensus, Fork, Proposal, BLOCK_GAS_LIMIT},
48        fees::{circuit_gas_use, compute_fee, GasData, PALLAS_SCHNORR_SIGNATURE_FEE},
49        pow::PoWModule,
50    },
51    zk::VerifyingKey,
52    Error, Result,
53};
54
55/// Verify given genesis [`BlockInfo`], and apply it to the provided overlay.
56pub async fn verify_genesis_block(
57    overlay: &BlockchainOverlayPtr,
58    block: &BlockInfo,
59    block_target: u32,
60) -> Result<()> {
61    let block_hash = block.hash().as_string();
62    debug!(target: "validator::verification::verify_genesis_block", "Validating genesis block {block_hash}");
63
64    // Check if block already exists
65    if overlay.lock().unwrap().has_block(block)? {
66        return Err(Error::BlockAlreadyExists(block_hash))
67    }
68
69    // Block height must be 0
70    if block.header.height != 0 {
71        return Err(Error::BlockIsInvalid(block_hash))
72    }
73
74    // Block version must be correct
75    if block.header.version != block_version(block.header.height) {
76        return Err(Error::BlockIsInvalid(block_hash))
77    }
78
79    // Block must use Darkfi native Proof of Work data
80    match block.header.pow_data {
81        DarkFi => { /* do nothing */ }
82        _ => return Err(Error::BlockIsInvalid(block_hash)),
83    }
84
85    // Verify transactions vector contains at least one(producers) transaction
86    if block.txs.is_empty() {
87        return Err(Error::BlockContainsNoTransactions(block_hash))
88    }
89
90    // Genesis producer transaction must be the Transaction::default() one(empty)
91    let producer_tx = block.txs.last().unwrap();
92    if producer_tx != &Transaction::default() {
93        error!(target: "validator::verification::verify_genesis_block", "Genesis producer transaction is not default one");
94        return Err(TxVerifyFailed::ErroneousTxs(vec![producer_tx.clone()]).into())
95    }
96
97    // Verify transactions, exluding producer(last) one/
98    // Genesis block doesn't check for fees
99    let mut tree = MerkleTree::new(1);
100    let txs = &block.txs[..block.txs.len() - 1];
101    if let Err(e) =
102        verify_transactions(overlay, block.header.height, block_target, txs, &mut tree, false).await
103    {
104        warn!(
105            target: "validator::verification::verify_genesis_block",
106            "[VALIDATOR] Erroneous transactions found in set",
107        );
108        overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
109        return Err(e)
110    }
111
112    // Append producer transaction to the tree and check tree matches header one
113    append_tx_to_merkle_tree(&mut tree, producer_tx);
114    if tree.root(0).unwrap() != block.header.transactions_root {
115        error!(target: "validator::verification::verify_genesis_block", "Genesis Merkle tree is invalid");
116        return Err(Error::BlockIsInvalid(block_hash))
117    }
118
119    // Verify header contracts states root
120    let state_monotree = overlay.lock().unwrap().contracts.get_state_monotree()?;
121    let Some(state_root) = state_monotree.get_headroot()? else {
122        return Err(Error::ContractsStatesRootNotFoundError);
123    };
124    if state_root != block.header.state_root {
125        return Err(Error::ContractsStatesRootError(
126            blake3::Hash::from_bytes(state_root).to_string(),
127            blake3::Hash::from_bytes(block.header.state_root).to_string(),
128        ));
129    }
130
131    // Genesis producer signature must be the Signature::dummy() one(empty)
132    if block.signature != Signature::dummy() {
133        error!(target: "validator::verification::verify_genesis_block", "Genesis producer signature is not dummy one");
134        return Err(Error::InvalidSignature)
135    }
136
137    // Insert block
138    overlay.lock().unwrap().add_block(block)?;
139
140    debug!(target: "validator::verification::verify_genesis_block", "Genesis block {block_hash} verified successfully");
141    Ok(())
142}
143
144/// Validate provided block according to set rules.
145///
146/// A block is considered valid when the following rules apply:
147///     1. Block version is correct for its height
148///     2. Previous hash is equal to the hash of the provided previous block
149///     3. Block height increments previous block height by 1
150///     4. Timestamp is valid based on PoWModule validation
151///     5. Block header Proof of Work data are valid
152///     6. Block hash is valid based on PoWModule validation
153/// Additional validity rules can be applied.
154pub fn validate_block(block: &BlockInfo, previous: &BlockInfo, module: &PoWModule) -> Result<()> {
155    // Check block version (1)
156    if block.header.version != block_version(block.header.height) {
157        return Err(Error::BlockIsInvalid(block.hash().as_string()))
158    }
159
160    // Check previous hash (2)
161    if block.header.previous != previous.hash() {
162        return Err(Error::BlockIsInvalid(block.hash().as_string()))
163    }
164
165    // Check heights are incremental (3)
166    if block.header.height != previous.header.height + 1 {
167        return Err(Error::BlockIsInvalid(block.hash().as_string()))
168    }
169
170    // Check timestamp validity (4)
171    if !module.verify_timestamp_by_median(block.header.timestamp) {
172        return Err(Error::BlockIsInvalid(block.hash().as_string()))
173    }
174
175    // Check PoW data validty (5)
176    if !block.header.validate_powdata() {
177        return Err(Error::BlockIsInvalid(block.hash().as_string()))
178    }
179
180    // Check block hash corresponds to next  mine target (6)
181    module.verify_block_hash(&block.header)?;
182
183    Ok(())
184}
185
186/// A blockchain is considered valid, when every block is valid,
187/// based on validate_block checks.
188/// Be careful as this will try to load everything in memory.
189pub fn validate_blockchain(
190    blockchain: &Blockchain,
191    pow_target: u32,
192    pow_fixed_difficulty: Option<BigUint>,
193) -> Result<()> {
194    // Generate a PoW module
195    let mut module = PoWModule::new(blockchain.clone(), pow_target, pow_fixed_difficulty, Some(0))?;
196
197    // We use block order store here so we have all blocks in order
198    let blocks = blockchain.blocks.get_all_order()?;
199    for (index, block) in blocks[1..].iter().enumerate() {
200        let full_blocks = blockchain.get_blocks_by_hash(&[blocks[index].1, block.1])?;
201        let full_block = &full_blocks[1];
202        validate_block(full_block, &full_blocks[0], &module)?;
203        // Update PoW module
204        module.append(&full_block.header, &module.next_difficulty()?)?;
205    }
206
207    Ok(())
208}
209
210/// Verify given [`BlockInfo`], and apply it to the provided overlay.
211pub async fn verify_block(
212    overlay: &BlockchainOverlayPtr,
213    diffs: &[SledDbOverlayStateDiff],
214    module: &PoWModule,
215    state_monotree: &mut Monotree<monotree::MemoryDb>,
216    block: &BlockInfo,
217    previous: &BlockInfo,
218    verify_fees: bool,
219) -> Result<()> {
220    let block_hash = block.hash();
221    debug!(target: "validator::verification::verify_block", "Validating block {block_hash}");
222
223    // Check if block already exists
224    if overlay.lock().unwrap().has_block(block)? {
225        return Err(Error::BlockAlreadyExists(block_hash.as_string()))
226    }
227
228    // Validate block, using its previous
229    validate_block(block, previous, module)?;
230
231    // Verify transactions vector contains at least one(producers) transaction
232    if block.txs.is_empty() {
233        return Err(Error::BlockContainsNoTransactions(block_hash.as_string()))
234    }
235
236    // Verify transactions, exluding producer(last) one
237    let mut tree = MerkleTree::new(1);
238    let txs = &block.txs[..block.txs.len() - 1];
239    let e = verify_transactions(
240        overlay,
241        block.header.height,
242        module.target,
243        txs,
244        &mut tree,
245        verify_fees,
246    )
247    .await;
248    if let Err(e) = e {
249        warn!(
250            target: "validator::verification::verify_block",
251            "[VALIDATOR] Erroneous transactions found in set",
252        );
253        overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
254        return Err(e)
255    }
256
257    // Verify producer transaction
258    let public_key = verify_producer_transaction(
259        overlay,
260        block.header.height,
261        module.target,
262        block.txs.last().unwrap(),
263        &mut tree,
264    )
265    .await?;
266
267    // Verify transactions merkle tree root matches header one
268    if tree.root(0).unwrap() != block.header.transactions_root {
269        error!(target: "validator::verification::verify_block", "Block Merkle tree root is invalid");
270        return Err(Error::BlockIsInvalid(block_hash.as_string()))
271    }
272
273    // Update the provided contracts states monotree and verify header contracts states root
274    let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(diffs)?;
275    overlay.lock().unwrap().contracts.update_state_monotree(&diff, state_monotree)?;
276    let Some(state_root) = state_monotree.get_headroot()? else {
277        return Err(Error::ContractsStatesRootNotFoundError);
278    };
279    if state_root != block.header.state_root {
280        return Err(Error::ContractsStatesRootError(
281            blake3::Hash::from_bytes(state_root).to_string(),
282            blake3::Hash::from_bytes(block.header.state_root).to_string(),
283        ));
284    }
285
286    // Verify producer signature
287    verify_producer_signature(block, &public_key)?;
288
289    // Insert block
290    overlay.lock().unwrap().add_block(block)?;
291
292    debug!(target: "validator::verification::verify_block", "Block {block_hash} verified successfully");
293    Ok(())
294}
295
296/// Verify given checkpoint [`BlockInfo`], and apply it to the provided overlay.
297pub async fn verify_checkpoint_block(
298    overlay: &BlockchainOverlayPtr,
299    diffs: &[SledDbOverlayStateDiff],
300    state_monotree: &mut Monotree<monotree::MemoryDb>,
301    block: &BlockInfo,
302    header: &HeaderHash,
303    block_target: u32,
304) -> Result<()> {
305    let block_hash = block.hash();
306    debug!(target: "validator::verification::verify_checkpoint_block", "Validating block {block_hash}");
307
308    // Check if block already exists
309    if overlay.lock().unwrap().has_block(block)? {
310        return Err(Error::BlockAlreadyExists(block_hash.as_string()))
311    }
312
313    // Check if block hash matches the expected(provided) one
314    if block_hash != *header {
315        error!(target: "validator::verification::verify_checkpoint_block", "Block hash doesn't match the expected one");
316        return Err(Error::BlockIsInvalid(block_hash.as_string()))
317    }
318
319    // Verify transactions vector contains at least one(producers) transaction
320    if block.txs.is_empty() {
321        return Err(Error::BlockContainsNoTransactions(block_hash.as_string()))
322    }
323
324    // Apply transactions, excluding producer(last) one
325    let mut tree = MerkleTree::new(1);
326    let txs = &block.txs[..block.txs.len() - 1];
327    let e = apply_transactions(overlay, block.header.height, block_target, txs, &mut tree).await;
328    if let Err(e) = e {
329        warn!(
330            target: "validator::verification::verify_checkpoint_block",
331            "[VALIDATOR] Erroneous transactions found in set",
332        );
333        overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
334        return Err(e)
335    }
336
337    // Apply producer transaction
338    let public_key = apply_producer_transaction(
339        overlay,
340        block.header.height,
341        block_target,
342        block.txs.last().unwrap(),
343        &mut tree,
344    )
345    .await?;
346
347    // Verify transactions merkle tree root matches header one
348    if tree.root(0).unwrap() != block.header.transactions_root {
349        error!(target: "validator::verification::verify_checkpoint_block", "Block Merkle tree root is invalid");
350        return Err(Error::BlockIsInvalid(block_hash.as_string()))
351    }
352
353    // Update the provided contracts states monotree and verify header contracts states root
354    let diff = overlay.lock().unwrap().overlay.lock().unwrap().diff(diffs)?;
355    overlay.lock().unwrap().contracts.update_state_monotree(&diff, state_monotree)?;
356    let Some(state_root) = state_monotree.get_headroot()? else {
357        return Err(Error::ContractsStatesRootNotFoundError);
358    };
359    if state_root != block.header.state_root {
360        return Err(Error::ContractsStatesRootError(
361            blake3::Hash::from_bytes(state_root).to_string(),
362            blake3::Hash::from_bytes(block.header.state_root).to_string(),
363        ));
364    }
365
366    // Verify producer signature
367    verify_producer_signature(block, &public_key)?;
368
369    // Insert block
370    overlay.lock().unwrap().add_block(block)?;
371
372    debug!(target: "validator::verification::verify_checkpoint_block", "Block {block_hash} verified successfully");
373    Ok(())
374}
375
376/// Verify block proposer signature, using the producer transaction signature as signing key
377/// over blocks header hash.
378pub fn verify_producer_signature(block: &BlockInfo, public_key: &PublicKey) -> Result<()> {
379    if !public_key.verify(block.header.hash().inner(), &block.signature) {
380        warn!(target: "validator::verification::verify_producer_signature", "Proposer {public_key} signature could not be verified");
381        return Err(Error::InvalidSignature)
382    }
383
384    Ok(())
385}
386
387/// Verify provided producer [`Transaction`].
388///
389/// Verify WASM execution, signatures, and ZK proofs and apply it to the provided,
390/// provided overlay. Returns transaction signature public key. Additionally,
391/// append its hash to the provided Merkle tree.
392pub async fn verify_producer_transaction(
393    overlay: &BlockchainOverlayPtr,
394    verifying_block_height: u32,
395    block_target: u32,
396    tx: &Transaction,
397    tree: &mut MerkleTree,
398) -> Result<PublicKey> {
399    let tx_hash = tx.hash();
400    debug!(target: "validator::verification::verify_producer_transaction", "Validating producer transaction {tx_hash}");
401
402    // Transaction must be a PoW reward one
403    if !tx.is_pow_reward() {
404        return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
405    }
406
407    // Retrieve first call from the transaction for further processing
408    let call = &tx.calls[0];
409
410    // Map of ZK proof verifying keys for the current transaction
411    let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
412
413    // Initialize the map
414    verifying_keys.insert(call.data.contract_id.to_bytes(), HashMap::new());
415
416    // Table of public inputs used for ZK proof verification
417    let mut zkp_table = vec![];
418    // Table of public keys used for signature verification
419    let mut sig_table = vec![];
420
421    debug!(target: "validator::verification::verify_producer_transaction", "Executing contract call");
422
423    // Write the actual payload data
424    let mut payload = vec![];
425    tx.calls.encode_async(&mut payload).await?; // Actual call data
426
427    debug!(target: "validator::verification::verify_producer_transaction", "Instantiating WASM runtime");
428    let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
429
430    let mut runtime = Runtime::new(
431        &wasm,
432        overlay.clone(),
433        call.data.contract_id,
434        verifying_block_height,
435        block_target,
436        tx_hash,
437        // Call index in producer tx is 0
438        0,
439    )?;
440
441    debug!(target: "validator::verification::verify_producer_transaction", "Executing \"metadata\" call");
442    let metadata = runtime.metadata(&payload)?;
443
444    // Decode the metadata retrieved from the execution
445    let mut decoder = Cursor::new(&metadata);
446
447    // The tuple is (zkas_ns, public_inputs)
448    let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
449        AsyncDecodable::decode_async(&mut decoder).await?;
450    let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
451
452    // Check that only one ZK proof and signature public key exist
453    if zkp_pub.len() != 1 || sig_pub.len() != 1 {
454        error!(target: "validator::verification::verify_producer_transaction", "Producer transaction contains multiple ZK proofs or signature public keys");
455        return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
456    }
457
458    // TODO: Make sure we've read all the bytes above.
459    debug!(target: "validator::verification::verify_producer_transaction", "Successfully executed \"metadata\" call");
460
461    // Here we'll look up verifying keys and insert them into the map.
462    debug!(target: "validator::verification::verify_producer_transaction", "Performing VerifyingKey lookups from the sled db");
463    for (zkas_ns, _) in &zkp_pub {
464        // TODO: verify this is correct behavior
465        let inner_vk_map = verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
466        if inner_vk_map.contains_key(zkas_ns.as_str()) {
467            continue
468        }
469
470        let (_zkbin, vk) =
471            overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
472
473        inner_vk_map.insert(zkas_ns.to_string(), vk);
474    }
475
476    zkp_table.push(zkp_pub);
477    let signature_public_key = *sig_pub.last().unwrap();
478    sig_table.push(sig_pub);
479
480    // After getting the metadata, we run the "exec" function with the same runtime
481    // and the same payload. We keep the returned state update in a buffer, prefixed
482    // by the call function ID, enforcing the state update function in the contract.
483    debug!(target: "validator::verification::verify_producer_transaction", "Executing \"exec\" call");
484    let mut state_update = vec![call.data.data[0]];
485    state_update.append(&mut runtime.exec(&payload)?);
486    debug!(target: "validator::verification::verify_producer_transaction", "Successfully executed \"exec\" call");
487
488    // If that was successful, we apply the state update in the ephemeral overlay.
489    debug!(target: "validator::verification::verify_producer_transaction", "Executing \"apply\" call");
490    runtime.apply(&state_update)?;
491    debug!(target: "validator::verification::verify_producer_transaction", "Successfully executed \"apply\" call");
492
493    // When we're done executing over the tx's contract call, we now move on with verification.
494    // First we verify the signatures as that's cheaper, and then finally we verify the ZK proofs.
495    debug!(target: "validator::verification::verify_producer_transaction", "Verifying signatures for transaction {tx_hash}");
496    if sig_table.len() != tx.signatures.len() {
497        error!(target: "validator::verification::verify_producer_transaction", "Incorrect number of signatures in tx {tx_hash}");
498        return Err(TxVerifyFailed::MissingSignatures.into())
499    }
500
501    // TODO: Go through the ZK circuits that have to be verified and account for the opcodes.
502
503    if let Err(e) = tx.verify_sigs(sig_table) {
504        error!(target: "validator::verification::verify_producer_transaction", "Signature verification for tx {tx_hash} failed: {e}");
505        return Err(TxVerifyFailed::InvalidSignature.into())
506    }
507
508    debug!(target: "validator::verification::verify_producer_transaction", "Signature verification successful");
509
510    debug!(target: "validator::verification::verify_producer_transaction", "Verifying ZK proofs for transaction {tx_hash}");
511    if let Err(e) = tx.verify_zkps(&verifying_keys, zkp_table).await {
512        error!(target: "validator::verification::verify_producer_transaction", "ZK proof verification for tx {tx_hash} failed: {e}");
513        return Err(TxVerifyFailed::InvalidZkProof.into())
514    }
515    debug!(target: "validator::verification::verify_producer_transaction", "ZK proof verification successful");
516
517    // Append hash to merkle tree
518    append_tx_to_merkle_tree(tree, tx);
519
520    debug!(target: "validator::verification::verify_producer_transaction", "Producer transaction {tx_hash} verified successfully");
521
522    Ok(signature_public_key)
523}
524
525/// Apply given producer [`Transaction`] to the provided overlay, without formal verification.
526/// Returns transaction signature public key. Additionally, append its hash to the provided Merkle tree.
527pub async fn apply_producer_transaction(
528    overlay: &BlockchainOverlayPtr,
529    verifying_block_height: u32,
530    block_target: u32,
531    tx: &Transaction,
532    tree: &mut MerkleTree,
533) -> Result<PublicKey> {
534    let tx_hash = tx.hash();
535    debug!(target: "validator::verification::apply_producer_transaction", "Applying producer transaction {tx_hash}");
536
537    // Producer transactions must contain a single, non-empty call
538    if !tx.is_single_call() {
539        return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
540    }
541
542    debug!(target: "validator::verification::apply_producer_transaction", "Executing contract call");
543
544    // Write the actual payload data
545    let mut payload = vec![];
546    tx.calls.encode_async(&mut payload).await?; // Actual call data
547
548    debug!(target: "validator::verification::apply_producer_transaction", "Instantiating WASM runtime");
549    let call = &tx.calls[0];
550    let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
551
552    let mut runtime = Runtime::new(
553        &wasm,
554        overlay.clone(),
555        call.data.contract_id,
556        verifying_block_height,
557        block_target,
558        tx_hash,
559        // Call index in producer tx is 0
560        0,
561    )?;
562
563    debug!(target: "validator::verification::apply_producer_transaction", "Executing \"metadata\" call");
564    let metadata = runtime.metadata(&payload)?;
565
566    // Decode the metadata retrieved from the execution
567    let mut decoder = Cursor::new(&metadata);
568
569    // The tuple is (zkas_ns, public_inputs)
570    let _: Vec<(String, Vec<pallas::Base>)> = AsyncDecodable::decode_async(&mut decoder).await?;
571    let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
572
573    // Check that only one ZK proof and signature public key exist
574    if sig_pub.len() != 1 {
575        error!(target: "validator::verification::apply_producer_transaction", "Producer transaction contains multiple ZK proofs or signature public keys");
576        return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
577    }
578
579    let signature_public_key = *sig_pub.last().unwrap();
580
581    // After getting the metadata, we run the "exec" function with the same runtime
582    // and the same payload. We keep the returned state update in a buffer, prefixed
583    // by the call function ID, enforcing the state update function in the contract.
584    debug!(target: "validator::verification::apply_producer_transaction", "Executing \"exec\" call");
585    let mut state_update = vec![call.data.data[0]];
586    state_update.append(&mut runtime.exec(&payload)?);
587    debug!(target: "validator::verification::apply_producer_transaction", "Successfully executed \"exec\" call");
588
589    // If that was successful, we apply the state update in the ephemeral overlay.
590    debug!(target: "validator::verification::apply_producer_transaction", "Executing \"apply\" call");
591    runtime.apply(&state_update)?;
592    debug!(target: "validator::verification::apply_producer_transaction", "Successfully executed \"apply\" call");
593
594    // Append hash to merkle tree
595    append_tx_to_merkle_tree(tree, tx);
596
597    debug!(target: "validator::verification::apply_producer_transaction", "Producer transaction {tx_hash} executed successfully");
598
599    Ok(signature_public_key)
600}
601
602/// Verify WASM execution, signatures, and ZK proofs for a given [`Transaction`],
603/// and apply it to the provided overlay. Additionally, append its hash to the
604/// provided Merkle tree.
605pub async fn verify_transaction(
606    overlay: &BlockchainOverlayPtr,
607    verifying_block_height: u32,
608    block_target: u32,
609    tx: &Transaction,
610    tree: &mut MerkleTree,
611    verifying_keys: &mut HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
612    verify_fee: bool,
613) -> Result<GasData> {
614    let tx_hash = tx.hash();
615    debug!(target: "validator::verification::verify_transaction", "Validating transaction {tx_hash}");
616
617    // Create a FeeData instance to hold the calculated fee data
618    let mut gas_data = GasData::default();
619
620    // Verify calls indexes integrity
621    if verify_fee {
622        dark_forest_leaf_vec_integrity_check(
623            &tx.calls,
624            Some(MIN_TX_CALLS + 1),
625            Some(MAX_TX_CALLS),
626        )?;
627    } else {
628        dark_forest_leaf_vec_integrity_check(&tx.calls, Some(MIN_TX_CALLS), Some(MAX_TX_CALLS))?;
629    }
630
631    // Table of public inputs used for ZK proof verification
632    let mut zkp_table = vec![];
633    // Table of public keys used for signature verification
634    let mut sig_table = vec![];
635
636    // Index of the Fee-paying call
637    let mut fee_call_idx = 0;
638
639    if verify_fee {
640        // Verify that there is a single money fee call in the transaction
641        let mut found_fee = false;
642        for (call_idx, call) in tx.calls.iter().enumerate() {
643            if !call.data.is_money_fee() {
644                continue
645            }
646
647            if found_fee {
648                error!(
649                    target: "validator::verification::verify_transcation",
650                    "[VALIDATOR] Transaction {tx_hash} contains multiple fee payment calls"
651                );
652                return Err(TxVerifyFailed::InvalidFee.into())
653            }
654
655            found_fee = true;
656            fee_call_idx = call_idx;
657        }
658
659        if !found_fee {
660            error!(
661                target: "validator::verification::verify_transcation",
662                "[VALIDATOR] Transaction {tx_hash} does not contain fee payment call"
663            );
664            return Err(TxVerifyFailed::InvalidFee.into())
665        }
666    }
667
668    // Write the transaction calls payload data
669    let mut payload = vec![];
670    tx.calls.encode_async(&mut payload).await?;
671
672    // Define a buffer in case we want to use a different payload in a specific call
673    let mut _call_payload = vec![];
674
675    // We'll also take note of all the circuits in a Vec so we can calculate their verification cost.
676    let mut circuits_to_verify = vec![];
677
678    // Iterate over all calls to get the metadata
679    for (idx, call) in tx.calls.iter().enumerate() {
680        debug!(target: "validator::verification::verify_transaction", "Executing contract call {idx}");
681
682        // Transaction must not contain a Pow reward call
683        if call.data.is_money_pow_reward() {
684            error!(target: "validator::verification::verify_transaction", "Reward transaction detected");
685            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
686        }
687
688        // Check if its the fee call so we only pass its payload
689        let (call_idx, call_payload) = if call.data.is_money_fee() {
690            _call_payload = vec![];
691            vec![call.clone()].encode_async(&mut _call_payload).await?;
692            (0_u8, &_call_payload)
693        } else {
694            (idx as u8, &payload)
695        };
696
697        debug!(target: "validator::verification::verify_transaction", "Instantiating WASM runtime");
698        let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
699        let mut runtime = Runtime::new(
700            &wasm,
701            overlay.clone(),
702            call.data.contract_id,
703            verifying_block_height,
704            block_target,
705            tx_hash,
706            call_idx,
707        )?;
708
709        debug!(target: "validator::verification::verify_transaction", "Executing \"metadata\" call");
710        let metadata = runtime.metadata(call_payload)?;
711
712        // Decode the metadata retrieved from the execution
713        let mut decoder = Cursor::new(&metadata);
714
715        // The tuple is (zkas_ns, public_inputs)
716        let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
717            AsyncDecodable::decode_async(&mut decoder).await?;
718        let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
719
720        if decoder.position() != metadata.len() as u64 {
721            error!(
722                target: "validator::verification::verify_transaction",
723                "[VALIDATOR] Failed decoding entire metadata buffer for {tx_hash}:{idx}"
724            );
725            return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
726        }
727
728        debug!(target: "validator::verification::verify_transaction", "Successfully executed \"metadata\" call");
729
730        // Here we'll look up verifying keys and insert them into the per-contract map.
731        // TODO: This vk map can potentially use a lot of RAM. Perhaps load keys on-demand at verification time?
732        debug!(target: "validator::verification::verify_transaction", "Performing VerifyingKey lookups from the sled db");
733        for (zkas_ns, _) in &zkp_pub {
734            let inner_vk_map = verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
735
736            // TODO: This will be a problem in case of ::deploy, unless we force a different
737            // namespace and disable updating existing circuit. Might be a smart idea to do
738            // so in order to have to care less about being able to verify historical txs.
739            if inner_vk_map.contains_key(zkas_ns.as_str()) {
740                continue
741            }
742
743            let (zkbin, vk) =
744                overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
745
746            inner_vk_map.insert(zkas_ns.to_string(), vk);
747            circuits_to_verify.push(zkbin);
748        }
749
750        zkp_table.push(zkp_pub);
751        sig_table.push(sig_pub);
752
753        // After getting the metadata, we run the "exec" function with the same runtime
754        // and the same payload. We keep the returned state update in a buffer, prefixed
755        // by the call function ID, enforcing the state update function in the contract.
756        debug!(target: "validator::verification::verify_transaction", "Executing \"exec\" call");
757        let mut state_update = vec![call.data.data[0]];
758        state_update.append(&mut runtime.exec(call_payload)?);
759        debug!(target: "validator::verification::verify_transaction", "Successfully executed \"exec\" call");
760
761        // If that was successful, we apply the state update in the ephemeral overlay.
762        debug!(target: "validator::verification::verify_transaction", "Executing \"apply\" call");
763        runtime.apply(&state_update)?;
764        debug!(target: "validator::verification::verify_transaction", "Successfully executed \"apply\" call");
765
766        // If this call is supposed to deploy a new contract, we have to instantiate
767        // a new `Runtime` and run its deploy function.
768        if call.data.is_deployment()
769        /* DeployV1 */
770        {
771            debug!(target: "validator::verification::verify_transaction", "Deploying new contract");
772            // Deserialize the deployment parameters
773            let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
774            let deploy_cid = ContractId::derive_public(deploy_params.public_key);
775
776            // Instantiate the new deployment runtime
777            let mut deploy_runtime = Runtime::new(
778                &deploy_params.wasm_bincode,
779                overlay.clone(),
780                deploy_cid,
781                verifying_block_height,
782                block_target,
783                tx_hash,
784                call_idx,
785            )?;
786
787            deploy_runtime.deploy(&deploy_params.ix)?;
788
789            let deploy_gas_used = deploy_runtime.gas_used();
790            debug!(target: "validator::verification::verify_transaction", "The gas used for deployment call {call:?} of transaction {tx_hash}: {deploy_gas_used}");
791            gas_data.deployments += deploy_gas_used;
792        }
793
794        // At this point we're done with the call and move on to the next one.
795        // Accumulate the WASM gas used.
796        let wasm_gas_used = runtime.gas_used();
797        debug!(target: "validator::verification::verify_transaction", "The gas used for WASM call {call:?} of transaction {tx_hash}: {wasm_gas_used}");
798
799        // Append the used wasm gas
800        gas_data.wasm += wasm_gas_used;
801    }
802
803    // The signature fee is tx_size + fixed_sig_fee * n_signatures
804    gas_data.signatures = (PALLAS_SCHNORR_SIGNATURE_FEE * tx.signatures.len() as u64) +
805        serialize_async(tx).await.len() as u64;
806    debug!(target: "validator::verification::verify_transaction", "The gas used for signature of transaction {tx_hash}: {}", gas_data.signatures);
807
808    // The ZK circuit fee is calculated using a function in validator/fees.rs
809    for zkbin in circuits_to_verify.iter() {
810        let zk_circuit_gas_used = circuit_gas_use(zkbin);
811        debug!(target: "validator::verification::verify_transaction", "The gas used for ZK circuit in namespace {} of transaction {tx_hash}: {zk_circuit_gas_used}", zkbin.namespace);
812
813        // Append the used zk circuit gas
814        gas_data.zk_circuits += zk_circuit_gas_used;
815    }
816
817    // Store the calculated total gas used to avoid recalculating it for subsequent uses
818    let total_gas_used = gas_data.total_gas_used();
819
820    if verify_fee {
821        // Deserialize the fee call to find the paid fee
822        let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
823            Ok(v) => v,
824            Err(e) => {
825                error!(
826                    target: "validator::verification::verify_transaction",
827                    "[VALIDATOR] Failed deserializing tx {tx_hash} fee call: {e}"
828                );
829                return Err(TxVerifyFailed::InvalidFee.into())
830            }
831        };
832
833        // Compute the required fee for this transaction
834        let required_fee = compute_fee(&total_gas_used);
835
836        // Check that enough fee has been paid for the used gas in this transaction
837        if required_fee > fee {
838            error!(
839                target: "validator::verification::verify_transaction",
840                "[VALIDATOR] Transaction {tx_hash} has insufficient fee. Required: {required_fee}, Paid: {fee}"
841            );
842            return Err(TxVerifyFailed::InsufficientFee.into())
843        }
844        debug!(target: "validator::verification::verify_transaction", "The gas paid for transaction {tx_hash}: {}", gas_data.paid);
845
846        // Store paid fee
847        gas_data.paid = fee;
848    }
849
850    // When we're done looping and executing over the tx's contract calls and
851    // (optionally) made sure that enough fee was paid, we now move on with
852    // verification. First we verify the transaction signatures and then we
853    // verify any accompanying ZK proofs.
854    debug!(target: "validator::verification::verify_transaction", "Verifying signatures for transaction {tx_hash}");
855    if sig_table.len() != tx.signatures.len() {
856        error!(
857            target: "validator::verification::verify_transaction",
858            "[VALIDATOR] Incorrect number of signatures in tx {tx_hash}"
859        );
860        return Err(TxVerifyFailed::MissingSignatures.into())
861    }
862
863    if let Err(e) = tx.verify_sigs(sig_table) {
864        error!(
865            target: "validator::verification::verify_transaction",
866            "[VALIDATOR] Signature verification for tx {tx_hash} failed: {e}"
867        );
868        return Err(TxVerifyFailed::InvalidSignature.into())
869    }
870    debug!(target: "validator::verification::verify_transaction", "Signature verification successful");
871
872    debug!(target: "validator::verification::verify_transaction", "Verifying ZK proofs for transaction {tx_hash}");
873    if let Err(e) = tx.verify_zkps(verifying_keys, zkp_table).await {
874        error!(
875            target: "validator::verification::verify_transaction",
876            "[VALIDATOR] ZK proof verification for tx {tx_hash} failed: {e}"
877        );
878        return Err(TxVerifyFailed::InvalidZkProof.into())
879    }
880    debug!(target: "validator::verification::verify_transaction", "ZK proof verification successful");
881
882    // Append hash to merkle tree
883    append_tx_to_merkle_tree(tree, tx);
884
885    debug!(target: "validator::verification::verify_transaction", "The total gas used for transaction {tx_hash}: {total_gas_used}");
886    debug!(target: "validator::verification::verify_transaction", "Transaction {tx_hash} verified successfully");
887    Ok(gas_data)
888}
889
890/// Apply given [`Transaction`] to the provided overlay.
891/// Additionally, append its hash to the provided Merkle tree.
892pub async fn apply_transaction(
893    overlay: &BlockchainOverlayPtr,
894    verifying_block_height: u32,
895    block_target: u32,
896    tx: &Transaction,
897    tree: &mut MerkleTree,
898) -> Result<()> {
899    let tx_hash = tx.hash();
900    debug!(target: "validator::verification::apply_transaction", "Applying transaction {tx_hash}");
901
902    // Write the transaction calls payload data
903    let mut payload = vec![];
904    tx.calls.encode_async(&mut payload).await?;
905
906    // Iterate over all calls to get the metadata
907    for (idx, call) in tx.calls.iter().enumerate() {
908        debug!(target: "validator::verification::apply_transaction", "Executing contract call {idx}");
909
910        debug!(target: "validator::verification::apply_transaction", "Instantiating WASM runtime");
911        let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
912        let mut runtime = Runtime::new(
913            &wasm,
914            overlay.clone(),
915            call.data.contract_id,
916            verifying_block_height,
917            block_target,
918            tx_hash,
919            idx as u8,
920        )?;
921
922        // Run the "exec" function. We keep the returned state update in a buffer, prefixed
923        // by the call function ID, enforcing the state update function in the contract.
924        debug!(target: "validator::verification::apply_transaction", "Executing \"exec\" call");
925        let mut state_update = vec![call.data.data[0]];
926        state_update.append(&mut runtime.exec(&payload)?);
927        debug!(target: "validator::verification::apply_transaction", "Successfully executed \"exec\" call");
928
929        // If that was successful, we apply the state update in the ephemeral overlay
930        debug!(target: "validator::verification::apply_transaction", "Executing \"apply\" call");
931        runtime.apply(&state_update)?;
932        debug!(target: "validator::verification::apply_transaction", "Successfully executed \"apply\" call");
933
934        // If this call is supposed to deploy a new contract, we have to instantiate
935        // a new `Runtime` and run its deploy function.
936        if call.data.is_deployment()
937        /* DeployV1 */
938        {
939            debug!(target: "validator::verification::apply_transaction", "Deploying new contract");
940            // Deserialize the deployment parameters
941            let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
942            let deploy_cid = ContractId::derive_public(deploy_params.public_key);
943
944            // Instantiate the new deployment runtime
945            let mut deploy_runtime = Runtime::new(
946                &deploy_params.wasm_bincode,
947                overlay.clone(),
948                deploy_cid,
949                verifying_block_height,
950                block_target,
951                tx_hash,
952                idx as u8,
953            )?;
954
955            deploy_runtime.deploy(&deploy_params.ix)?;
956        }
957    }
958
959    // Append hash to merkle tree
960    append_tx_to_merkle_tree(tree, tx);
961
962    debug!(target: "validator::verification::apply_transaction", "Transaction {tx_hash} applied successfully");
963    Ok(())
964}
965
966/// Verify a set of [`Transaction`] in sequence and apply them if all are valid.
967///
968/// In case any of the transactions fail, they will be returned to the caller as an error.
969/// If all transactions are valid, the function will return the total gas used and total
970/// paid fees from all the transactions. Additionally, their hash is appended to the provided
971/// Merkle tree.
972pub async fn verify_transactions(
973    overlay: &BlockchainOverlayPtr,
974    verifying_block_height: u32,
975    block_target: u32,
976    txs: &[Transaction],
977    tree: &mut MerkleTree,
978    verify_fees: bool,
979) -> Result<(u64, u64)> {
980    debug!(target: "validator::verification::verify_transactions", "Verifying {} transactions", txs.len());
981    if txs.is_empty() {
982        return Ok((0, 0))
983    }
984
985    // Tracker for failed txs
986    let mut erroneous_txs = vec![];
987
988    // Total gas accumulators
989    let mut total_gas_used = 0;
990    let mut total_gas_paid = 0;
991
992    // Map of ZK proof verifying keys for the current transaction batch
993    let mut vks: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
994
995    // Initialize the map
996    for tx in txs {
997        for call in &tx.calls {
998            vks.insert(call.data.contract_id.to_bytes(), HashMap::new());
999        }
1000    }
1001
1002    // Iterate over transactions and attempt to verify them
1003    for tx in txs {
1004        overlay.lock().unwrap().checkpoint();
1005        let gas_data = match verify_transaction(
1006            overlay,
1007            verifying_block_height,
1008            block_target,
1009            tx,
1010            tree,
1011            &mut vks,
1012            verify_fees,
1013        )
1014        .await
1015        {
1016            Ok(gas_values) => gas_values,
1017            Err(e) => {
1018                warn!(target: "validator::verification::verify_transactions", "Transaction verification failed: {e}");
1019                erroneous_txs.push(tx.clone());
1020                overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
1021                overlay.lock().unwrap().revert_to_checkpoint()?;
1022                continue
1023            }
1024        };
1025
1026        // Store the gas used by the verified transaction
1027        let tx_gas_used = gas_data.total_gas_used();
1028
1029        // Calculate current accumulated gas usage
1030        let accumulated_gas_usage = total_gas_used + tx_gas_used;
1031
1032        // Check gas limit - if accumulated gas used exceeds it, break out of loop
1033        if accumulated_gas_usage > BLOCK_GAS_LIMIT {
1034            warn!(
1035                target: "validator::verification::verify_transactions",
1036                "Transaction {} exceeds configured transaction gas limit: {accumulated_gas_usage} - {BLOCK_GAS_LIMIT}",
1037                tx.hash()
1038            );
1039            erroneous_txs.push(tx.clone());
1040            overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
1041            overlay.lock().unwrap().revert_to_checkpoint()?;
1042            break
1043        }
1044
1045        // Update accumulated total gas
1046        total_gas_used += tx_gas_used;
1047        total_gas_paid += gas_data.paid;
1048    }
1049
1050    if !erroneous_txs.is_empty() {
1051        return Err(TxVerifyFailed::ErroneousTxs(erroneous_txs).into())
1052    }
1053
1054    Ok((total_gas_used, total_gas_paid))
1055}
1056
1057/// Apply given set of [`Transaction`] in sequence, without formal verification.
1058/// In case any of the transactions fail, they will be returned to the caller as an error.
1059/// Additionally, their hash is appended to the provided Merkle tree.
1060async fn apply_transactions(
1061    overlay: &BlockchainOverlayPtr,
1062    verifying_block_height: u32,
1063    block_target: u32,
1064    txs: &[Transaction],
1065    tree: &mut MerkleTree,
1066) -> Result<()> {
1067    debug!(target: "validator::verification::apply_transactions", "Applying {} transactions", txs.len());
1068    if txs.is_empty() {
1069        return Ok(())
1070    }
1071
1072    // Tracker for failed txs
1073    let mut erroneous_txs = vec![];
1074
1075    // Iterate over transactions and attempt to apply them
1076    for tx in txs {
1077        overlay.lock().unwrap().checkpoint();
1078        if let Err(e) =
1079            apply_transaction(overlay, verifying_block_height, block_target, tx, tree).await
1080        {
1081            warn!(target: "validator::verification::apply_transactions", "Transaction apply failed: {e}");
1082            erroneous_txs.push(tx.clone());
1083            overlay.lock().unwrap().revert_to_checkpoint()?;
1084        };
1085    }
1086
1087    if !erroneous_txs.is_empty() {
1088        return Err(TxVerifyFailed::ErroneousTxs(erroneous_txs).into())
1089    }
1090
1091    Ok(())
1092}
1093
1094/// Verify given [`Proposal`] against provided consensus state.
1095///
1096/// A proposal is considered valid when the following rules apply:
1097///     1. Proposal hash matches the actual block one
1098///     2. Block is valid
1099/// Additional validity rules can be applied.
1100pub async fn verify_proposal(
1101    consensus: &Consensus,
1102    proposal: &Proposal,
1103    verify_fees: bool,
1104) -> Result<(Fork, Option<usize>)> {
1105    // Check if proposal hash matches actual one (1)
1106    let proposal_hash = proposal.block.hash();
1107    if proposal.hash != proposal_hash {
1108        warn!(
1109            target: "validator::verification::verify_proposal", "Received proposal contains mismatched hashes: {} - {proposal_hash}",
1110            proposal.hash
1111        );
1112        return Err(Error::ProposalHashesMissmatchError)
1113    }
1114
1115    // Check if proposal extends any existing forks
1116    let (mut fork, index) = consensus.find_extended_fork(proposal).await?;
1117
1118    // Grab overlay last block
1119    let previous = fork.overlay.lock().unwrap().last_block()?;
1120
1121    // Verify proposal block (2)
1122    if let Err(e) = verify_block(
1123        &fork.overlay,
1124        &fork.diffs,
1125        &fork.module,
1126        &mut fork.state_monotree,
1127        &proposal.block,
1128        &previous,
1129        verify_fees,
1130    )
1131    .await
1132    {
1133        error!(target: "validator::verification::verify_proposal", "Erroneous proposal block found: {e}");
1134        fork.overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
1135        return Err(Error::BlockIsInvalid(proposal.hash.as_string()))
1136    };
1137
1138    Ok((fork, index))
1139}
1140
1141/// Verify given [`Proposal`] against provided fork state.
1142///
1143/// A proposal is considered valid when the following rules apply:
1144///     1. Proposal hash matches the actual block one
1145///     2. Block is valid
1146/// Additional validity rules can be applied.
1147pub async fn verify_fork_proposal(
1148    fork: &mut Fork,
1149    proposal: &Proposal,
1150    verify_fees: bool,
1151) -> Result<()> {
1152    // Check if proposal hash matches actual one (1)
1153    let proposal_hash = proposal.block.hash();
1154    if proposal.hash != proposal_hash {
1155        warn!(
1156            target: "validator::verification::verify_fork_proposal", "Received proposal contains mismatched hashes: {} - {proposal_hash}",
1157            proposal.hash
1158        );
1159        return Err(Error::ProposalHashesMissmatchError)
1160    }
1161
1162    // Grab overlay last block
1163    let previous = fork.overlay.lock().unwrap().last_block()?;
1164
1165    // Verify proposal block (2)
1166    if let Err(e) = verify_block(
1167        &fork.overlay,
1168        &fork.diffs,
1169        &fork.module,
1170        &mut fork.state_monotree,
1171        &proposal.block,
1172        &previous,
1173        verify_fees,
1174    )
1175    .await
1176    {
1177        error!(target: "validator::verification::verify_fork_proposal", "Erroneous proposal block found: {e}");
1178        fork.overlay.lock().unwrap().overlay.lock().unwrap().purge_new_trees()?;
1179        return Err(Error::BlockIsInvalid(proposal.hash.as_string()))
1180    };
1181
1182    Ok(())
1183}