From 8ebe6edc4c98a3cf654cba457034a02597e8cb19 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 21 Dec 2024 17:07:45 +0100 Subject: [PATCH] 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 f93a0963cd..0000000000 --- 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 541c42bc2b..603cef789f 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 6f521eb4d8..2c3499e316 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_data::events::contract::{ContractEvent, ContractTxEvent}; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; use parking_lot::RwLock; @@ -39,7 +38,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; @@ -670,163 +668,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 83c0fdd09d..4225ed43f6 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 83a853f4b8..e9b37fcdd7 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 4516141501..8151c590c5 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;