Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Enforce `min_tx_gas` in mempool preverify, reject TXs where `gas_limit < max(min_gas_limit, min_tx_gas)` [#3940]

## [1.4.1] - 2025-12-04

### Added
Expand Down Expand Up @@ -76,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- First `dusk-node` release

<!-- Issues -->
[#3940]: https://github.com/dusk-network/rusk/issues/3940
[#3917]: https://github.com/dusk-network/rusk/issues/3917
[#3874]: https://github.com/dusk-network/rusk/issues/3874
[#3871]: https://github.com/dusk-network/rusk/issues/3871
Expand Down
16 changes: 11 additions & 5 deletions node/src/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub enum TxAcceptanceError {
VerificationFailed(String),
#[error("gas price lower than minimum {0}")]
GasPriceTooLow(u64),
#[error("gas limit lower than minimum {0}")]
#[error("gas limit lower than minimum {0} LUX")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gas limit is not expressed in LUX

GasLimitTooLow(u64),
#[error("Maximum count of transactions exceeded {0}")]
MaxTxnCountExceeded(usize),
Expand Down Expand Up @@ -349,10 +349,16 @@ impl MempoolSrv {
dusk_consensus::validate_blob_sidecars(tx)?;
}

// Check global minimum gas limit
let min_gas_limit = vm.min_gas_limit();
if tx.inner.gas_limit() < min_gas_limit {
return Err(TxAcceptanceError::GasLimitTooLow(min_gas_limit));
// Check global minimum gas limit and per-tx fee floor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gas limit is not fee

let chain_min_gas_limit = vm.min_gas_limit();
let min_tx_gas = vm.min_tx_gas(tip_height);
let required_gas_limit =
core::cmp::max(chain_min_gas_limit, min_tx_gas);

if tx.inner.gas_limit() < required_gas_limit {
return Err(TxAcceptanceError::GasLimitTooLow(
required_gas_limit,
));
}
}

Expand Down
1 change: 1 addition & 0 deletions node/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub trait VMExecution: Send + Sync + 'static {
fn wasm64_disabled(&self, block_height: u64) -> bool;
fn wasm32_disabled(&self, block_height: u64) -> bool;
fn third_party_disabled(&self, block_height: u64) -> bool;
fn min_tx_gas(&self, height: u64) -> u64;
}

#[allow(clippy::large_enum_variant)]
Expand Down
5 changes: 5 additions & 0 deletions rusk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add TX floor price feature, including for well-known chain ids [#3940]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a price


## [1.4.1] - 2025-12-04

### Added
Expand Down Expand Up @@ -406,6 +410,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add build system that generates keys for circuits and caches them.

<!-- Issues -->
[#3940]: https://github.com/dusk-network/rusk/issues/3940
[#3917]: https://github.com/dusk-network/rusk/issues/3917
[#3897]: https://github.com/dusk-network/rusk/issues/3897
[#3894]: https://github.com/dusk-network/rusk/issues/3894
Expand Down
13 changes: 13 additions & 0 deletions rusk/src/lib/node/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,19 @@ impl VMExecution for Rusk {
.map(|activation| activation.is_active_at(block_height))
.unwrap_or(false)
}

fn min_tx_gas(&self, height: u64) -> u64 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would return this as Option to be aligned with its meaning

self.vm_config
.feature(FEATURE_MIN_TX_GAS)
.map(|activation| {
if activation.is_active_at(height) {
self.vm_config.min_tx_gas
} else {
0
}
})
.unwrap_or(0)
}
}

fn has_unique_elements<T>(iter: T) -> bool
Expand Down
25 changes: 25 additions & 0 deletions rusk/src/lib/node/vm/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const DEFAULT_GAS_PER_BLOB: u64 = 1_000_000;
const DEFAULT_MIN_DEPLOY_POINTS: u64 = 5_000_000;
const DEFAULT_MIN_DEPLOYMENT_GAS_PRICE: u64 = 2_000;
const DEFAULT_BLOCK_GAS_LIMIT: u64 = 3 * 1_000_000_000;
/// Default per‑tx minimum gas floor. 0 disables it.
const DEFAULT_MIN_TX_GAS: u64 = 5_000_000;

/// Configuration for the execution of a transaction.
#[derive(Debug, Clone, serde::Serialize)]
Expand All @@ -45,6 +47,9 @@ pub struct Config {
#[serde(with = "humantime_serde")]
pub generation_timeout: Option<Duration>,

/// Minimum gas charged for any transaction.
pub min_tx_gas: u64,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an option


/// Set of features to activate
features: HashMap<String, FeatureActivation>,
}
Expand All @@ -61,6 +66,7 @@ pub(crate) mod feature {
pub const FEATURE_DISABLE_WASM64: &str = "DISABLE_WASM64";
pub const FEATURE_DISABLE_WASM32: &str = "DISABLE_WASM32";
pub const FEATURE_DISABLE_3RD_PARTY: &str = "DISABLE_3RD_PARTY";
pub const FEATURE_MIN_TX_GAS: &str = "MIN_TX_GAS";

pub const HQ_KECCAK256: &str = "HQ_KECCAK256";
}
Expand All @@ -73,6 +79,7 @@ impl Config {
min_deployment_gas_price: DEFAULT_MIN_DEPLOYMENT_GAS_PRICE,
min_deploy_points: DEFAULT_MIN_DEPLOY_POINTS,
block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
min_tx_gas: DEFAULT_MIN_TX_GAS,
generation_timeout: None,
features: HashMap::new(),
}
Expand Down Expand Up @@ -121,6 +128,12 @@ impl Config {
self
}

/// Set the minimum gas charged for any transaction.
pub const fn with_min_tx_gas(mut self, min_tx_gas: u64) -> Self {
self.min_tx_gas = min_tx_gas;
self
}

/// Create a new `Config` with the given parameters.
pub fn to_execution_config(&self, block_height: u64) -> ExecutionConfig {
let with_public_sender: bool = self
Expand All @@ -143,11 +156,23 @@ impl Config {
.feature(feature::FEATURE_DISABLE_3RD_PARTY)
.map(|activation| activation.is_active_at(block_height))
.unwrap_or_default();
let min_tx_gas = self
.feature(feature::FEATURE_MIN_TX_GAS)
.map(|activation| {
if activation.is_active_at(block_height) {
self.min_tx_gas
} else {
0
}
})
.unwrap_or(0);

ExecutionConfig {
gas_per_blob: self.gas_per_blob,
gas_per_deploy_byte: self.gas_per_deploy_byte,
min_deploy_points: self.min_deploy_points,
min_deploy_gas_price: self.min_deployment_gas_price,
min_tx_gas,
with_public_sender,
with_blob,
disable_wasm64,
Expand Down
22 changes: 20 additions & 2 deletions rusk/src/lib/node/vm/config/known.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use std::sync::LazyLock;

use dusk_vm::FeatureActivation;

use crate::node::{FEATURE_DISABLE_3RD_PARTY, FEATURE_DISABLE_WASM32};
use crate::node::{
FEATURE_DISABLE_3RD_PARTY, FEATURE_DISABLE_WASM32, FEATURE_MIN_TX_GAS,
};

use super::feature::{
FEATURE_ABI_PUBLIC_SENDER, FEATURE_BLOB, FEATURE_DISABLE_WASM64,
Expand All @@ -19,6 +21,7 @@ use super::feature::{
use super::{
DEFAULT_BLOCK_GAS_LIMIT, DEFAULT_GAS_PER_BLOB, DEFAULT_GAS_PER_DEPLOY_BYTE,
DEFAULT_MIN_DEPLOYMENT_GAS_PRICE, DEFAULT_MIN_DEPLOY_POINTS,
DEFAULT_MIN_TX_GAS,
};

pub const MAINNET_ID: u8 = 1;
Expand All @@ -36,7 +39,8 @@ pub struct WellKnownConfig {
pub min_deploy_points: u64,
pub min_deployment_gas_price: u64,
pub block_gas_limit: u64,
pub features: [(&'static str, FeatureActivation); 6],
pub min_tx_gas: u64,
pub features: [(&'static str, FeatureActivation); 7],
}

impl WellKnownConfig {
Expand Down Expand Up @@ -79,6 +83,9 @@ static MAINNET_DISABLE_WASM_64: LazyLock<FeatureActivation> =
const MAINNET_BLOB_ACTIVATION: FeatureActivation =
FeatureActivation::Height(MAINNET_AT_10_12_2025_AT_09_00_UTC);

const MAINNET_MIN_TX_GAS_ACTIVATION: FeatureActivation =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should mention a timestamp also

FeatureActivation::Height(3_060_210);

/// Mainnet VM configuration.
static MAINNET_CONFIG: LazyLock<WellKnownConfig> =
LazyLock::new(|| WellKnownConfig {
Expand All @@ -87,34 +94,41 @@ static MAINNET_CONFIG: LazyLock<WellKnownConfig> =
min_deploy_points: DEFAULT_MIN_DEPLOY_POINTS,
min_deployment_gas_price: DEFAULT_MIN_DEPLOYMENT_GAS_PRICE,
block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
min_tx_gas: DEFAULT_MIN_TX_GAS,
features: [
(FEATURE_ABI_PUBLIC_SENDER, MAINNET_SENDER_ACTIVATION_HEIGHT),
(HQ_KECCAK256, NEVER),
(FEATURE_BLOB, MAINNET_BLOB_ACTIVATION),
(FEATURE_DISABLE_WASM64, MAINNET_DISABLE_WASM_64.clone()),
(FEATURE_DISABLE_WASM32, MAINNET_3RD_PARTY_OFF.clone()),
(FEATURE_DISABLE_3RD_PARTY, MAINNET_3RD_PARTY_OFF.clone()),
(FEATURE_MIN_TX_GAS, MAINNET_MIN_TX_GAS_ACTIVATION),
],
});

/// Estimated testnet block height for 12th November 2025, 09:00 UTC.
const TESTNET_AT_12_11_2025_AT_09_00_UTC: FeatureActivation =
FeatureActivation::Height(1_814_090);

const TESTNET_MIN_TX_GAS_ACTIVATION: FeatureActivation =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timestamp

FeatureActivation::Height(2_242_800);

/// Testnet VM configuration.
const TESTNET_CONFIG: WellKnownConfig = WellKnownConfig {
gas_per_blob: DEFAULT_GAS_PER_BLOB,
gas_per_deploy_byte: DEFAULT_GAS_PER_DEPLOY_BYTE,
min_deploy_points: DEFAULT_MIN_DEPLOY_POINTS,
min_deployment_gas_price: DEFAULT_MIN_DEPLOYMENT_GAS_PRICE,
block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
min_tx_gas: DEFAULT_MIN_TX_GAS,
features: [
(FEATURE_ABI_PUBLIC_SENDER, GENESIS),
(HQ_KECCAK256, NEVER),
(FEATURE_BLOB, TESTNET_AT_12_11_2025_AT_09_00_UTC),
(FEATURE_DISABLE_WASM64, TESTNET_AT_12_11_2025_AT_09_00_UTC),
(FEATURE_DISABLE_WASM32, NEVER),
(FEATURE_DISABLE_3RD_PARTY, NEVER),
(FEATURE_MIN_TX_GAS, TESTNET_MIN_TX_GAS_ACTIVATION),
],
};

Expand All @@ -125,13 +139,15 @@ const DEVNET_CONFIG: WellKnownConfig = WellKnownConfig {
min_deploy_points: DEFAULT_MIN_DEPLOY_POINTS,
min_deployment_gas_price: DEFAULT_MIN_DEPLOYMENT_GAS_PRICE,
block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
min_tx_gas: DEFAULT_MIN_TX_GAS,
features: [
(FEATURE_ABI_PUBLIC_SENDER, GENESIS),
(HQ_KECCAK256, GENESIS),
(FEATURE_BLOB, GENESIS),
(FEATURE_DISABLE_WASM64, GENESIS),
(FEATURE_DISABLE_WASM32, NEVER),
(FEATURE_DISABLE_3RD_PARTY, NEVER),
(FEATURE_MIN_TX_GAS, GENESIS),
],
};

Expand All @@ -142,12 +158,14 @@ const LOCALNET_CONFIG: WellKnownConfig = WellKnownConfig {
min_deploy_points: DEFAULT_MIN_DEPLOY_POINTS,
min_deployment_gas_price: DEFAULT_MIN_DEPLOYMENT_GAS_PRICE,
block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
min_tx_gas: DEFAULT_MIN_TX_GAS,
features: [
(FEATURE_ABI_PUBLIC_SENDER, GENESIS),
(HQ_KECCAK256, GENESIS),
(FEATURE_BLOB, GENESIS),
(FEATURE_DISABLE_WASM64, GENESIS),
(FEATURE_DISABLE_WASM32, NEVER),
(FEATURE_DISABLE_3RD_PARTY, NEVER),
(FEATURE_MIN_TX_GAS, GENESIS),
],
};
12 changes: 12 additions & 0 deletions rusk/src/lib/node/vm/config/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub struct OptionalConfig {
#[serde(default, with = "humantime_serde")]
pub generation_timeout: Option<Duration>,

/// Minimum gas charged for any transaction.
pub min_tx_gas: Option<u64>,

/// Set of features to activate
#[serde(default)]
features: HashMap<String, FeatureActivation>,
Expand Down Expand Up @@ -111,6 +114,12 @@ impl OptionalConfig {
config.block_gas_limit,
);

Self::set_or_warn(
"min_tx_gas",
&mut self.min_tx_gas,
config.min_tx_gas,
);

for (feature, activation) in &config.features {
if let Some(v) = self.feature(feature) {
if v != activation {
Expand Down Expand Up @@ -192,6 +201,9 @@ impl TryFrom<OptionalConfig> for Config {
block_gas_limit: value
.block_gas_limit
.ok_or(anyhow!("Missing block_gas_limit"))?,
min_tx_gas: value
.min_tx_gas
.ok_or(anyhow!("Missing min_tx_gas"))?,
generation_timeout: value.generation_timeout,
features: value.features,
})
Expand Down
Loading
Loading