From 092129eb37dcdc8a4559d3c4c6a74d4ce1e836d4 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:04:16 +0100 Subject: [PATCH 01/19] vm: Add transaction execution function --- vm/src/lib.rs | 33 ++------ vm/src/session.rs | 196 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 28 deletions(-) create mode 100644 vm/src/session.rs diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 4de346889..6c3bfdd43 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -23,7 +23,7 @@ use std::fmt::{self, Debug, Formatter}; use std::path::{Path, PathBuf}; use std::thread; -use dusk_core::abi::{Metadata, Query}; +use dusk_core::abi::Query; use piecrust::{SessionData, VM as PiecrustVM}; use self::host_queries::{ @@ -33,33 +33,10 @@ use self::host_queries::{ pub(crate) mod cache; pub mod host_queries; - -/// Create a new session based on the given `VM`. -pub fn new_session( - vm: &VM, - base: [u8; 32], - chain_id: u8, - block_height: u64, -) -> Result { - vm.session( - SessionData::builder() - .base(base) - .insert(Metadata::CHAIN_ID, chain_id)? - .insert(Metadata::BLOCK_HEIGHT, block_height)?, - ) -} - -/// Create a new genesis session based on the given [`VM`]. -pub fn new_genesis_session(vm: &VM, chain_id: u8) -> Session { - vm.session( - SessionData::builder() - .insert(Metadata::CHAIN_ID, chain_id) - .expect("Inserting chain ID in metadata should succeed") - .insert(Metadata::BLOCK_HEIGHT, 0) - .expect("Inserting block height in metadata should succeed"), - ) - .expect("Creating a genesis session should always succeed") -} +pub(crate) mod session; +pub use session::{ + execute, genesis as new_genesis_session, new as new_session, +}; /// Dusk VM is a [`PiecrustVM`] enriched with the host functions specified in /// Dusk's ABI. diff --git a/vm/src/session.rs b/vm/src/session.rs new file mode 100644 index 000000000..078a7eaa2 --- /dev/null +++ b/vm/src/session.rs @@ -0,0 +1,196 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use dusk_core::abi::{gen_contract_id, ContractError, Metadata}; +use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; +use piecrust::{CallReceipt, Error, Session, SessionData}; + +use crate::VM; + +/// Create a new session based on the given `VM`. +pub fn new( + vm: &VM, + base: [u8; 32], + chain_id: u8, + block_height: u64, +) -> Result { + vm.session( + SessionData::builder() + .base(base) + .insert(Metadata::CHAIN_ID, chain_id)? + .insert(Metadata::BLOCK_HEIGHT, block_height)?, + ) +} + +/// Create a new genesis session based on the given [`VM`]. +pub fn genesis(vm: &VM, chain_id: u8) -> Session { + vm.session( + SessionData::builder() + .insert(Metadata::CHAIN_ID, chain_id) + .expect("Inserting chain ID in metadata should succeed") + .insert(Metadata::BLOCK_HEIGHT, 0) + .expect("Inserting block height in metadata should succeed"), + ) + .expect("Creating a genesis session should always succeed") +} + +/// Executes a transaction, returning the receipt of the call and the gas spent. +/// The following steps are performed: +/// +/// 1. Check if the transaction contains contract deployment data, and if so, +/// verifies if gas limit is enough for deployment and if the gas price is +/// sufficient for deployment. If either gas price or gas limit is not +/// sufficient for deployment, transaction is discarded. +/// +/// 2. Call the "spend_and_execute" function on the transfer contract with +/// unlimited gas. If this fails, an error is returned. If an error is +/// returned the transaction should be considered unspendable/invalid, but no +/// re-execution of previous transactions is required. +/// +/// 3. If the transaction contains contract deployment data, additional checks +/// are performed and if they pass, deployment is executed. The following +/// checks are performed: +/// - gas limit should be is smaller than deploy charge plus gas used for +/// spending funds +/// - transaction's bytecode's bytes are consistent with bytecode's hash +/// Deployment execution may fail for deployment-specific reasons, such as +/// for example: +/// - contract already deployed +/// - corrupted bytecode +/// If deployment execution fails, the entire gas limit is consumed and error +/// is returned. +/// +/// 4. Call the "refund" function on the transfer contract with unlimited gas. +/// The amount charged depends on the gas spent by the transaction, and the +/// optional contract call in steps 2 or 3. +/// +/// Note that deployment transaction will never be re-executed for reasons +/// related to deployment, as it is either discarded or it charges the +/// full gas limit. It might be re-executed only if some other transaction +/// failed to fit the block. +pub fn execute( + session: &mut Session, + tx: &Transaction, + gas_per_deploy_byte: u64, + min_deploy_points: u64, + min_deploy_gas_price: u64, +) -> Result, ContractError>>, Error> { + // Transaction will be discarded if it is a deployment transaction + // with gas limit smaller than deploy charge. + deploy_check(tx, gas_per_deploy_byte, min_deploy_gas_price)?; + + // Spend the inputs and execute the call. If this errors the transaction is + // unspendable. + let mut receipt = session.call::<_, Result, ContractError>>( + TRANSFER_CONTRACT, + "spend_and_execute", + tx.strip_off_bytecode().as_ref().unwrap_or(tx), + tx.gas_limit(), + )?; + + // Deploy if this is a deployment transaction and spend part is successful. + contract_deploy( + session, + tx, + gas_per_deploy_byte, + min_deploy_points, + &mut receipt, + ); + + // Ensure all gas is consumed if there's an error in the contract call + if receipt.data.is_err() { + receipt.gas_spent = receipt.gas_limit; + } + + // Refund the appropriate amount to the transaction. This call is guaranteed + // to never error. If it does, then a programming error has occurred. As + // such, the call to `Result::expect` is warranted. + let refund_receipt = session + .call::<_, ()>( + TRANSFER_CONTRACT, + "refund", + &receipt.gas_spent, + u64::MAX, + ) + .expect("Refunding must succeed"); + + receipt.events.extend(refund_receipt.events); + + Ok(receipt) +} + +fn deploy_check( + tx: &Transaction, + gas_per_deploy_byte: u64, + min_deploy_gas_price: u64, +) -> Result<(), Error> { + if tx.deploy().is_some() { + let deploy_charge = + tx.deploy_charge(gas_per_deploy_byte, min_deploy_gas_price); + + if tx.gas_price() < min_deploy_gas_price { + return Err(Error::Panic("gas price too low to deploy".into())); + } + if tx.gas_limit() < deploy_charge { + return Err(Error::Panic("not enough gas to deploy".into())); + } + } + + Ok(()) +} + +// Contract deployment will fail and charge full gas limit in the +// following cases: +// 1) Transaction gas limit is smaller than deploy charge plus gas used for +// spending funds. +// 2) Transaction's bytecode's bytes are not consistent with bytecode's hash. +// 3) Deployment fails for deploy-specific reasons like e.g.: +// - contract already deployed +// - corrupted bytecode +// - sufficient gas to spend funds yet insufficient for deployment +fn contract_deploy( + session: &mut Session, + tx: &Transaction, + gas_per_deploy_byte: u64, + min_deploy_points: u64, + receipt: &mut CallReceipt, ContractError>>, +) { + if let Some(deploy) = tx.deploy() { + let gas_left = tx.gas_limit() - receipt.gas_spent; + if receipt.data.is_ok() { + let deploy_charge = + tx.deploy_charge(gas_per_deploy_byte, min_deploy_points); + let min_gas_limit = receipt.gas_spent + deploy_charge; + if gas_left < min_gas_limit { + receipt.data = Err(ContractError::OutOfGas); + } else if !deploy.bytecode.verify_hash() { + receipt.data = Err(ContractError::Panic( + "failed bytecode hash check".into(), + )) + } else { + let result = session.deploy_raw( + Some(gen_contract_id( + &deploy.bytecode.bytes, + deploy.nonce, + &deploy.owner, + )), + deploy.bytecode.bytes.as_slice(), + deploy.init_args.clone(), + deploy.owner.clone(), + gas_left, + ); + match result { + // Should the gas spent by the INIT method charged too? + Ok(_) => receipt.gas_spent += deploy_charge, + Err(err) => { + let msg = format!("failed deployment: {err:?}"); + receipt.data = Err(ContractError::Panic(msg)) + } + } + } + } + } +} From 955fe66c7dffdd33328a6378818e1c60b9550a21 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:05:03 +0100 Subject: [PATCH 02/19] core: Add deployment methods --- core/Cargo.toml | 2 ++ core/src/abi.rs | 68 +++++++++++++++++++++++++++++++++++++-- core/src/transfer.rs | 19 +++++++++++ core/src/transfer/data.rs | 8 +++++ 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 2c3174149..a70f4910d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,6 +18,8 @@ rkyv = { workspace = true, features = ["size_32"] } bytecheck = { workspace = true } rand = { workspace = true } ff = { workspace = true } +blake3 = { workspace = true } +blake2b_simd = { workspace = true } # plonk dependencies dusk-plonk = { workspace = true, features = ["rkyv-impl", "alloc"], optional = true } diff --git a/core/src/abi.rs b/core/src/abi.rs index fd22be42e..aca5e3f98 100644 --- a/core/src/abi.rs +++ b/core/src/abi.rs @@ -14,6 +14,8 @@ pub use piecrust_uplink::{ #[cfg(feature = "abi")] pub use self::host_queries::*; +use blake2b_simd::Params; + /// Enum storing the metadata identifiers. pub enum Metadata {} @@ -44,10 +46,70 @@ impl Query { pub const VERIFY_BLS_MULTISIG: &'static str = "verify_bls_multisig"; } +/// Generate a [`ContractId`] address from: +/// - slice of bytes, +/// - nonce +/// - owner +/// +/// # Panics +/// Panics if [blake2b-hasher] doesn't produce a [`CONTRACT_ID_BYTES`] +/// bytes long hash. +/// +/// [blake2b-hasher]: [`blake2b_simd::Params.finalize`] +pub fn gen_contract_id( + bytes: impl AsRef<[u8]>, + nonce: u64, + owner: impl AsRef<[u8]>, +) -> ContractId { + let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); + hasher.update(bytes.as_ref()); + hasher.update(&nonce.to_le_bytes()[..]); + hasher.update(owner.as_ref()); + let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher + .finalize() + .as_bytes() + .try_into() + .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); + ContractId::from_bytes(hash_bytes) +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + use rand::rngs::StdRng; + use rand::{RngCore, SeedableRng}; + + use super::*; + + #[test] + fn test_gen_contract_id() { + let mut rng = StdRng::seed_from_u64(42); + + let mut bytes = vec![0; 1000]; + rng.fill_bytes(&mut bytes); + + let nonce = rng.next_u64(); + + let mut owner = vec![0, 100]; + rng.fill_bytes(&mut owner); + + let contract_id = + gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); + + assert_eq!( + contract_id.as_bytes(), + [ + 45, 168, 182, 39, 119, 137, 168, 140, 114, 21, 120, 158, 34, + 126, 244, 221, 151, 72, 109, 178, 82, 229, 84, 128, 92, 123, + 135, 74, 23, 224, 119, 133 + ] + ); + } +} + #[cfg(feature = "abi")] pub(crate) mod host_queries { - use alloc::vec::Vec; - #[cfg(feature = "abi-debug")] pub use piecrust_uplink::debug as piecrust_debug; pub use piecrust_uplink::{ @@ -57,6 +119,8 @@ pub(crate) mod host_queries { * spend_and_execute */ }; + use alloc::vec::Vec; + use dusk_bytes::Serializable; use piecrust_uplink::{host_query, meta_data}; diff --git a/core/src/transfer.rs b/core/src/transfer.rs index 47cedd9a2..4b2d8f891 100644 --- a/core/src/transfer.rs +++ b/core/src/transfer.rs @@ -9,6 +9,7 @@ use alloc::string::String; use alloc::vec::Vec; +use core::cmp::max; use core::fmt::Debug; use bytecheck::CheckBytes; @@ -339,6 +340,24 @@ impl Transaction { Self::Moonlight(tx) => tx.hash(), } } + + /// Returns the charge for a contract deployment. The deployment of a + /// contract will cost at least `min_deploy_points`. + /// If the transaction is not a deploy-transaction, the deploy-charge will + /// be 0. + #[must_use] + pub fn deploy_charge( + &self, + gas_per_deploy_byte: u64, + min_deploy_points: u64, + ) -> u64 { + if let Some(deploy) = self.deploy() { + let bytecode_len = deploy.bytecode.bytes.len() as u64; + max(bytecode_len * gas_per_deploy_byte, min_deploy_points) + } else { + 0 + } + } } impl From for Transaction { diff --git a/core/src/transfer/data.rs b/core/src/transfer/data.rs index a2685c100..b4ebbcaa2 100644 --- a/core/src/transfer/data.rs +++ b/core/src/transfer/data.rs @@ -213,6 +213,14 @@ impl ContractBytecode { self.hash.to_vec() } + /// Verifies that the stored bytes-hash is correct. + #[must_use] + pub fn verify_hash(&self) -> bool { + let computed: [u8; 32] = blake3::hash(self.bytes.as_slice()).into(); + + self.hash == computed + } + /// Serializes this object into a variable length buffer #[must_use] pub fn to_var_bytes(&self) -> Vec { From ed18d6810aa18315ee62135571c797cc1e13f922 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:05:55 +0100 Subject: [PATCH 03/19] transfer-contract: Use `vm::execute` for tests --- contracts/transfer/tests/common/utils.rs | 40 +----------- contracts/transfer/tests/moonlight.rs | 83 ++++++++++++------------ contracts/transfer/tests/phoenix.rs | 42 ++++++------ 3 files changed, 68 insertions(+), 97 deletions(-) diff --git a/contracts/transfer/tests/common/utils.rs b/contracts/transfer/tests/common/utils.rs index c42bfd22b..82f26c52e 100644 --- a/contracts/transfer/tests/common/utils.rs +++ b/contracts/transfer/tests/common/utils.rs @@ -6,13 +6,13 @@ use std::sync::mpsc; -use dusk_core::abi::{ContractError, ContractId}; +use dusk_core::abi::ContractId; use dusk_core::signatures::bls::PublicKey as AccountPublicKey; use dusk_core::transfer::moonlight::AccountData; use dusk_core::transfer::phoenix::{Note, NoteLeaf, ViewKey as PhoenixViewKey}; -use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; +use dusk_core::transfer::TRANSFER_CONTRACT; use dusk_core::BlsScalar; -use dusk_vm::{CallReceipt, Error as VMError, Session}; +use dusk_vm::{Error as VMError, Session}; const GAS_LIMIT: u64 = 0x10_000_000; @@ -31,40 +31,6 @@ pub fn chain_id(session: &mut Session) -> Result { .map(|r| r.data) } -/// Executes a transaction. -/// Returns result containing gas spent. -pub fn execute( - session: &mut Session, - tx: impl Into, -) -> Result, ContractError>>, VMError> { - let tx = tx.into(); - - let mut receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "spend_and_execute", - &tx, - tx.gas_limit(), - )?; - - // Ensure all gas is consumed if there's an error in the contract call - if receipt.data.is_err() { - receipt.gas_spent = receipt.gas_limit; - } - - let refund_receipt = session - .call::<_, ()>( - TRANSFER_CONTRACT, - "refund", - &receipt.gas_spent, - u64::MAX, - ) - .expect("Refunding must succeed"); - - receipt.events.extend(refund_receipt.events); - - Ok(receipt) -} - // moonlight helper functions pub fn account( diff --git a/contracts/transfer/tests/moonlight.rs b/contracts/transfer/tests/moonlight.rs index 5f8f0dfc1..a42d80c3b 100644 --- a/contracts/transfer/tests/moonlight.rs +++ b/contracts/transfer/tests/moonlight.rs @@ -4,17 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -pub mod common; - -use crate::common::utils::{ - account, chain_id, contract_balance, execute, existing_nullifiers, - filter_notes_owned_by, leaves_from_height, owned_notes_value, update_root, -}; - -use ff::Field; -use rand::rngs::StdRng; -use rand::SeedableRng; - use dusk_core::abi::{ContractError, ContractId}; use dusk_core::signatures::bls::{ PublicKey as AccountPublicKey, SecretKey as AccountSecretKey, @@ -29,10 +18,21 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::transfer::{ - ContractToAccount, ContractToContract, TRANSFER_CONTRACT, + ContractToAccount, ContractToContract, Transaction, TRANSFER_CONTRACT, }; use dusk_core::{dusk, JubJubScalar, LUX}; -use dusk_vm::{new_genesis_session, new_session, ContractData, Session, VM}; +use dusk_vm::{ + execute, new_genesis_session, new_session, ContractData, Session, VM, +}; +use ff::Field; +use rand::rngs::StdRng; +use rand::SeedableRng; + +pub mod common; +use crate::common::utils::{ + account, chain_id, contract_balance, existing_nullifiers, + filter_notes_owned_by, leaves_from_height, owned_notes_value, update_root, +}; const MOONLIGHT_GENESIS_VALUE: u64 = dusk(1_000.0); const MOONLIGHT_GENESIS_NONCE: u64 = 0; @@ -171,7 +171,7 @@ fn transfer() { let session = &mut instantiate(&moonlight_sender_pk); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sender_sk, Some(moonlight_receiver_pk), TRANSFER_VALUE, @@ -184,7 +184,7 @@ fn transfer() { ) .expect("Creating moonlight transaction should succeed"); - let gas_spent = execute(session, transaction) + let gas_spent = execute(session, &transaction, 0, 0, 0) .expect("Transaction should succeed") .gas_spent; @@ -233,7 +233,7 @@ fn transfer_with_refund() { "The receiver account should be empty" ); - let transaction = MoonlightTransaction::new_with_refund( + let transaction: Transaction = MoonlightTransaction::new_with_refund( &moonlight_sender_sk, &moonlight_refund_pk, Some(moonlight_receiver_pk), @@ -245,10 +245,11 @@ fn transfer_with_refund() { CHAIN_ID, None::, ) - .expect("Creating moonlight transaction should succeed"); + .expect("Creating moonlight transaction should succeed") + .into(); let max_gas = GAS_LIMIT * LUX; - let gas_spent = execute(session, transaction) + let gas_spent = execute(session, &transaction, 0, 0, 0) .expect("Transaction should succeed") .gas_spent; let gas_refund = max_gas - gas_spent; @@ -300,7 +301,7 @@ fn transfer_gas_fails() { "The receiver account should be empty" ); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sender_sk, Some(moonlight_receiver_pk), TRANSFER_VALUE, @@ -313,7 +314,7 @@ fn transfer_gas_fails() { ) .expect("Creating moonlight transaction should succeed"); - let result = execute(session, transaction); + let result = execute(session, &transaction, 0, 0, 0); assert!( result.is_err(), @@ -352,7 +353,7 @@ fn alice_ping() { fn_args: vec![], }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -365,7 +366,7 @@ fn alice_ping() { ) .expect("Creating moonlight transaction should succeed"); - let gas_spent = execute(session, transaction) + let gas_spent = execute(session, &transaction, 0, 0, 0) .expect("Transaction should succeed") .gas_spent; @@ -432,7 +433,7 @@ fn convert_to_phoenix() { .to_vec(), }; - let tx = MoonlightTransaction::new( + let tx = Transaction::moonlight( &moonlight_sk, None, 0, @@ -446,7 +447,7 @@ fn convert_to_phoenix() { ) .expect("Creating moonlight transaction should succeed"); - let gas_spent = execute(&mut session, tx) + let gas_spent = execute(&mut session, &tx, 0, 0, 0) .expect("Executing transaction should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -552,7 +553,7 @@ fn convert_to_moonlight_fails() { .to_vec(), }; - let tx = MoonlightTransaction::new( + let tx = Transaction::moonlight( &moonlight_sk, None, 0, @@ -566,8 +567,8 @@ fn convert_to_moonlight_fails() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); // check that the transaction execution panicked with the correct message assert!(receipt.data.is_err()); @@ -659,7 +660,7 @@ fn convert_wrong_contract_targeted() { .to_vec(), }; - let tx = MoonlightTransaction::new( + let tx = Transaction::moonlight( &moonlight_sk, None, 0, @@ -672,7 +673,7 @@ fn convert_wrong_contract_targeted() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = execute(&mut session, tx) + let receipt = execute(&mut session, &tx, 0, 0, 0) .expect("Executing transaction should succeed"); update_root(session).expect("Updating the root should succeed"); @@ -731,7 +732,7 @@ fn contract_to_contract() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -744,8 +745,8 @@ fn contract_to_contract() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("SEND TO CONTRACT: {:?}", receipt.data); @@ -798,7 +799,7 @@ fn contract_to_account() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -811,8 +812,8 @@ fn contract_to_account() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("SEND TO ACCOUNT: {:?}", receipt.data); @@ -862,7 +863,7 @@ fn contract_to_account_insufficient_funds() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -875,8 +876,8 @@ fn contract_to_account_insufficient_funds() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("SEND TO ACCOUNT (insufficient funds): {:?}", receipt.data); @@ -933,7 +934,7 @@ fn contract_to_account_direct_call() { .to_vec(), }); - let transaction = MoonlightTransaction::new( + let transaction = Transaction::moonlight( &moonlight_sk, None, 0, @@ -946,8 +947,8 @@ fn contract_to_account_direct_call() { ) .expect("Creating moonlight transaction should succeed"); - let receipt = - execute(session, transaction).expect("Transaction should succeed"); + let receipt = execute(session, &transaction, 0, 0, 0) + .expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!( diff --git a/contracts/transfer/tests/phoenix.rs b/contracts/transfer/tests/phoenix.rs index 97496751c..0b3bbc0fb 100644 --- a/contracts/transfer/tests/phoenix.rs +++ b/contracts/transfer/tests/phoenix.rs @@ -24,12 +24,12 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::transfer::{ - ContractToAccount, ContractToContract, TRANSFER_CONTRACT, + ContractToAccount, ContractToContract, Transaction, TRANSFER_CONTRACT, }; use dusk_core::{BlsScalar, JubJubScalar, LUX}; use dusk_vm::{ - new_genesis_session, new_session, ContractData, Error as VMError, Session, - VM, + execute, new_genesis_session, new_session, ContractData, Error as VMError, + Session, VM, }; use ff::Field; use rand::rngs::StdRng; @@ -37,7 +37,7 @@ use rand::{CryptoRng, RngCore, SeedableRng}; use rusk_prover::LocalProver; use crate::common::utils::{ - account, chain_id, contract_balance, execute, existing_nullifiers, + account, chain_id, contract_balance, existing_nullifiers, filter_notes_owned_by, leaves_from_height, new_owned_notes_value, owned_notes_value, update_root, }; @@ -254,7 +254,7 @@ fn transfer_1_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -361,7 +361,7 @@ fn transfer_2_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -469,7 +469,7 @@ fn transfer_3_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -577,7 +577,7 @@ fn transfer_4_2() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -681,7 +681,7 @@ fn transfer_gas_fails() { let total_num_notes_before_tx = num_notes(session).expect("Getting num_notes should succeed"); - let result = execute(session, tx); + let result = execute(session, &tx, 0, 0, 0); assert!( result.is_err(), @@ -752,7 +752,7 @@ fn alice_ping() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -822,7 +822,7 @@ fn contract_deposit() { contract_call, ); - let gas_spent = execute(session, tx.clone()) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -835,7 +835,7 @@ fn contract_deposit() { PHOENIX_GENESIS_VALUE, transfer_value + tx.deposit() - + tx.max_fee() + + tx.gas_limit() * tx.gas_price() + tx.outputs()[1] .value(Some(&PhoenixViewKey::from(&phoenix_sender_sk))) .unwrap() @@ -930,7 +930,7 @@ fn contract_withdraw() { contract_call, ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -1056,7 +1056,8 @@ fn convert_to_phoenix_fails() { Some(contract_call), ); - let receipt = execute(session, tx).expect("Executing TX should succeed"); + let receipt = + execute(session, &tx, 0, 0, 0).expect("Executing TX should succeed"); // check that the transaction execution panicked with the correct message assert!(receipt.data.is_err()); @@ -1176,7 +1177,7 @@ fn convert_to_moonlight() { Some(contract_call), ); - let gas_spent = execute(session, tx) + let gas_spent = execute(session, &tx, 0, 0, 0) .expect("Executing TX should succeed") .gas_spent; update_root(session).expect("Updating the root should succeed"); @@ -1286,7 +1287,7 @@ fn convert_wrong_contract_targeted() { Some(contract_call), ); - let receipt = execute(&mut session, tx) + let receipt = execute(&mut session, &tx, 0, 0, 0) .expect("Executing transaction should succeed"); update_root(session).expect("Updating the root should succeed"); @@ -1377,7 +1378,8 @@ fn contract_to_contract() { Some(contract_call), ); - let receipt = execute(session, tx).expect("Transaction should succeed"); + let receipt = + execute(session, &tx, 0, 0, 0).expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("CONTRACT TO CONTRACT: {gas_spent} gas"); @@ -1470,7 +1472,8 @@ fn contract_to_account() { Some(contract_call), ); - let receipt = execute(session, tx).expect("Transaction should succeed"); + let receipt = + execute(session, &tx, 0, 0, 0).expect("Transaction should succeed"); let gas_spent = receipt.gas_spent; println!("CONTRACT TO ACCOUNT: {gas_spent} gas"); @@ -1585,7 +1588,7 @@ fn create_phoenix_transaction( obfuscated_transaction: bool, deposit: u64, data: Option>, -) -> PhoenixTransaction { +) -> Transaction { // Get the root of the tree of phoenix-notes. let root = root(session).expect("Getting the anchor should be successful"); @@ -1629,4 +1632,5 @@ fn create_phoenix_transaction( &LocalProver, ) .expect("creating the creation shouldn't fail") + .into() } From 28bd7b28114d6faaed081d6b9f6adcbb1bea6a1f Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:06:28 +0100 Subject: [PATCH 04/19] stake-contract: Use `vm::execute` for tests --- contracts/stake/tests/common/utils.rs | 41 +------------------------- contracts/stake/tests/partial_stake.rs | 29 +++++++++--------- contracts/stake/tests/stake.rs | 17 +++++------ 3 files changed, 23 insertions(+), 64 deletions(-) diff --git a/contracts/stake/tests/common/utils.rs b/contracts/stake/tests/common/utils.rs index ac4b672e3..8ba795690 100644 --- a/contracts/stake/tests/common/utils.rs +++ b/contracts/stake/tests/common/utils.rs @@ -6,7 +6,6 @@ use std::sync::mpsc; -use dusk_core::abi::ContractError; use dusk_core::transfer::data::TransactionData; use dusk_core::transfer::phoenix::{ Note, NoteLeaf, NoteOpening, NoteTreeItem, PublicKey as PhoenixPublicKey, @@ -15,7 +14,7 @@ use dusk_core::transfer::phoenix::{ }; use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; use dusk_core::{BlsScalar, LUX}; -use dusk_vm::{CallReceipt, Error as VMError, Session}; +use dusk_vm::{Error as VMError, Session}; use rand::rngs::StdRng; use rusk_prover::LocalProver; @@ -98,44 +97,6 @@ pub fn filter_notes_owned_by>( .collect() } -/// Executes a transaction, returning the call receipt -pub fn execute( - session: &mut Session, - tx: impl Into, -) -> Result, ContractError>>, VMError> { - let tx = tx.into(); - - // Spend the inputs and execute the call. If this errors the transaction is - // unspendable. - let mut receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "spend_and_execute", - &tx, - tx.gas_limit(), - )?; - - // Ensure all gas is consumed if there's an error in the contract call - if receipt.data.is_err() { - receipt.gas_spent = receipt.gas_limit; - } - - // Refund the appropriate amount to the transaction. This call is guaranteed - // to never error. If it does, then a programming error has occurred. As - // such, the call to `Result::expect` is warranted. - let refund_receipt = session - .call::<_, ()>( - TRANSFER_CONTRACT, - "refund", - &receipt.gas_spent, - u64::MAX, - ) - .expect("Refunding must succeed"); - - receipt.events.extend(refund_receipt.events); - - Ok(receipt) -} - /// Generate a TxCircuit given the sender secret-key, receiver public-key, the /// input note positions in the transaction tree and the new output-notes. pub fn create_transaction( diff --git a/contracts/stake/tests/partial_stake.rs b/contracts/stake/tests/partial_stake.rs index 0db5c20ef..e4beb8d4a 100644 --- a/contracts/stake/tests/partial_stake.rs +++ b/contracts/stake/tests/partial_stake.rs @@ -11,8 +11,8 @@ use dusk_core::signatures::bls::{ use dusk_core::stake::{Reward, RewardReason, EPOCH, STAKE_CONTRACT}; use dusk_core::transfer::TRANSFER_CONTRACT; use dusk_vm::{ - new_genesis_session, new_session, ContractData, Error as VMError, Session, - VM, + execute, new_genesis_session, new_session, ContractData, Error as VMError, + Session, VM, }; use rand::rngs::StdRng; use rand::SeedableRng; @@ -21,7 +21,6 @@ use wallet_core::transaction::{ }; pub mod common; - use crate::common::assert::*; use crate::common::init::CHAIN_ID; use crate::common::utils::*; @@ -63,7 +62,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st stake transaction let gas_spent_1 = receipt.gas_spent; @@ -91,7 +90,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 2nd stake transaction let gas_spent_2 = receipt.gas_spent; @@ -125,7 +124,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 3rd stake transaction let gas_spent_3 = receipt.gas_spent; @@ -160,7 +159,7 @@ fn stake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - assert!(execute(&mut session, tx,).is_err()); + assert!(execute(&mut session, &tx, 0, 0, 0).is_err()); Ok(()) } @@ -194,7 +193,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; let mut moonlight_balance = GENESIS_VALUE - STAKE_VALUE - receipt.gas_spent; assert_moonlight(&mut session, &moonlight_pk, moonlight_balance, nonce); @@ -216,7 +215,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st unstake transaction let gas_spent_1 = receipt.gas_spent; @@ -246,7 +245,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; total_stake = STAKE_VALUE; let mut locked = unstake_1 / 10; assert_stake(&mut session, &stake_pk, total_stake, locked, 0); @@ -272,7 +271,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 2nd unstake transaction let gas_spent_2 = receipt.gas_spent; @@ -310,7 +309,7 @@ fn unstake() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 3rd unstake transaction let gas_spent_3 = receipt.gas_spent; @@ -353,7 +352,7 @@ fn withdraw_reward() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; let mut moonlight_balance = GENESIS_VALUE - STAKE_VALUE - receipt.gas_spent; assert_moonlight(&mut session, &moonlight_pk, moonlight_balance, nonce); // add a reward to the staked key @@ -378,7 +377,7 @@ fn withdraw_reward() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st reward withdrawal let gas_spent_1 = receipt.gas_spent; @@ -413,7 +412,7 @@ fn withdraw_reward() -> Result<(), VMError> { CHAIN_ID, ) .expect("tx creation should pass"); - let receipt = execute(&mut session, tx)?; + let receipt = execute(&mut session, &tx, 0, 0, 0)?; // verify 1st reward withdrawal let gas_spent_2 = receipt.gas_spent; diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index aed9e0f1c..708892076 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -4,8 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -pub mod common; - use dusk_core::signatures::bls::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, }; @@ -21,11 +19,12 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::{dusk, JubJubScalar}; -use dusk_vm::{new_session, VM}; +use dusk_vm::{execute, new_session, VM}; use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; +pub mod common; use crate::common::assert::{ assert_reward_event, assert_stake, assert_stake_event, }; @@ -86,8 +85,8 @@ fn stake_withdraw_unstake() { contract_call, ); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); let gas_spent = receipt.gas_spent; receipt.data.expect("Executed TX should not error"); @@ -181,8 +180,8 @@ fn stake_withdraw_unstake() { let mut session = new_session(vm, base, CHAIN_ID, 2) .expect("Instantiating new session should succeed"); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); let gas_spent = receipt.gas_spent; receipt.data.expect("Executed TX should not error"); @@ -277,8 +276,8 @@ fn stake_withdraw_unstake() { let mut session = new_session(vm, base, CHAIN_ID, 3) .expect("Instantiating new session should succeed"); - let receipt = - execute(&mut session, tx).expect("Executing TX should succeed"); + let receipt = execute(&mut session, &tx, 0, 0, 0) + .expect("Executing TX should succeed"); update_root(&mut session).expect("Updating the root should succeed"); let gas_spent = receipt.gas_spent; From c6b05e6e7b3afa61db370977184810c0547acc13 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:06:53 +0100 Subject: [PATCH 05/19] node: Move `bytecode_charge` function to `core` --- node/src/mempool.rs | 14 ++++++-------- node/src/vm.rs | 14 -------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/node/src/mempool.rs b/node/src/mempool.rs index 2d0470399..a1ec8593d 100644 --- a/node/src/mempool.rs +++ b/node/src/mempool.rs @@ -212,7 +212,7 @@ impl MempoolSrv { return Err(TxAcceptanceError::GasPriceTooLow(1)); } - if let Some(deploy) = tx.inner.deploy() { + if tx.inner.deploy().is_some() { let vm = vm.read().await; let min_deployment_gas_price = vm.min_deployment_gas_price(); if tx.gas_price() < min_deployment_gas_price { @@ -222,13 +222,11 @@ impl MempoolSrv { } let gas_per_deploy_byte = vm.gas_per_deploy_byte(); - let min_gas_limit = vm::bytecode_charge( - &deploy.bytecode, - gas_per_deploy_byte, - vm.min_deploy_points(), - ); - if tx.inner.gas_limit() < min_gas_limit { - return Err(TxAcceptanceError::GasLimitTooLow(min_gas_limit)); + let deploy_charge = tx + .inner + .deploy_charge(gas_per_deploy_byte, vm.min_deploy_points()); + if tx.inner.gas_limit() < deploy_charge { + return Err(TxAcceptanceError::GasLimitTooLow(deploy_charge)); } } else { let vm = vm.read().await; diff --git a/node/src/vm.rs b/node/src/vm.rs index dec91117e..bc104b0a4 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -9,11 +9,9 @@ use dusk_consensus::operations::{CallParams, VerificationOutput, Voter}; use dusk_consensus::user::provisioners::Provisioners; use dusk_consensus::user::stake::Stake; use dusk_core::signatures::bls::PublicKey as BlsPublicKey; -use dusk_core::transfer::data::ContractBytecode; use dusk_core::transfer::moonlight::AccountData; use node_data::events::contract::ContractEvent; use node_data::ledger::{Block, SpentTransaction, Transaction}; -use std::cmp::max; #[derive(Default)] pub struct Config {} @@ -102,15 +100,3 @@ pub enum PreverificationResult { nonce_used: u64, }, } - -// Returns gas charge for bytecode deployment. -pub fn bytecode_charge( - bytecode: &ContractBytecode, - gas_per_deploy_byte: u64, - min_deploy_points: u64, -) -> u64 { - max( - bytecode.bytes.len() as u64 * gas_per_deploy_byte, - min_deploy_points, - ) -} From e59474dfdba9fa63007c2c24465fb27e8376f703 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:07:45 +0100 Subject: [PATCH 06/19] rusk: Expose transaction execution function through `dusk-vm` --- rusk/src/lib/gen_id.rs | 57 ------- rusk/src/lib/lib.rs | 1 - rusk/src/lib/node/rusk.rs | 169 +-------------------- rusk/tests/services/contract_deployment.rs | 3 +- rusk/tests/services/contract_stake.rs | 3 +- rusk/tests/services/owner_calls.rs | 3 +- 6 files changed, 8 insertions(+), 228 deletions(-) delete mode 100644 rusk/src/lib/gen_id.rs diff --git a/rusk/src/lib/gen_id.rs b/rusk/src/lib/gen_id.rs deleted file mode 100644 index f93a0963c..000000000 --- a/rusk/src/lib/gen_id.rs +++ /dev/null @@ -1,57 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -use blake2b_simd::Params; -use dusk_core::abi::{ContractId, CONTRACT_ID_BYTES}; - -/// Generate a [`ContractId`] address from: -/// - slice of bytes, -/// - nonce -/// - owner -pub fn gen_contract_id( - bytes: impl AsRef<[u8]>, - nonce: u64, - owner: impl AsRef<[u8]>, -) -> ContractId { - let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); - hasher.update(bytes.as_ref()); - hasher.update(&nonce.to_le_bytes()[..]); - hasher.update(owner.as_ref()); - let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher - .finalize() - .as_bytes() - .try_into() - .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); - ContractId::from_bytes(hash_bytes) -} - -#[cfg(test)] -mod tests { - use super::*; - use rand::rngs::StdRng; - use rand::{RngCore, SeedableRng}; - - #[test] - fn test_gen_contract_id() { - let mut rng = StdRng::seed_from_u64(42); - - let mut bytes = vec![0; 1000]; - rng.fill_bytes(&mut bytes); - - let nonce = rng.next_u64(); - - let mut owner = vec![0, 100]; - rng.fill_bytes(&mut owner); - - let contract_id = - gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); - - assert_eq!( - hex::encode(contract_id.as_bytes()), - "2da8b6277789a88c7215789e227ef4dd97486db252e554805c7b874a17e07785" - ); - } -} diff --git a/rusk/src/lib/lib.rs b/rusk/src/lib/lib.rs index 541c42bc2..603cef789 100644 --- a/rusk/src/lib/lib.rs +++ b/rusk/src/lib/lib.rs @@ -8,7 +8,6 @@ mod bloom; mod error; -pub mod gen_id; pub mod http; #[cfg(feature = "chain")] pub mod node; diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 7d39c289c..df707b2fc 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -16,19 +16,18 @@ use dusk_consensus::config::{ RATIFICATION_COMMITTEE_CREDITS, VALIDATION_COMMITTEE_CREDITS, }; use dusk_consensus::operations::{CallParams, VerificationOutput, Voter}; -use dusk_core::abi::{ContractError, Event}; +use dusk_core::abi::Event; use dusk_core::signatures::bls::PublicKey as BlsPublicKey; use dusk_core::stake::{ Reward, RewardReason, StakeData, StakeKeys, STAKE_CONTRACT, }; use dusk_core::transfer::{ - data::ContractDeploy, moonlight::AccountData, - Transaction as ProtocolTransaction, PANIC_NONCE_NOT_READY, - TRANSFER_CONTRACT, + moonlight::AccountData, PANIC_NONCE_NOT_READY, TRANSFER_CONTRACT, }; use dusk_core::{BlsScalar, Dusk}; -use dusk_vm::{new_session, CallReceipt, Error as VMError, Session, VM}; -use node::vm::bytecode_charge; +use dusk_vm::{ + execute, new_session, CallReceipt, Error as VMError, Session, VM, +}; use node::DUSK_CONSENSUS_KEY; use node_data::events::contract::{ContractEvent, ContractTxEvent}; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; @@ -40,7 +39,6 @@ use tracing::info; use {node_data::archive::ArchivalData, tokio::sync::mpsc::Sender}; use crate::bloom::Bloom; -use crate::gen_id::gen_contract_id; use crate::http::RuesEvent; use crate::node::{coinbase_value, Rusk, RuskTip}; use crate::Error::InvalidCreditsCount; @@ -665,163 +663,6 @@ fn accept( )) } -// Contract deployment will fail and charge full gas limit in the -// following cases: -// 1) Transaction gas limit is smaller than deploy charge plus gas used for -// spending funds. -// 2) Transaction's bytecode's bytes are not consistent with bytecode's hash. -// 3) Deployment fails for deploy-specific reasons like e.g.: -// - contract already deployed -// - corrupted bytecode -// - sufficient gas to spend funds yet insufficient for deployment -fn contract_deploy( - session: &mut Session, - deploy: &ContractDeploy, - gas_limit: u64, - gas_per_deploy_byte: u64, - min_deploy_points: u64, - receipt: &mut CallReceipt, ContractError>>, -) { - let deploy_charge = bytecode_charge( - &deploy.bytecode, - gas_per_deploy_byte, - min_deploy_points, - ); - let min_gas_limit = receipt.gas_spent + deploy_charge; - let hash = blake3::hash(deploy.bytecode.bytes.as_slice()); - if gas_limit < min_gas_limit { - receipt.data = Err(ContractError::OutOfGas); - } else if hash != deploy.bytecode.hash { - receipt.data = - Err(ContractError::Panic("failed bytecode hash check".into())) - } else { - let result = session.deploy_raw( - Some(gen_contract_id( - &deploy.bytecode.bytes, - deploy.nonce, - &deploy.owner, - )), - deploy.bytecode.bytes.as_slice(), - deploy.init_args.clone(), - deploy.owner.clone(), - gas_limit, - ); - match result { - // Should the gas spent by the INIT method charged too? - Ok(_) => receipt.gas_spent += deploy_charge, - Err(err) => { - info!("Tx caused deployment error {err:?}"); - let msg = format!("failed deployment: {err:?}"); - receipt.data = Err(ContractError::Panic(msg)) - } - } - } -} - -/// Executes a transaction, returning the receipt of the call and the gas spent. -/// The following steps are performed: -/// -/// 1. Check if the transaction contains contract deployment data, and if so, -/// verifies if gas limit is enough for deployment and if the gas price is -/// sufficient for deployment. If either gas price or gas limit is not -/// sufficient for deployment, transaction is discarded. -/// -/// 2. Call the "spend_and_execute" function on the transfer contract with -/// unlimited gas. If this fails, an error is returned. If an error is -/// returned the transaction should be considered unspendable/invalid, but no -/// re-execution of previous transactions is required. -/// -/// 3. If the transaction contains contract deployment data, additional checks -/// are performed and if they pass, deployment is executed. The following -/// checks are performed: -/// - gas limit should be is smaller than deploy charge plus gas used for -/// spending funds -/// - transaction's bytecode's bytes are consistent with bytecode's hash -/// Deployment execution may fail for deployment-specific reasons, such as -/// for example: -/// - contract already deployed -/// - corrupted bytecode -/// If deployment execution fails, the entire gas limit is consumed and error -/// is returned. -/// -/// 4. Call the "refund" function on the transfer contract with unlimited gas. -/// The amount charged depends on the gas spent by the transaction, and the -/// optional contract call in steps 2 or 3. -/// -/// Note that deployment transaction will never be re-executed for reasons -/// related to deployment, as it is either discarded or it charges the -/// full gas limit. It might be re-executed only if some other transaction -/// failed to fit the block. -fn execute( - session: &mut Session, - tx: &ProtocolTransaction, - gas_per_deploy_byte: u64, - min_deploy_points: u64, - min_deployment_gas_price: u64, -) -> Result, ContractError>>, VMError> { - // Transaction will be discarded if it is a deployment transaction - // with gas limit smaller than deploy charge. - if let Some(deploy) = tx.deploy() { - let deploy_charge = bytecode_charge( - &deploy.bytecode, - gas_per_deploy_byte, - min_deploy_points, - ); - if tx.gas_price() < min_deployment_gas_price { - return Err(VMError::Panic("gas price too low to deploy".into())); - } - if tx.gas_limit() < deploy_charge { - return Err(VMError::Panic("not enough gas to deploy".into())); - } - } - - let tx_stripped = tx.strip_off_bytecode(); - // Spend the inputs and execute the call. If this errors the transaction is - // unspendable. - let mut receipt = session.call::<_, Result, ContractError>>( - TRANSFER_CONTRACT, - "spend_and_execute", - tx_stripped.as_ref().unwrap_or(tx), - tx.gas_limit(), - )?; - - // Deploy if this is a deployment transaction and spend part is successful. - if let Some(deploy) = tx.deploy() { - let gas_left = tx.gas_limit() - receipt.gas_spent; - if receipt.data.is_ok() { - contract_deploy( - session, - deploy, - gas_left, - gas_per_deploy_byte, - min_deploy_points, - &mut receipt, - ); - } - }; - - // Ensure all gas is consumed if there's an error in the contract call - if receipt.data.is_err() { - receipt.gas_spent = receipt.gas_limit; - } - - // Refund the appropriate amount to the transaction. This call is guaranteed - // to never error. If it does, then a programming error has occurred. As - // such, the call to `Result::expect` is warranted. - let refund_receipt = session - .call::<_, ()>( - TRANSFER_CONTRACT, - "refund", - &receipt.gas_spent, - u64::MAX, - ) - .expect("Refunding must succeed"); - - receipt.events.extend(refund_receipt.events); - - Ok(receipt) -} - fn reward_slash_and_update_root( session: &mut Session, block_height: u64, diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 83c0fdd09..4225ed43f 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -8,14 +8,13 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; -use dusk_core::abi::ContractId; +use dusk_core::abi::{gen_contract_id, ContractId}; use dusk_core::transfer::data::{ ContractBytecode, ContractDeploy, TransactionData, }; use dusk_vm::{new_session, ContractData, Error as VMError, VM}; use rand::prelude::*; use rand::rngs::StdRng; -use rusk::gen_id::gen_contract_id; use rusk::{Result, Rusk}; use rusk_recovery_tools::state; use tempfile::tempdir; diff --git a/rusk/tests/services/contract_stake.rs b/rusk/tests/services/contract_stake.rs index 83a853f4b..e9b37fcdd 100644 --- a/rusk/tests/services/contract_stake.rs +++ b/rusk/tests/services/contract_stake.rs @@ -10,13 +10,12 @@ use std::sync::{Arc, RwLock}; use dusk_core::stake::{self, Stake, DEFAULT_MINIMUM_STAKE, EPOCH}; use dusk_bytes::Serializable; -use dusk_core::abi::ContractId; +use dusk_core::abi::{gen_contract_id, ContractId}; use dusk_core::transfer::data::ContractCall; use dusk_core::transfer::{self, Transaction}; use node_data::ledger::SpentTransaction; use rand::prelude::*; use rand::rngs::StdRng; -use rusk::gen_id::gen_contract_id; use rusk::{Result, Rusk}; use std::collections::HashMap; use tempfile::tempdir; diff --git a/rusk/tests/services/owner_calls.rs b/rusk/tests/services/owner_calls.rs index 451614150..8151c590c 100644 --- a/rusk/tests/services/owner_calls.rs +++ b/rusk/tests/services/owner_calls.rs @@ -14,13 +14,12 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; -use dusk_core::abi::ContractId; +use dusk_core::abi::{gen_contract_id, ContractId}; use dusk_core::signatures::bls::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature as BlsSignature, }; use dusk_vm::{new_session, CallReceipt, ContractData, Session, VM}; -use rusk::gen_id::gen_contract_id; use rusk::{Error, Result, Rusk}; use rusk_recovery_tools::state; use tempfile::tempdir; From eafb0d1889c7bc823c497d0b76354294034cc0ef Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:45:05 +0100 Subject: [PATCH 07/19] vm: Rename cache env variables `RUSK_ABI_..` to `DUSK_VM_..` --- vm/src/cache.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/cache.rs b/vm/src/cache.rs index 103b73b60..a70ea1633 100644 --- a/vm/src/cache.rs +++ b/vm/src/cache.rs @@ -76,7 +76,7 @@ define_cache!( with_plonk_cache, bool, 512, - "RUSK_ABI_PLONK_CACHE_SIZE" + "DUSK_VM_PLONK_CACHE_SIZE" ); define_cache!( get_groth16_verification, @@ -84,7 +84,7 @@ define_cache!( with_groth16_cache, bool, 512, - "RUSK_ABI_GROTH16_CACHE_SIZE" + "DUSK_VM_GROTH16_CACHE_SIZE" ); define_cache!( get_bls_verification, @@ -92,5 +92,5 @@ define_cache!( with_bls_cache, bool, 512, - "RUSK_ABI_BLS_CACHE_SIZE" + "DUSK_VM_BLS_CACHE_SIZE" ); From b9b2f06a4fc69b1402af6b170ccd495e58c5e1bd Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:46:45 +0100 Subject: [PATCH 08/19] vm: Remove rustfmt.toml --- vm/rustfmt.toml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 vm/rustfmt.toml diff --git a/vm/rustfmt.toml b/vm/rustfmt.toml deleted file mode 100644 index 3450fc407..000000000 --- a/vm/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -max_width = 80 -wrap_comments = true From 89495e7fa285dca46fb4a1f39dd2b341090e0465 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:48:19 +0100 Subject: [PATCH 09/19] vm: Move `new_session` and `new_genesis_session` under `VM` --- vm/src/{session.rs => execute.rs} | 33 ++------------------------- vm/src/lib.rs | 38 ++++++++++++++++++++++++------- vm/tests/vm.rs | 5 ++-- 3 files changed, 35 insertions(+), 41 deletions(-) rename vm/src/{session.rs => execute.rs} (86%) diff --git a/vm/src/session.rs b/vm/src/execute.rs similarity index 86% rename from vm/src/session.rs rename to vm/src/execute.rs index 078a7eaa2..808ef4a75 100644 --- a/vm/src/session.rs +++ b/vm/src/execute.rs @@ -4,38 +4,9 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_core::abi::{gen_contract_id, ContractError, Metadata}; +use dusk_core::abi::{gen_contract_id, ContractError}; use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; -use piecrust::{CallReceipt, Error, Session, SessionData}; - -use crate::VM; - -/// Create a new session based on the given `VM`. -pub fn new( - vm: &VM, - base: [u8; 32], - chain_id: u8, - block_height: u64, -) -> Result { - vm.session( - SessionData::builder() - .base(base) - .insert(Metadata::CHAIN_ID, chain_id)? - .insert(Metadata::BLOCK_HEIGHT, block_height)?, - ) -} - -/// Create a new genesis session based on the given [`VM`]. -pub fn genesis(vm: &VM, chain_id: u8) -> Session { - vm.session( - SessionData::builder() - .insert(Metadata::CHAIN_ID, chain_id) - .expect("Inserting chain ID in metadata should succeed") - .insert(Metadata::BLOCK_HEIGHT, 0) - .expect("Inserting block height in metadata should succeed"), - ) - .expect("Creating a genesis session should always succeed") -} +use piecrust::{CallReceipt, Error, Session}; /// Executes a transaction, returning the receipt of the call and the gas spent. /// The following steps are performed: diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 6c3bfdd43..1a5974c88 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -13,6 +13,7 @@ extern crate alloc; +pub use self::execute::execute; pub use piecrust::{ CallReceipt, CallTree, CallTreeElem, ContractData, Error, PageOpening, Session, @@ -23,7 +24,7 @@ use std::fmt::{self, Debug, Formatter}; use std::path::{Path, PathBuf}; use std::thread; -use dusk_core::abi::Query; +use dusk_core::abi::{Metadata, Query}; use piecrust::{SessionData, VM as PiecrustVM}; use self::host_queries::{ @@ -32,11 +33,8 @@ use self::host_queries::{ }; pub(crate) mod cache; +mod execute; pub mod host_queries; -pub(crate) mod session; -pub use session::{ - execute, genesis as new_genesis_session, new as new_session, -}; /// Dusk VM is a [`PiecrustVM`] enriched with the host functions specified in /// Dusk's ABI. @@ -84,7 +82,8 @@ impl VM { Ok(vm) } - /// Spawn a [`Session`]. + /// Spawn a [`Session`] on top of the given `commit`, given the `chain_id` + /// and `block_height`. /// /// # Errors /// If base commit is provided but does not exist. @@ -92,9 +91,32 @@ impl VM { /// [`Session`]: Session pub fn session( &self, - data: impl Into, + base: [u8; 32], + chain_id: u8, + block_height: u64, ) -> Result { - self.0.session(data) + self.0.session( + SessionData::builder() + .base(base) + .insert(Metadata::CHAIN_ID, chain_id)? + .insert(Metadata::BLOCK_HEIGHT, block_height)?, + ) + } + + /// Spawn a new genesis-[`Session`] given the `chain_id` with a + /// `block_height` of 0. + pub fn genesis_session(&self, chain_id: u8) -> Session { + self.0 + .session( + SessionData::builder() + .insert(Metadata::CHAIN_ID, chain_id) + .expect("Inserting chain ID in metadata should succeed") + .insert(Metadata::BLOCK_HEIGHT, 0) + .expect( + "Inserting block height in metadata should succeed", + ), + ) + .expect("Creating a genesis session should always succeed") } /// Return all existing commits. diff --git a/vm/tests/vm.rs b/vm/tests/vm.rs index 2a17e6c17..3478d10c3 100644 --- a/vm/tests/vm.rs +++ b/vm/tests/vm.rs @@ -66,7 +66,7 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { "../../target/dusk/wasm32-unknown-unknown/release/host_fn.wasm" ); - let mut session = dusk_vm::new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); let contract_id = session .deploy( @@ -78,7 +78,8 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { let base = session.commit().expect("Committing should succeed"); - let session = dusk_vm::new_session(vm, base, CHAIN_ID, height) + let session = vm + .session(base, CHAIN_ID, height) .expect("Instantiating new session should succeed"); (session, contract_id) From 1fcc390242400713d1306c2bd529da975f9a20a6 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:49:15 +0100 Subject: [PATCH 10/19] rusk-recovery: Move `new_session` and `new_genesis_session` under `VM` --- rusk-recovery/src/state.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index ea60b9bef..71a286557 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -15,7 +15,7 @@ use dusk_core::stake::{StakeAmount, StakeData, StakeKeys, STAKE_CONTRACT}; use dusk_core::transfer::phoenix::{Note, PublicKey, Sender}; use dusk_core::transfer::TRANSFER_CONTRACT; use dusk_core::JubJubScalar; -use dusk_vm::{new_genesis_session, new_session, ContractData, Session, VM}; +use dusk_vm::{ContractData, Session, VM}; use ff::Field; use once_cell::sync::Lazy; use rand::rngs::StdRng; @@ -177,7 +177,7 @@ fn generate_empty_state>( let state_dir = state_dir.as_ref(); let vm = VM::new(state_dir)?; - let mut session = new_genesis_session(&vm, GENESIS_CHAIN_ID); + let mut session = vm.genesis_session(GENESIS_CHAIN_ID); let transfer_code = include_bytes!( "../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm" @@ -260,12 +260,8 @@ where None => generate_empty_state(state_dir, snapshot), }?; - let mut session = new_session( - &vm, - old_commit_id, - GENESIS_CHAIN_ID, - GENESIS_BLOCK_HEIGHT, - )?; + let mut session = + vm.session(old_commit_id, GENESIS_CHAIN_ID, GENESIS_BLOCK_HEIGHT)?; generate_transfer_state(&mut session, snapshot)?; generate_stake_state(&mut session, snapshot)?; From 2accad10738c0fe76bcf1ae7b368305ec9450881 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:49:44 +0100 Subject: [PATCH 11/19] transfer-contract: Move `new_session` and `new_genesis_session` under `VM` --- contracts/transfer/tests/moonlight.rs | 9 ++++----- contracts/transfer/tests/phoenix.rs | 10 ++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/contracts/transfer/tests/moonlight.rs b/contracts/transfer/tests/moonlight.rs index a42d80c3b..b2ab55c4f 100644 --- a/contracts/transfer/tests/moonlight.rs +++ b/contracts/transfer/tests/moonlight.rs @@ -21,9 +21,7 @@ use dusk_core::transfer::{ ContractToAccount, ContractToContract, Transaction, TRANSFER_CONTRACT, }; use dusk_core::{dusk, JubJubScalar, LUX}; -use dusk_vm::{ - execute, new_genesis_session, new_session, ContractData, Session, VM, -}; +use dusk_vm::{execute, ContractData, Session, VM}; use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; @@ -70,7 +68,7 @@ fn instantiate(moonlight_pk: &AccountPublicKey) -> Session { let vm = &mut VM::ephemeral().expect("Creating ephemeral VM should work"); - let mut session = new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); session .deploy( @@ -125,7 +123,8 @@ fn instantiate(moonlight_pk: &AccountPublicKey) -> Session { // operations to 1 let base = session.commit().expect("Committing should succeed"); // start a new session from that base-commit - let mut session = new_session(vm, base, CHAIN_ID, 1) + let mut session = vm + .session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed"); // check genesis state diff --git a/contracts/transfer/tests/phoenix.rs b/contracts/transfer/tests/phoenix.rs index 0b3bbc0fb..e481c5716 100644 --- a/contracts/transfer/tests/phoenix.rs +++ b/contracts/transfer/tests/phoenix.rs @@ -27,10 +27,7 @@ use dusk_core::transfer::{ ContractToAccount, ContractToContract, Transaction, TRANSFER_CONTRACT, }; use dusk_core::{BlsScalar, JubJubScalar, LUX}; -use dusk_vm::{ - execute, new_genesis_session, new_session, ContractData, Error as VMError, - Session, VM, -}; +use dusk_vm::{execute, ContractData, Error as VMError, Session, VM}; use ff::Field; use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; @@ -84,7 +81,7 @@ fn instantiate( let vm = &mut VM::ephemeral().expect("Creating ephemeral VM should work"); - let mut session = new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); session .deploy( @@ -160,7 +157,8 @@ fn instantiate( // operations to 1 let base = session.commit().expect("Committing should succeed"); // start a new session from that base-commit - let mut session = new_session(vm, base, CHAIN_ID, 1) + let mut session = vm + .session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed"); // check that the genesis state is correct: From 4a57371b72a2c18eb25db032a7e315d9e2ce0543 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:50:04 +0100 Subject: [PATCH 12/19] stake-contract: Move `new_session` and `new_genesis_session` under `VM` --- contracts/stake/benches/get_provisioners.rs | 5 +++-- contracts/stake/tests/common/init.rs | 4 ++-- contracts/stake/tests/partial_stake.rs | 14 ++++++-------- contracts/stake/tests/stake.rs | 8 +++++--- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/contracts/stake/benches/get_provisioners.rs b/contracts/stake/benches/get_provisioners.rs index cb4d8a993..164952c5c 100644 --- a/contracts/stake/benches/get_provisioners.rs +++ b/contracts/stake/benches/get_provisioners.rs @@ -22,6 +22,7 @@ const NUM_STAKES: usize = 1000; const OWNER: [u8; 32] = [0; 32]; const POINT_LIMIT: u64 = 0x100000000; const TEST_STAKE: u64 = 500_000_000_000_000; +const CHAIN_ID: u8 = 42; fn config() -> Criterion { Criterion::default().sample_size(SAMPLE_SIZE) @@ -41,7 +42,7 @@ fn instantiate(vm: &VM) -> Session { "../../../target/dusk/wasm32-unknown-unknown/release/stake_contract.wasm" ); - let mut session = abi::new_genesis_session(vm); + let mut session = vm.genesis_session(CHAIN_ID); session .deploy( @@ -63,7 +64,7 @@ fn instantiate(vm: &VM) -> Session { let base = session.commit().expect("Committing should succeed"); - abi::new_session(vm, base, 1) + vm.session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } diff --git a/contracts/stake/tests/common/init.rs b/contracts/stake/tests/common/init.rs index 3e0ec49d6..e697b8460 100644 --- a/contracts/stake/tests/common/init.rs +++ b/contracts/stake/tests/common/init.rs @@ -27,7 +27,7 @@ pub fn instantiate( pk: &PhoenixPublicKey, genesis_value: u64, ) -> Session { - let mut session = dusk_vm::new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); // deploy transfer-contract let transfer_bytecode = include_bytes!( @@ -81,6 +81,6 @@ pub fn instantiate( // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - dusk_vm::new_session(vm, base, CHAIN_ID, 1) + vm.session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } diff --git a/contracts/stake/tests/partial_stake.rs b/contracts/stake/tests/partial_stake.rs index e4beb8d4a..e4759b867 100644 --- a/contracts/stake/tests/partial_stake.rs +++ b/contracts/stake/tests/partial_stake.rs @@ -10,10 +10,7 @@ use dusk_core::signatures::bls::{ }; use dusk_core::stake::{Reward, RewardReason, EPOCH, STAKE_CONTRACT}; use dusk_core::transfer::TRANSFER_CONTRACT; -use dusk_vm::{ - execute, new_genesis_session, new_session, ContractData, Error as VMError, - Session, VM, -}; +use dusk_vm::{execute, ContractData, Error as VMError, Session, VM}; use rand::rngs::StdRng; use rand::SeedableRng; use wallet_core::transaction::{ @@ -108,7 +105,7 @@ fn stake() -> Result<(), VMError> { // in order to test the locking some of the stake during a top-up, we need // to start a new session at a block-height on which the stake is eligible let base = session.commit()?; - let mut session = new_session(&vm, base, CHAIN_ID, 2 * EPOCH)?; + let mut session = vm.session(base, CHAIN_ID, 2 * EPOCH)?; // execute 3rd stake transaction let stake_3 = STAKE_VALUE - stake_1 - stake_2; @@ -232,7 +229,7 @@ fn unstake() -> Result<(), VMError> { // re-stake the unstaked value after the stake has become eligible let base = session.commit()?; - let mut session = new_session(&vm, base, CHAIN_ID, 2 * EPOCH)?; + let mut session = vm.session(base, CHAIN_ID, 2 * EPOCH)?; nonce += 1; let tx = moonlight_stake( &moonlight_sk, @@ -457,7 +454,7 @@ fn add_reward( /// genesis-value. fn instantiate(vm: &mut VM, moonlight_pk: &BlsPublicKey) -> Session { // create a new session using an ephemeral vm - let mut session = new_genesis_session(vm, CHAIN_ID); + let mut session = vm.genesis_session(CHAIN_ID); // deploy transfer-contract const OWNER: [u8; 32] = [0; 32]; @@ -501,7 +498,8 @@ fn instantiate(vm: &mut VM, moonlight_pk: &BlsPublicKey) -> Session { // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - let mut session = new_session(vm, base, CHAIN_ID, 1) + let mut session = vm + .session(base, CHAIN_ID, 1) .expect("Instantiating new session should succeed"); // check that the moonlight account is initialized as expected diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index 708892076..18ba44bc0 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -19,7 +19,7 @@ use dusk_core::transfer::withdraw::{ Withdraw, WithdrawReceiver, WithdrawReplayToken, }; use dusk_core::{dusk, JubJubScalar}; -use dusk_vm::{execute, new_session, VM}; +use dusk_vm::{execute, VM}; use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; @@ -177,7 +177,8 @@ fn stake_withdraw_unstake() { // set different block height so that the new notes are easily located and // filtered let base = session.commit().expect("Committing should succeed"); - let mut session = new_session(vm, base, CHAIN_ID, 2) + let mut session = vm + .session(base, CHAIN_ID, 2) .expect("Instantiating new session should succeed"); let receipt = execute(&mut session, &tx, 0, 0, 0) @@ -273,7 +274,8 @@ fn stake_withdraw_unstake() { // filtered // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - let mut session = new_session(vm, base, CHAIN_ID, 3) + let mut session = vm + .session(base, CHAIN_ID, 3) .expect("Instantiating new session should succeed"); let receipt = execute(&mut session, &tx, 0, 0, 0) From 9b98d5445dcf94df7c3e5ebd47c8cd22a3d7d193 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 14:50:22 +0100 Subject: [PATCH 13/19] rusk: Move `new_session` and `new_genesis_session` under `VM` --- rusk/src/lib/node/rusk.rs | 9 +++------ rusk/tests/rusk-state.rs | 6 +++--- rusk/tests/services/contract_deployment.rs | 8 +++++--- rusk/tests/services/owner_calls.rs | 7 ++++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index df707b2fc..e0b2ec304 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -25,9 +25,7 @@ use dusk_core::transfer::{ moonlight::AccountData, PANIC_NONCE_NOT_READY, TRANSFER_CONTRACT, }; use dusk_core::{BlsScalar, Dusk}; -use dusk_vm::{ - execute, new_session, CallReceipt, Error as VMError, Session, VM, -}; +use dusk_vm::{execute, CallReceipt, Error as VMError, Session, VM}; use node::DUSK_CONSENSUS_KEY; use node_data::events::contract::{ContractEvent, ContractTxEvent}; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; @@ -528,8 +526,7 @@ impl Rusk { tip.current }); - let session = - new_session(&self.vm, commit, self.chain_id, block_height)?; + let session = self.vm.session(commit, self.chain_id, block_height)?; Ok(session) } @@ -548,7 +545,7 @@ impl Rusk { for d in to_merge { if d == base { // Don't finalize the new tip, otherwise it will not be - // aceessible anymore + // accessible anymore continue; }; self.vm.finalize_commit(d)?; diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index 505978624..d9d687d29 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -83,9 +83,9 @@ where rusk.with_tip(|mut tip, vm| { let current_commit = tip.current; - let mut session = - dusk_vm::new_session(vm, current_commit, CHAIN_ID, BLOCK_HEIGHT) - .expect("current commit should exist"); + let mut session = vm + .session(current_commit, CHAIN_ID, BLOCK_HEIGHT) + .expect("current commit should exist"); session .call::<_, Note>( diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 4225ed43f..52ce440f6 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -12,7 +12,7 @@ use dusk_core::abi::{gen_contract_id, ContractId}; use dusk_core::transfer::data::{ ContractBytecode, ContractDeploy, TransactionData, }; -use dusk_vm::{new_session, ContractData, Error as VMError, VM}; +use dusk_vm::{ContractData, Error as VMError, VM}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; @@ -231,7 +231,8 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); - let mut session = new_session(&vm, commit, CHAIN_ID, 0) + let mut session = vm + .session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, @@ -249,7 +250,8 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); - let mut session = new_session(&vm, commit, CHAIN_ID, 0) + let mut session = vm + .session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, diff --git a/rusk/tests/services/owner_calls.rs b/rusk/tests/services/owner_calls.rs index 8151c590c..c92b182ae 100644 --- a/rusk/tests/services/owner_calls.rs +++ b/rusk/tests/services/owner_calls.rs @@ -19,7 +19,7 @@ use dusk_core::signatures::bls::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature as BlsSignature, }; -use dusk_vm::{new_session, CallReceipt, ContractData, Session, VM}; +use dusk_vm::{CallReceipt, ContractData, Session, VM}; use rusk::{Error, Result, Rusk}; use rusk_recovery_tools::state; use tempfile::tempdir; @@ -145,7 +145,8 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); - let mut session = new_session(&vm, commit, CHAIN_ID, 0) + let mut session = vm + .session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, @@ -170,7 +171,7 @@ impl Fixture { let vm = VM::new(self.path.as_path()).expect("VM creation should succeed"); self.session = Some( - new_session(&vm, commit, CHAIN_ID, 0) + vm.session(commit, CHAIN_ID, 0) .expect("Session creation should succeed"), ); } From b4e54c8697365c4da5994fa88b15526729d35d78 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 15:52:01 +0100 Subject: [PATCH 14/19] core: Remove left over mention of execution-core --- core/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/error.rs b/core/src/error.rs index d4e5bfcd6..66a1a0a3c 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -39,7 +39,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Execution-Core Error: {:?}", &self) + write!(f, "Dusk-Core Error: {:?}", &self) } } From c1e54a0009effe2283e60032e81add8d7d2004ff Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 15:52:40 +0100 Subject: [PATCH 15/19] core: Move `gen_contract_id` to `dusk_vm` --- core/Cargo.toml | 1 - core/src/abi.rs | 64 ------------------------------------------------- 2 files changed, 65 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index a70f4910d..11a26536e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,7 +19,6 @@ bytecheck = { workspace = true } rand = { workspace = true } ff = { workspace = true } blake3 = { workspace = true } -blake2b_simd = { workspace = true } # plonk dependencies dusk-plonk = { workspace = true, features = ["rkyv-impl", "alloc"], optional = true } diff --git a/core/src/abi.rs b/core/src/abi.rs index aca5e3f98..94a5232d4 100644 --- a/core/src/abi.rs +++ b/core/src/abi.rs @@ -14,8 +14,6 @@ pub use piecrust_uplink::{ #[cfg(feature = "abi")] pub use self::host_queries::*; -use blake2b_simd::Params; - /// Enum storing the metadata identifiers. pub enum Metadata {} @@ -46,68 +44,6 @@ impl Query { pub const VERIFY_BLS_MULTISIG: &'static str = "verify_bls_multisig"; } -/// Generate a [`ContractId`] address from: -/// - slice of bytes, -/// - nonce -/// - owner -/// -/// # Panics -/// Panics if [blake2b-hasher] doesn't produce a [`CONTRACT_ID_BYTES`] -/// bytes long hash. -/// -/// [blake2b-hasher]: [`blake2b_simd::Params.finalize`] -pub fn gen_contract_id( - bytes: impl AsRef<[u8]>, - nonce: u64, - owner: impl AsRef<[u8]>, -) -> ContractId { - let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); - hasher.update(bytes.as_ref()); - hasher.update(&nonce.to_le_bytes()[..]); - hasher.update(owner.as_ref()); - let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher - .finalize() - .as_bytes() - .try_into() - .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); - ContractId::from_bytes(hash_bytes) -} - -#[cfg(test)] -mod tests { - use alloc::vec; - - use rand::rngs::StdRng; - use rand::{RngCore, SeedableRng}; - - use super::*; - - #[test] - fn test_gen_contract_id() { - let mut rng = StdRng::seed_from_u64(42); - - let mut bytes = vec![0; 1000]; - rng.fill_bytes(&mut bytes); - - let nonce = rng.next_u64(); - - let mut owner = vec![0, 100]; - rng.fill_bytes(&mut owner); - - let contract_id = - gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); - - assert_eq!( - contract_id.as_bytes(), - [ - 45, 168, 182, 39, 119, 137, 168, 140, 114, 21, 120, 158, 34, - 126, 244, 221, 151, 72, 109, 178, 82, 229, 84, 128, 92, 123, - 135, 74, 23, 224, 119, 133 - ] - ); - } -} - #[cfg(feature = "abi")] pub(crate) mod host_queries { #[cfg(feature = "abi-debug")] From e8d22fc9b4150e3fe2202fa8520958140a0b5f7f Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 15:55:47 +0100 Subject: [PATCH 16/19] vm: Add `gen_contract_id` from `dusk-core` --- vm/src/execute.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++- vm/src/lib.rs | 11 +------- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/vm/src/execute.rs b/vm/src/execute.rs index 808ef4a75..c47f60cf6 100644 --- a/vm/src/execute.rs +++ b/vm/src/execute.rs @@ -4,7 +4,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_core::abi::{gen_contract_id, ContractError}; +use blake2b_simd::Params; +use dusk_core::abi::{ContractError, ContractId, CONTRACT_ID_BYTES}; use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; use piecrust::{CallReceipt, Error, Session}; @@ -165,3 +166,69 @@ fn contract_deploy( } } } + +/// Generate a [`ContractId`] address from: +/// - slice of bytes, +/// - nonce +/// - owner +/// +/// # Panics +/// Panics if [blake2b-hasher] doesn't produce a [`CONTRACT_ID_BYTES`] +/// bytes long hash. +/// +/// [blake2b-hasher]: [`blake2b_simd::Params.finalize`] +pub fn gen_contract_id( + bytes: impl AsRef<[u8]>, + nonce: u64, + owner: impl AsRef<[u8]>, +) -> ContractId { + let mut hasher = Params::new().hash_length(CONTRACT_ID_BYTES).to_state(); + hasher.update(bytes.as_ref()); + hasher.update(&nonce.to_le_bytes()[..]); + hasher.update(owner.as_ref()); + let hash_bytes: [u8; CONTRACT_ID_BYTES] = hasher + .finalize() + .as_bytes() + .try_into() + .expect("the hash result is exactly `CONTRACT_ID_BYTES` long"); + ContractId::from_bytes(hash_bytes) +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + // the `unused_crate_dependencies` lint complains for dev-dependencies that + // are only used in integration tests, so adding this work-around here + use ff as _; + use once_cell as _; + use rand::rngs::StdRng; + use rand::{RngCore, SeedableRng}; + + use super::*; + + #[test] + fn test_gen_contract_id() { + let mut rng = StdRng::seed_from_u64(42); + + let mut bytes = vec![0; 1000]; + rng.fill_bytes(&mut bytes); + + let nonce = rng.next_u64(); + + let mut owner = vec![0, 100]; + rng.fill_bytes(&mut owner); + + let contract_id = + gen_contract_id(bytes.as_slice(), nonce, owner.as_slice()); + + assert_eq!( + contract_id.as_bytes(), + [ + 45, 168, 182, 39, 119, 137, 168, 140, 114, 21, 120, 158, 34, + 126, 244, 221, 151, 72, 109, 178, 82, 229, 84, 128, 92, 123, + 135, 74, 23, 224, 119, 133 + ] + ); + } +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 1a5974c88..444c1fb2b 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -13,7 +13,7 @@ extern crate alloc; -pub use self::execute::execute; +pub use self::execute::{execute, gen_contract_id}; pub use piecrust::{ CallReceipt, CallTree, CallTreeElem, ContractData, Error, PageOpening, Session, @@ -170,12 +170,3 @@ impl VM { ); } } - -#[cfg(test)] -mod tests { - // the `unused_crate_dependencies` lint complains for dev-dependencies that - // are only used in integration tests, so adding this work-around here - use ff as _; - use once_cell as _; - use rand as _; -} From a7bea6dde17cbf313918a7952ecabc916bf1c030 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 15:58:32 +0100 Subject: [PATCH 17/19] rusk: Move `gen_contract_id` from `dusk-core` to `dusk-vm` --- rusk/tests/services/contract_deployment.rs | 4 ++-- rusk/tests/services/contract_stake.rs | 3 ++- rusk/tests/services/owner_calls.rs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 52ce440f6..66701e837 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -8,11 +8,11 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; -use dusk_core::abi::{gen_contract_id, ContractId}; +use dusk_core::abi::ContractId; use dusk_core::transfer::data::{ ContractBytecode, ContractDeploy, TransactionData, }; -use dusk_vm::{ContractData, Error as VMError, VM}; +use dusk_vm::{gen_contract_id, ContractData, Error as VMError, VM}; use rand::prelude::*; use rand::rngs::StdRng; use rusk::{Result, Rusk}; diff --git a/rusk/tests/services/contract_stake.rs b/rusk/tests/services/contract_stake.rs index e9b37fcdd..90bee3616 100644 --- a/rusk/tests/services/contract_stake.rs +++ b/rusk/tests/services/contract_stake.rs @@ -10,9 +10,10 @@ use std::sync::{Arc, RwLock}; use dusk_core::stake::{self, Stake, DEFAULT_MINIMUM_STAKE, EPOCH}; use dusk_bytes::Serializable; -use dusk_core::abi::{gen_contract_id, ContractId}; +use dusk_core::abi::ContractId; use dusk_core::transfer::data::ContractCall; use dusk_core::transfer::{self, Transaction}; +use dusk_vm::gen_contract_id; use node_data::ledger::SpentTransaction; use rand::prelude::*; use rand::rngs::StdRng; diff --git a/rusk/tests/services/owner_calls.rs b/rusk/tests/services/owner_calls.rs index c92b182ae..a4d450c39 100644 --- a/rusk/tests/services/owner_calls.rs +++ b/rusk/tests/services/owner_calls.rs @@ -14,12 +14,12 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; -use dusk_core::abi::{gen_contract_id, ContractId}; +use dusk_core::abi::ContractId; use dusk_core::signatures::bls::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature as BlsSignature, }; -use dusk_vm::{CallReceipt, ContractData, Session, VM}; +use dusk_vm::{gen_contract_id, CallReceipt, ContractData, Session, VM}; use rusk::{Error, Result, Rusk}; use rusk_recovery_tools::state; use tempfile::tempdir; From c38dcd5a462416ee1e84adc040e61d90c68c7eed Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 16:31:12 +0100 Subject: [PATCH 18/19] core: Move bytecode verification to `dusk-vm` This removes the `blake3` dep to `dusk-vm` as well, minimizing our contract sizes. --- core/Cargo.toml | 1 - core/src/transfer/data.rs | 8 -------- 2 files changed, 9 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 11a26536e..2c3174149 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -18,7 +18,6 @@ rkyv = { workspace = true, features = ["size_32"] } bytecheck = { workspace = true } rand = { workspace = true } ff = { workspace = true } -blake3 = { workspace = true } # plonk dependencies dusk-plonk = { workspace = true, features = ["rkyv-impl", "alloc"], optional = true } diff --git a/core/src/transfer/data.rs b/core/src/transfer/data.rs index b4ebbcaa2..a2685c100 100644 --- a/core/src/transfer/data.rs +++ b/core/src/transfer/data.rs @@ -213,14 +213,6 @@ impl ContractBytecode { self.hash.to_vec() } - /// Verifies that the stored bytes-hash is correct. - #[must_use] - pub fn verify_hash(&self) -> bool { - let computed: [u8; 32] = blake3::hash(self.bytes.as_slice()).into(); - - self.hash == computed - } - /// Serializes this object into a variable length buffer #[must_use] pub fn to_var_bytes(&self) -> Vec { From 27a067ea0f6e7402649ba584b2cdcdc469ba8928 Mon Sep 17 00:00:00 2001 From: moana Date: Sun, 22 Dec 2024 16:32:13 +0100 Subject: [PATCH 19/19] vm: Move contract bytecode hash verification from `dusk-core` --- vm/Cargo.toml | 1 + vm/src/execute.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index b5fb1ab8f..cab154ef5 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -14,6 +14,7 @@ dusk-bytes = { workspace = true } piecrust = { workspace = true } lru = { workspace = true } blake2b_simd = { workspace = true } +blake3 = { workspace = true } dusk-poseidon = { workspace = true } rkyv = { workspace = true, features = ["size_32"] } diff --git a/vm/src/execute.rs b/vm/src/execute.rs index c47f60cf6..6d6900e06 100644 --- a/vm/src/execute.rs +++ b/vm/src/execute.rs @@ -6,7 +6,9 @@ use blake2b_simd::Params; use dusk_core::abi::{ContractError, ContractId, CONTRACT_ID_BYTES}; -use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT}; +use dusk_core::transfer::{ + data::ContractBytecode, Transaction, TRANSFER_CONTRACT, +}; use piecrust::{CallReceipt, Error, Session}; /// Executes a transaction, returning the receipt of the call and the gas spent. @@ -138,7 +140,7 @@ fn contract_deploy( let min_gas_limit = receipt.gas_spent + deploy_charge; if gas_left < min_gas_limit { receipt.data = Err(ContractError::OutOfGas); - } else if !deploy.bytecode.verify_hash() { + } else if !verify_bytecode_hash(&deploy.bytecode) { receipt.data = Err(ContractError::Panic( "failed bytecode hash check".into(), )) @@ -167,6 +169,13 @@ fn contract_deploy( } } +// Verifies that the stored contract bytecode hash is correct. +fn verify_bytecode_hash(bytecode: &ContractBytecode) -> bool { + let computed: [u8; 32] = blake3::hash(bytecode.bytes.as_slice()).into(); + + bytecode.hash == computed +} + /// Generate a [`ContractId`] address from: /// - slice of bytes, /// - nonce