Skip to content

Commit

Permalink
rusk: Expose transaction execution function through dusk-vm
Browse files Browse the repository at this point in the history
  • Loading branch information
moCello committed Dec 21, 2024
1 parent e1a4413 commit f957ca8
Show file tree
Hide file tree
Showing 6 changed files with 8 additions and 228 deletions.
57 changes: 0 additions & 57 deletions rusk/src/lib/gen_id.rs

This file was deleted.

1 change: 0 additions & 1 deletion rusk/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

mod bloom;
mod error;
pub mod gen_id;
pub mod http;
#[cfg(feature = "chain")]
pub mod node;
Expand Down
169 changes: 5 additions & 164 deletions rusk/src/lib/node/rusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<Result<Vec<u8>, 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<CallReceipt<Result<Vec<u8>, 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<Vec<u8>, 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,
Expand Down
3 changes: 1 addition & 2 deletions rusk/tests/services/contract_deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions rusk/tests/services/contract_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions rusk/tests/services/owner_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit f957ca8

Please sign in to comment.