Skip to content

Commit

Permalink
rusk: Move transaction execution function to rusk-abi
Browse files Browse the repository at this point in the history
  • Loading branch information
moCello committed Dec 16, 2024
1 parent 0152616 commit 9e2e0ec
Show file tree
Hide file tree
Showing 6 changed files with 6 additions and 222 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
162 changes: 3 additions & 159 deletions rusk/src/lib/node/rusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,18 @@ use dusk_consensus::operations::{CallParams, VerificationOutput, Voter};
use execution_core::{
signatures::bls::PublicKey as BlsPublicKey,
stake::{Reward, RewardReason, StakeData, STAKE_CONTRACT},
transfer::{
data::ContractDeploy, moonlight::AccountData,
Transaction as ProtocolTransaction, TRANSFER_CONTRACT,
},
BlsScalar, ContractError, Dusk, Event,
transfer::{moonlight::AccountData, TRANSFER_CONTRACT},
BlsScalar, Dusk, Event,
};
use node::vm::bytecode_charge;
use node_data::events::contract::{ContractEvent, ContractTxEvent};
use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction};
use rusk_abi::{CallReceipt, PiecrustError, Session};
use rusk_abi::{execute, CallReceipt, PiecrustError, Session};
use rusk_profile::to_rusk_state_id_path;
use tokio::sync::broadcast;
#[cfg(feature = "archive")]
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 @@ -661,157 +656,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,
receipt: &mut CallReceipt<Result<Vec<u8>, ContractError>>,
) {
let deploy_charge = bytecode_charge(&deploy.bytecode, gas_per_deploy_byte);
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_deployment_gas_price: u64,
) -> Result<CallReceipt<Result<Vec<u8>, ContractError>>, PiecrustError> {
// 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);
if tx.gas_price() < min_deployment_gas_price {
return Err(PiecrustError::Panic(
"gas price too low to deploy".into(),
));
}
if tx.gas_limit() < deploy_charge {
return Err(PiecrustError::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,
&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 @@ -14,9 +14,8 @@ use execution_core::{
};
use rand::prelude::*;
use rand::rngs::StdRng;
use rusk::gen_id::gen_contract_id;
use rusk::{Result, Rusk};
use rusk_abi::{ContractData, PiecrustError};
use rusk_abi::{gen_contract_id, ContractData, PiecrustError};
use rusk_recovery_tools::state;
use tempfile::tempdir;
use test_wallet::{self as wallet, Wallet};
Expand Down
2 changes: 1 addition & 1 deletion rusk/tests/services/contract_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use execution_core::ContractId;
use node_data::ledger::SpentTransaction;
use rand::prelude::*;
use rand::rngs::StdRng;
use rusk::gen_id::gen_contract_id;
use rusk::{Result, Rusk};
use rusk_abi::gen_contract_id;
use std::collections::HashMap;
use tempfile::tempdir;
use test_wallet::{self as wallet};
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 @@ -21,9 +21,8 @@ use execution_core::{
},
ContractId,
};
use rusk::gen_id::gen_contract_id;
use rusk::{Error, Result, Rusk};
use rusk_abi::{CallReceipt, ContractData, Session};
use rusk_abi::{gen_contract_id, CallReceipt, ContractData, Session};
use rusk_recovery_tools::state;
use tempfile::tempdir;
use test_wallet::{self as wallet, Wallet};
Expand Down

0 comments on commit 9e2e0ec

Please sign in to comment.