diff --git a/node/src/mempool.rs b/node/src/mempool.rs index 2ad43bdd54..9ff0e20415 100644 --- a/node/src/mempool.rs +++ b/node/src/mempool.rs @@ -35,6 +35,10 @@ pub enum TxAcceptanceError { SpendIdExistsInMempool, #[error("this transaction is invalid {0}")] VerificationFailed(String), + #[error("gas price lower than minimum {0}")] + GasPriceTooLow(u64), + #[error("gas limit lower than minimum {0}")] + GasLimitTooLow(u64), #[error("Maximum count of transactions exceeded {0}")] MaxTxnCountExceeded(usize), #[error("A generic error occurred {0}")] @@ -191,6 +195,27 @@ impl MempoolSrv { ) -> Result>, TxAcceptanceError> { let tx_id = tx.id(); + if tx.gas_price() < 1 { + return Err(TxAcceptanceError::GasPriceTooLow(1)); + } + + if let Some(deploy) = tx.inner.deploy() { + let vm = vm.read().await; + let min_deployment_gas_price = vm.min_deployment_gas_price(); + if tx.gas_price() < min_deployment_gas_price { + return Err(TxAcceptanceError::GasPriceTooLow( + min_deployment_gas_price, + )); + } + + let gas_per_deploy_byte = vm.gas_per_deploy_byte(); + let min_gas_limit = + vm::bytecode_charge(&deploy.bytecode, gas_per_deploy_byte); + if tx.inner.gas_limit() < min_gas_limit { + return Err(TxAcceptanceError::GasLimitTooLow(min_gas_limit)); + } + } + // Perform basic checks on the transaction db.read().await.view(|view| { let count = view.txs_count(); diff --git a/node/src/vm.rs b/node/src/vm.rs index 6407214e8f..c68ff9d573 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -10,6 +10,7 @@ use dusk_consensus::{ user::{provisioners::Provisioners, stake::Stake}, }; use execution_core::signatures::bls::PublicKey as BlsPublicKey; +use execution_core::transfer::data::ContractBytecode; use node_data::ledger::{Block, SpentTransaction, Transaction}; #[derive(Default)] @@ -73,4 +74,15 @@ pub trait VMExecution: Send + Sync + 'static { fn revert(&self, state_hash: [u8; 32]) -> anyhow::Result<[u8; 32]>; fn revert_to_finalized(&self) -> anyhow::Result<[u8; 32]>; + + fn gas_per_deploy_byte(&self) -> u64; + fn min_deployment_gas_price(&self) -> u64; +} + +// Returns gas charge for bytecode deployment. +pub fn bytecode_charge( + bytecode: &ContractBytecode, + gas_per_deploy_byte: u64, +) -> u64 { + bytecode.bytes.len() as u64 * gas_per_deploy_byte } diff --git a/rusk/src/lib/builder/node.rs b/rusk/src/lib/builder/node.rs index c2d1d23e8f..ba1ee5d2f0 100644 --- a/rusk/src/lib/builder/node.rs +++ b/rusk/src/lib/builder/node.rs @@ -52,6 +52,9 @@ pub struct RuskNodeBuilder { command_revert: bool, } +const DEFAULT_GAS_PER_DEPLOY_BYTE: u64 = 100; +const DEFAULT_MIN_DEPLOYMENT_GAS_PRICE: u64 = 2000; + impl RuskNodeBuilder { pub fn with_consensus_keys(mut self, consensus_keys_path: String) -> Self { self.consensus_keys_path = consensus_keys_path; @@ -167,12 +170,19 @@ impl RuskNodeBuilder { #[cfg(feature = "archive")] let (archive_sender, archive_receiver) = mpsc::channel(1000); + let gas_per_deploy_byte = self + .gas_per_deploy_byte + .unwrap_or(DEFAULT_GAS_PER_DEPLOY_BYTE); + let min_deployment_gas_price = self + .min_deployment_gas_price + .unwrap_or(DEFAULT_MIN_DEPLOYMENT_GAS_PRICE); + let rusk = Rusk::new( self.state_dir, self.kadcast.kadcast_id.unwrap_or_default(), self.generation_timeout, - self.gas_per_deploy_byte, - self.min_deployment_gas_price, + gas_per_deploy_byte, + min_deployment_gas_price, self.block_gas_limit, self.feeder_call_gas, rues_sender.clone(), diff --git a/rusk/src/lib/node.rs b/rusk/src/lib/node.rs index 9e224b40ee..ab8409ee83 100644 --- a/rusk/src/lib/node.rs +++ b/rusk/src/lib/node.rs @@ -41,8 +41,8 @@ pub struct Rusk { dir: PathBuf, pub(crate) chain_id: u8, pub(crate) generation_timeout: Option, - pub(crate) gas_per_deploy_byte: Option, - pub(crate) min_deployment_gas_price: Option, + pub(crate) gas_per_deploy_byte: u64, + pub(crate) min_deployment_gas_price: u64, pub(crate) feeder_gas_limit: u64, pub(crate) block_gas_limit: u64, pub(crate) event_sender: broadcast::Sender, diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 09cd5bc6ce..39492ed9cb 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -26,12 +26,12 @@ use execution_core::{ signatures::bls::PublicKey as BlsPublicKey, stake::{Reward, RewardReason, StakeData, STAKE_CONTRACT}, transfer::{ - data::{ContractBytecode, ContractDeploy}, - moonlight::AccountData, + data::ContractDeploy, moonlight::AccountData, Transaction as ProtocolTransaction, TRANSFER_CONTRACT, }, BlsScalar, ContractError, Dusk, Event, }; +use node::vm::bytecode_charge; use node_data::events::contract::ContractTxEvent; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; use rusk_abi::{CallReceipt, PiecrustError, Session, VM}; @@ -53,17 +53,14 @@ pub static DUSK_KEY: LazyLock = LazyLock::new(|| { .expect("Dusk consensus public key to be valid") }); -const DEFAULT_GAS_PER_DEPLOY_BYTE: u64 = 100; -const DEFAULT_MIN_DEPLOYMENT_GAS_PRICE: u64 = 2000; - impl Rusk { #[allow(clippy::too_many_arguments)] pub fn new>( dir: P, chain_id: u8, generation_timeout: Option, - gas_per_deploy_byte: Option, - min_deployment_gas_price: Option, + gas_per_deploy_byte: u64, + min_deployment_gas_price: u64, block_gas_limit: u64, feeder_gas_limit: u64, event_sender: broadcast::Sender, @@ -519,8 +516,8 @@ fn accept( txs: &[Transaction], slashing: Vec, voters: &[Voter], - gas_per_deploy_byte: Option, - min_deployment_gas_price: Option, + gas_per_deploy_byte: u64, + min_deployment_gas_price: u64, ) -> Result<( Vec, VerificationOutput, @@ -609,15 +606,6 @@ fn accept( )) } -// Returns gas charge for bytecode deployment. -fn bytecode_charge( - bytecode: &ContractBytecode, - gas_per_deploy_byte: &Option, -) -> u64 { - bytecode.bytes.len() as u64 - * gas_per_deploy_byte.unwrap_or(DEFAULT_GAS_PER_DEPLOY_BYTE) -} - // 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 @@ -631,10 +619,10 @@ fn contract_deploy( session: &mut Session, deploy: &ContractDeploy, gas_limit: u64, - gas_per_deploy_byte: Option, + gas_per_deploy_byte: u64, receipt: &mut CallReceipt, ContractError>>, ) { - let deploy_charge = bytecode_charge(&deploy.bytecode, &gas_per_deploy_byte); + 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 { @@ -702,18 +690,15 @@ fn contract_deploy( fn execute( session: &mut Session, tx: &ProtocolTransaction, - gas_per_deploy_byte: Option, - min_deployment_gas_price: Option, + gas_per_deploy_byte: u64, + min_deployment_gas_price: u64, ) -> Result, 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 - .unwrap_or(DEFAULT_MIN_DEPLOYMENT_GAS_PRICE) - { + 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(), )); diff --git a/rusk/src/lib/node/vm.rs b/rusk/src/lib/node/vm.rs index af782125ea..9d64509fd6 100644 --- a/rusk/src/lib/node/vm.rs +++ b/rusk/src/lib/node/vm.rs @@ -231,6 +231,14 @@ impl VMExecution for Rusk { fn get_block_gas_limit(&self) -> u64 { self.block_gas_limit() } + + fn gas_per_deploy_byte(&self) -> u64 { + self.gas_per_deploy_byte + } + + fn min_deployment_gas_price(&self) -> u64 { + self.min_deployment_gas_price + } } fn has_unique_elements(iter: T) -> bool diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index fb5c046430..9c122a1b46 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -30,6 +30,8 @@ use tokio::sync::broadcast; use tracing::info; const CHAIN_ID: u8 = 0xFA; +pub const DEFAULT_GAS_PER_DEPLOY_BYTE: u64 = 100; +pub const DEFAULT_MIN_DEPLOYMENT_GAS_PRICE: u64 = 2000; // Creates a Rusk initial state in the given directory pub fn new_state>( @@ -48,8 +50,8 @@ pub fn new_state>( dir, CHAIN_ID, None, - None, - None, + DEFAULT_GAS_PER_DEPLOY_BYTE, + DEFAULT_MIN_DEPLOYMENT_GAS_PRICE, block_gas_limit, u64::MAX, sender, diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index b9731ed10a..2b429b065f 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -24,6 +24,8 @@ use tokio::sync::broadcast; use tracing::info; use crate::common::logger; +use crate::common::state::DEFAULT_GAS_PER_DEPLOY_BYTE; +use crate::common::state::DEFAULT_MIN_DEPLOYMENT_GAS_PRICE; use crate::common::state::{generator_procedure, ExecuteResult}; use crate::common::wallet::{TestStateClient, TestStore}; @@ -99,8 +101,8 @@ fn initial_state>(dir: P, deploy_bob: bool) -> Result { dir, CHAIN_ID, None, - None, - None, + DEFAULT_GAS_PER_DEPLOY_BYTE, + DEFAULT_MIN_DEPLOYMENT_GAS_PRICE, BLOCK_GAS_LIMIT, u64::MAX, sender, diff --git a/rusk/tests/services/owner_calls.rs b/rusk/tests/services/owner_calls.rs index f784185299..a80e8c7f32 100644 --- a/rusk/tests/services/owner_calls.rs +++ b/rusk/tests/services/owner_calls.rs @@ -31,6 +31,8 @@ use tokio::sync::broadcast; use tracing::info; use crate::common::logger; +use crate::common::state::DEFAULT_GAS_PER_DEPLOY_BYTE; +use crate::common::state::DEFAULT_MIN_DEPLOYMENT_GAS_PRICE; use crate::common::wallet::{TestStateClient, TestStore}; const BLOCK_GAS_LIMIT: u64 = 1_000_000_000_000; @@ -78,8 +80,8 @@ fn initial_state>( dir, CHAIN_ID, None, - None, - None, + DEFAULT_GAS_PER_DEPLOY_BYTE, + DEFAULT_MIN_DEPLOYMENT_GAS_PRICE, BLOCK_GAS_LIMIT, u64::MAX, sender,