validator: producer tx on last instead of first position in block txs vector and proper header Merkle tree validation

This commit is contained in:
skoupidi
2024-02-08 15:44:41 +02:00
parent 3e6e54121d
commit 2ce7f38880
7 changed files with 152 additions and 41 deletions

View File

@@ -76,8 +76,11 @@ impl Harness {
// Generate default genesis block
let mut genesis_block = BlockInfo::default();
// Retrieve genesis producer transaction
let producer_tx = genesis_block.txs.pop().unwrap();
// Append genesis transactions
genesis_block.txs.push(genesis_mint_tx);
genesis_block.append_txs(vec![genesis_mint_tx, producer_tx])?;
// Generate validators configuration
// NOTE: we are not using consensus constants here so we

View File

@@ -19,7 +19,7 @@
use darkfi_sdk::{
crypto::{
schnorr::{SchnorrSecret, Signature},
SecretKey,
MerkleTree, SecretKey,
},
pasta::{group::ff::FromUniformBytes, pallas},
};
@@ -119,11 +119,7 @@ impl BlockInfo {
/// Append a transaction to the block. Also adds it to the Merkle tree.
pub fn append_tx(&mut self, tx: Transaction) -> Result<()> {
let mut buf = [0u8; 64];
buf[..blake3::OUT_LEN].copy_from_slice(tx.hash()?.as_bytes());
let leaf = pallas::Base::from_uniform_bytes(&buf);
self.header.tree.append(leaf.into());
append_tx_to_merkle_tree(&mut self.header.tree, &tx)?;
self.txs.push(tx);
Ok(())
@@ -662,3 +658,12 @@ impl BlockDifficultyStoreOverlay {
Ok(())
}
}
/// Auxiliary function to append a transaction to a Merkle tree.
pub fn append_tx_to_merkle_tree(tree: &mut MerkleTree, tx: &Transaction) -> Result<()> {
let mut buf = [0u8; 64];
buf[..blake3::OUT_LEN].copy_from_slice(tx.hash()?.as_bytes());
let leaf = pallas::Base::from_uniform_bytes(&buf);
tree.append(leaf.into());
Ok(())
}

View File

@@ -78,8 +78,15 @@ pub async fn append_fee_call(
// First we will verify the fee-less transaction to see how much gas
// it uses for execution and verification.
let tx = tx_builder.build()?;
let gas_used =
verify_transaction(overlay, verifying_block_height, &tx, verifying_keys, false).await?;
let gas_used = verify_transaction(
overlay,
verifying_block_height,
&tx,
&mut MerkleTree::new(1),
verifying_keys,
false,
)
.await?;
// TODO: We could actually take a set of coins and then find one with
// enough value, instead of expecting one. It depends, the API

View File

@@ -194,6 +194,8 @@ impl TestHarness {
let mut holders = HashMap::new();
let mut genesis_block = BlockInfo::default();
genesis_block.header.timestamp = Timestamp(1689772567);
let producer_tx = genesis_block.txs.pop().unwrap();
genesis_block.append_txs(vec![producer_tx])?;
// Deterministic PRNG
let mut rng = Pcg32::new(42);

View File

@@ -16,7 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use darkfi_sdk::{crypto::SecretKey, pasta::pallas};
use darkfi_sdk::{
crypto::{MerkleTree, SecretKey},
pasta::pallas,
};
use darkfi_serial::{async_trait, serialize, SerialDecodable, SerialEncodable};
use log::{debug, error, info};
use num_bigint::BigUint;
@@ -387,8 +390,14 @@ impl Fork {
let overlay = self.overlay.lock().unwrap().full_clone()?;
// Verify transactions
if let Err(e) =
verify_transactions(&overlay, verifying_block_height, &unproposed_txs, false).await
if let Err(e) = verify_transactions(
&overlay,
verifying_block_height,
&unproposed_txs,
&mut MerkleTree::new(1),
false,
)
.await
{
match e {
crate::Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(erroneous_txs)) => {

View File

@@ -18,7 +18,7 @@
use std::sync::Arc;
use darkfi_sdk::crypto::PublicKey;
use darkfi_sdk::crypto::{MerkleTree, PublicKey};
use darkfi_serial::serialize_async;
use log::{debug, error, info, warn};
use num_bigint::BigUint;
@@ -182,7 +182,15 @@ impl Validator {
let next_block_height = fork.get_next_block_height()?;
// Verify transaction
match verify_transactions(&overlay, next_block_height, &tx_vec, false).await {
match verify_transactions(
&overlay,
next_block_height,
&tx_vec,
&mut MerkleTree::new(1),
false,
)
.await
{
Ok(_) => {}
Err(crate::Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => continue,
Err(e) => return Err(e),
@@ -200,7 +208,15 @@ impl Validator {
let overlay = BlockchainOverlay::new(&self.blockchain)?;
let next_block_height = self.blockchain.last_block()?.header.height + 1;
let mut erroneous_txs = vec![];
match verify_transactions(&overlay, next_block_height, &tx_vec, false).await {
match verify_transactions(
&overlay,
next_block_height,
&tx_vec,
&mut MerkleTree::new(1),
false,
)
.await
{
Ok(_) => valid = true,
Err(crate::Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(etx))) => {
erroneous_txs = etx
@@ -255,7 +271,15 @@ impl Validator {
let next_block_height = fork.get_next_block_height()?;
// Verify transaction
match verify_transactions(&overlay, next_block_height, &tx_vec, false).await {
match verify_transactions(
&overlay,
next_block_height,
&tx_vec,
&mut MerkleTree::new(1),
false,
)
.await
{
Ok(_) => {
valid = true;
continue
@@ -271,7 +295,15 @@ impl Validator {
// Verify transaction against canonical state
let overlay = BlockchainOverlay::new(&self.blockchain)?;
let next_block_height = self.blockchain.last_block()?.header.height + 1;
match verify_transactions(&overlay, next_block_height, &tx_vec, false).await {
match verify_transactions(
&overlay,
next_block_height,
&tx_vec,
&mut MerkleTree::new(1),
false,
)
.await
{
Ok(_) => valid = true,
Err(crate::Error::TxVerifyFailed(TxVerifyFailed::ErroneousTxs(_))) => {}
Err(e) => return Err(e),
@@ -425,7 +457,14 @@ impl Validator {
let overlay = BlockchainOverlay::new(&self.blockchain)?;
// Verify all transactions and get erroneous ones
let e = verify_transactions(&overlay, verifying_block_height, txs, self.verify_fees).await;
let e = verify_transactions(
&overlay,
verifying_block_height,
txs,
&mut MerkleTree::new(1),
self.verify_fees,
)
.await;
let lock = overlay.lock().unwrap();
let mut overlay = lock.overlay.lock().unwrap();
@@ -461,7 +500,14 @@ impl Validator {
// Verify transaction
let mut erroneous_txs = vec![];
if let Err(e) = verify_producer_transaction(&overlay, verifying_block_height, tx).await {
if let Err(e) = verify_producer_transaction(
&overlay,
verifying_block_height,
tx,
&mut MerkleTree::new(1),
)
.await
{
warn!(target: "validator::add_test_producer_transaction", "Transaction verification failed: {}", e);
erroneous_txs.push(tx.clone());
}

View File

@@ -21,7 +21,8 @@ use std::collections::HashMap;
use darkfi_sdk::{
blockchain::{block_epoch, block_version},
crypto::{
schnorr::SchnorrPublic, ContractId, PublicKey, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
schnorr::SchnorrPublic, ContractId, MerkleTree, PublicKey, DEPLOYOOOR_CONTRACT_ID,
MONEY_CONTRACT_ID,
},
dark_tree::dark_forest_leaf_vec_integrity_check,
deploy::DeployParamsV1,
@@ -35,7 +36,9 @@ use num_bigint::BigUint;
use smol::io::Cursor;
use crate::{
blockchain::{BlockInfo, Blockchain, BlockchainOverlayPtr},
blockchain::{
block_store::append_tx_to_merkle_tree, BlockInfo, Blockchain, BlockchainOverlayPtr,
},
error::TxVerifyFailed,
runtime::vm_runtime::Runtime,
tx::{Transaction, MAX_TX_CALLS, MIN_TX_CALLS},
@@ -78,15 +81,17 @@ pub async fn verify_genesis_block(overlay: &BlockchainOverlayPtr, block: &BlockI
return Err(Error::BlockContainsNoTransactions(block_hash))
}
// Genesis transaction must be the Transaction::default() one(empty)
if block.txs[0] != Transaction::default() {
error!(target: "validator::verification::verify_genesis_block", "Genesis proposal transaction is not default one");
return Err(TxVerifyFailed::ErroneousTxs(vec![block.txs[0].clone()]).into())
// Genesis producer transaction must be the Transaction::default() one(empty)
let producer_tx = block.txs.last().unwrap();
if producer_tx != &Transaction::default() {
error!(target: "validator::verification::verify_genesis_block", "Genesis producer transaction is not default one");
return Err(TxVerifyFailed::ErroneousTxs(vec![producer_tx.clone()]).into())
}
// Verify transactions, exluding producer(first) one
let txs = &block.txs[1..];
if let Err(e) = verify_transactions(overlay, block.header.height, txs, false).await {
// Verify transactions, exluding producer(last) one
let mut tree = MerkleTree::new(1);
let txs = &block.txs[..block.txs.len() - 1];
if let Err(e) = verify_transactions(overlay, block.header.height, txs, &mut tree, false).await {
warn!(
target: "validator::verification::verify_genesis_block",
"[VALIDATOR] Erroneous transactions found in set",
@@ -95,6 +100,13 @@ pub async fn verify_genesis_block(overlay: &BlockchainOverlayPtr, block: &BlockI
return Err(e)
}
// Append producer transaction to the tree and check tree matches header one
append_tx_to_merkle_tree(&mut tree, producer_tx)?;
if tree != block.header.tree {
error!(target: "validator::verification::verify_genesis_block", "Genesis Merkle tree is invalid");
return Err(Error::BlockIsInvalid(block_hash))
}
// Insert block
overlay.lock().unwrap().add_block(block)?;
@@ -189,14 +201,10 @@ pub async fn verify_block(
return Err(Error::BlockContainsNoTransactions(block_hash))
}
// Verify proposal transaction.
let public_key =
verify_producer_transaction(overlay, block.header.height, &block.txs[0]).await?;
verify_producer_signature(block, &public_key)?;
// Verify transactions, exluding producer(first) one
let txs = &block.txs[1..];
let e = verify_transactions(overlay, block.header.height, txs, false).await;
// Verify transactions, exluding producer(last) one
let mut tree = MerkleTree::new(1);
let txs = &block.txs[..block.txs.len() - 1];
let e = verify_transactions(overlay, block.header.height, txs, &mut tree, false).await;
if let Err(e) = e {
warn!(
target: "validator::verification::verify_block",
@@ -206,6 +214,22 @@ pub async fn verify_block(
return Err(e)
}
// Verify producer transaction
let public_key = verify_producer_transaction(
overlay,
block.header.height,
block.txs.last().unwrap(),
&mut tree,
)
.await?;
verify_producer_signature(block, &public_key)?;
// Verify tree matches header one
if tree != block.header.tree {
error!(target: "validator::verification::verify_block", "Block Merkle tree is invalid");
return Err(Error::BlockIsInvalid(block_hash))
}
// Insert block
overlay.lock().unwrap().add_block(block)?;
@@ -226,10 +250,12 @@ pub fn verify_producer_signature(block: &BlockInfo, public_key: &PublicKey) -> R
/// Verify WASM execution, signatures, and ZK proofs for a given producer [`Transaction`],
/// and apply it to the provided overlay. Returns transaction signature public key.
/// Additionally, append its hash to the provided Merkle tree.
pub async fn verify_producer_transaction(
overlay: &BlockchainOverlayPtr,
verifying_block_height: u64,
tx: &Transaction,
tree: &mut MerkleTree,
) -> Result<PublicKey> {
let tx_hash = tx.hash()?;
debug!(target: "validator::verification::verify_producer_transaction", "Validating proposal transaction {}", tx_hash);
@@ -342,19 +368,24 @@ pub async fn verify_producer_transaction(
error!(target: "validator::verification::verify_proposal_transaction", "ZK proof verification for tx {} failed: {}", tx_hash, e);
return Err(TxVerifyFailed::InvalidZkProof.into())
}
debug!(target: "validator::verification::verify_producer_transaction", "ZK proof verification successful");
// Append hash to merkle tree
append_tx_to_merkle_tree(tree, tx)?;
debug!(target: "validator::verification::verify_producer_transaction", "Proposal transaction {} verified successfully", tx_hash);
Ok(signature_public_key)
}
/// Verify WASM execution, signatures, and ZK proofs for a given [`Transaction`],
/// and apply it to the provided overlay.
/// and apply it to the provided overlay. Additionally, append its hash to the
/// provided Merkle tree.
pub async fn verify_transaction(
overlay: &BlockchainOverlayPtr,
verifying_block_height: u64,
tx: &Transaction,
tree: &mut MerkleTree,
verifying_keys: &mut HashMap<[u8; 32], HashMap<String, VerifyingKey>>,
verify_fee: bool,
) -> Result<u64> {
@@ -577,6 +608,9 @@ pub async fn verify_transaction(
}
debug!(target: "validator::verification::verify_transaction", "ZK proof verification successful");
// Append hash to merkle tree
append_tx_to_merkle_tree(tree, tx)?;
debug!(target: "validator::verification::verify_transaction", "Transaction {} verified successfully", tx_hash);
Ok(gas_used)
}
@@ -584,14 +618,18 @@ pub async fn verify_transaction(
/// Verify a set of [`Transaction`] in sequence and apply them if all are valid.
/// In case any of the transactions fail, they will be returned to the caller as an error.
/// If all transactions are valid, the function will return the accumulated gas used from
/// all the transactions.
/// all the transactions. Additionally, their hash is appended to the provided Merkle tree.
pub async fn verify_transactions(
overlay: &BlockchainOverlayPtr,
verifying_block_height: u64,
txs: &[Transaction],
tree: &mut MerkleTree,
verify_fees: bool,
) -> Result<u64> {
debug!(target: "validator::verification::verify_transactions", "Verifying {} transactions", txs.len());
if txs.is_empty() {
return Ok(0)
}
// Tracker for failed txs
let mut erroneous_txs = vec![];
@@ -612,12 +650,13 @@ pub async fn verify_transactions(
// Iterate over transactions and attempt to verify them
for tx in txs {
overlay.lock().unwrap().checkpoint();
match verify_transaction(overlay, verifying_block_height, tx, &mut vks, verify_fees).await {
match verify_transaction(overlay, verifying_block_height, tx, tree, &mut vks, verify_fees)
.await
{
Ok(gas) => gas_used += gas,
Err(e) => {
warn!(target: "validator::verification::verify_transactions", "Transaction verification failed: {}", e);
erroneous_txs.push(tx.clone());
// TODO: verify this works as expected
overlay.lock().unwrap().revert_to_checkpoint()?;
}
}