Repository: dusk-network/rusk (language: rust)

Issue:
rusk: add Moonlight TX sender metadata as host function
#### Summary
Right now, when a contract receive funds (using transfer::deposit function) it's not possible to know where the funds came from.
This lead to a smartcontract to not be able to reject tx based on transaction models

#### Possible solution design or implementation
1. Add host function to query the ongoing transaction model
2. Add host funciton to query the moonlight sender

Second option would help other scenario, since it can be used to do additional assumptions that right now require dedicated signatures

#### Additional context
This requires a change in piecrust to allow setting metadata for an existing session


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index bee3effc7f..a4fa0cc02d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3693,9 +3693,9 @@ dependencies = [
 
 [[package]]
 name = "piecrust"
-version = "0.27.0"
+version = "0.27.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7615b52ada11b2f89751c19fa55ddbfa84c625bd8103528179be514874c32d3b"
+checksum = "10c5b83e833e13d1be1a5bfb7183a3edd4dbd786ec3524918c78bdc982a32c7e"
 dependencies = [
  "blake3",
  "bytecheck",
diff --git a/Cargo.toml b/Cargo.toml
index 686a592c58..3404fff1a5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,7 +61,7 @@ jubjub-schnorr = { version = "0.5", default-features = false }
 kadcast = "0.7"
 phoenix-circuits = { version = "0.5", default-features = false }
 phoenix-core = { version = "0.33.1", default-features = false }
-piecrust = "0.27"
+piecrust = "0.27.1"
 piecrust-uplink = "0.17.3"
 poseidon-merkle = "0.7"
 
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index c35e0f3b3d..3b11f8d94f 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -7,7 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
-[1.0.0] - 2025-01-23
+### Added
+
+- Add `METADATA::PUBLIC_SENDER` [#3341]
+- Add `abi::public_sender` host fn [#3341]
+
+## [1.0.0] - 2025-01-23
 
 ### Changed
 
@@ -22,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <!-- Issues -->
 [#3405]: https://github.com/dusk-network/rusk/issues/3405
+[#3341]: https://github.com/dusk-network/rusk/issues/3341
 
 [Unreleased]: https://github.com/dusk-network/rusk/compare/dusk-core-1.0.0...HEAD
 [1.0.0]: https://github.com/dusk-network/rusk/compare/dusk-core-0.1.0...dusk-core-1.0.0
diff --git a/core/src/abi.rs b/core/src/abi.rs
index 94a5232d4c..39aece8dbb 100644
--- a/core/src/abi.rs
+++ b/core/src/abi.rs
@@ -22,6 +22,8 @@ impl Metadata {
     pub const CHAIN_ID: &'static str = "chain_id";
     /// The current block-height.
     pub const BLOCK_HEIGHT: &'static str = "block_height";
+    /// The sender of the transaction, if the transaction is public.
+    pub const PUBLIC_SENDER: &'static str = "public_sender";
 }
 
 /// Enum storing the available host-queries.
@@ -156,6 +158,18 @@ pub(crate) mod host_queries {
         meta_data(Metadata::BLOCK_HEIGHT).unwrap()
     }
 
+    /// Get the public sender of the ongoing tx. Returns `None` if the
+    /// transaction is shielded.
+    ///
+    /// # Panics
+    /// Panics if the chain doesn't store a `Option<BlsPublicKey>`
+    /// `PUBLIC_SENDER` in the metadata.
+    #[must_use]
+    pub fn public_sender() -> Option<BlsPublicKey> {
+        meta_data(Metadata::PUBLIC_SENDER)
+            .expect("moonlight sender metadata to be set")
+    }
+
     /// Query owner of a given contract.
     /// Returns none if contract is not found.
     ///
diff --git a/rusk/CHANGELOG.md b/rusk/CHANGELOG.md
index 2eb213fe6e..50db6f6b7e 100644
--- a/rusk/CHANGELOG.md
+++ b/rusk/CHANGELOG.md
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Add `abi::public_sender` [#3341]
+- Add `[vm]` config section [#3341]
+
+### Changed
+
+- Deprecate `[chain].gas_per_deploy_byte` config [#3341]
+- Deprecate `[chain].min_deployment_gas_price` config [#3341]
+- Deprecate `[chain].generation_timeout` config [#3341]
+- Deprecate `[chain].min_deploy_points` config [#3341]
+- Deprecate `[chain].block_gas_limit` config [#3341]
+
 ### Removed
 
 - Remove legacy event system 
@@ -308,6 +321,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 <!-- Issues -->
 [#3422]: https://github.com/dusk-network/rusk/issues/3422
 [#3405]: https://github.com/dusk-network/rusk/issues/3405
+[#3341]: https://github.com/dusk-network/rusk/issues/3341
 [#3359]: https://github.com/dusk-network/rusk/issues/3359
 [#3206]: https://github.com/dusk-network/rusk/issues/3206
 [#2597]: https://github.com/dusk-network/rusk/issues/2597
diff --git a/rusk/benches/block_ingestion.rs b/rusk/benches/block_ingestion.rs
index 683a10cf7c..21c85deb6d 100644
--- a/rusk/benches/block_ingestion.rs
+++ b/rusk/benches/block_ingestion.rs
@@ -30,8 +30,6 @@ use tempfile::tempdir;
 use common::state::new_state;
 
 const BLOCK_GAS_LIMIT: u64 = 1_000_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
 fn load_phoenix_txs() -> Vec<Transaction> {
     // The file "phoenix-txs" can be generated using
@@ -160,7 +158,8 @@ pub fn accept_benchmark(c: &mut Criterion) {
     let snapshot = toml::from_str(include_str!("../tests/config/bench.toml"))
         .expect("Cannot deserialize config");
 
-    let rusk = new_state(&tmp, &snapshot, VM_CONFIG)
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
+    let rusk = new_state(&tmp, &snapshot, vm_config)
         .expect("Creating state should work");
 
     let phoenix_txs = load_phoenix_txs();
diff --git a/rusk/default.config.toml b/rusk/default.config.toml
index 830c4d38b3..d9e316ea48 100644
--- a/rusk/default.config.toml
+++ b/rusk/default.config.toml
@@ -21,12 +21,21 @@
 [chain]
 #db_path = '/home/user/.dusk/rusk'
 #consensus_keys_path = '/home/user/.dusk/rusk/consensus.keys'
-#generation_timeout = '3s'
-# Note: changing the gas per deploy byte parameter is equivalent to forking the chain.
-#gas_per_deploy_byte = 100
-#min_deployment_gas_price = 2000
-#min_gas_limit = 75000
-#min_deploy_points = 5000000
+min_gas_limit = 150000
+
+# Note: changing the vm settings is equivalent to forking the chain.
+[vm]
+generation_timeout = '3s'
+gas_per_deploy_byte = 100
+min_deployment_gas_price = 2000
+min_deploy_points = 5000000
+block_gas_limit = 3000000000
+
+[vm.features]
+# ABI_PUBLIC_SENDER = <TBD>
+# key = activation_height
+# key = activation_height
+# key = activation_height
 
 [databroker]
 max_inv_entries = 100
diff --git a/rusk/src/bin/config.rs b/rusk/src/bin/config.rs
index a0286a23b3..5b74910149 100644
--- a/rusk/src/bin/config.rs
+++ b/rusk/src/bin/config.rs
@@ -26,6 +26,9 @@ use self::{
     mempool::MempoolConfig, telemetry::TelemetryConfig,
 };
 
+#[cfg(feature = "chain")]
+use rusk::node::RuskVmConfig;
+
 use serde::{Deserialize, Serialize};
 
 use crate::args::Args;
@@ -50,6 +53,10 @@ pub(crate) struct Config {
     #[serde(default = "ChainConfig::default")]
     pub(crate) chain: ChainConfig,
 
+    #[cfg(feature = "chain")]
+    #[serde(default = "RuskVmConfig::default")]
+    pub(crate) vm: RuskVmConfig,
+
     #[serde(default = "HttpConfig::default")]
     pub(crate) http: HttpConfig,
 
diff --git a/rusk/src/bin/config/chain.rs b/rusk/src/bin/config/chain.rs
index 4195793451..34f9db7d4d 100644
--- a/rusk/src/bin/config/chain.rs
+++ b/rusk/src/bin/config/chain.rs
@@ -22,16 +22,21 @@ pub(crate) struct ChainConfig {
     consensus_keys_path: Option<PathBuf>,
     #[serde(with = "humantime_serde")]
     #[serde(default)]
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     generation_timeout: Option<Duration>,
 
     max_queue_size: Option<usize>,
 
     // NB: changing the gas_per_deploy_byte/block_gas_limit is equivalent to
     // forking the chain.
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     gas_per_deploy_byte: Option<u64>,
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     min_deployment_gas_price: Option<u64>,
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     min_deploy_points: Option<u64>,
     min_gas_limit: Option<u64>,
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     block_gas_limit: Option<u64>,
 
     #[serde(with = "humantime_serde")]
@@ -80,19 +85,27 @@ impl ChainConfig {
         self.db_options.clone().unwrap_or_default()
     }
 
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     pub(crate) fn generation_timeout(&self) -> Option<Duration> {
+        #[allow(deprecated)]
         self.generation_timeout
     }
 
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     pub(crate) fn gas_per_deploy_byte(&self) -> Option<u64> {
+        #[allow(deprecated)]
         self.gas_per_deploy_byte
     }
 
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     pub(crate) fn min_deployment_gas_price(&self) -> Option<u64> {
+        #[allow(deprecated)]
         self.min_deployment_gas_price
     }
 
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     pub(crate) fn min_deploy_points(&self) -> Option<u64> {
+        #[allow(deprecated)]
         self.min_deploy_points
     }
 
@@ -104,7 +117,9 @@ impl ChainConfig {
         self.max_queue_size.unwrap_or(10_000)
     }
 
+    #[deprecated(since = "1.0.3", note = "please use `RuskVmConfig` instead")]
     pub(crate) fn block_gas_limit(&self) -> Option<u64> {
+        #[allow(deprecated)]
         self.block_gas_limit
     }
 
diff --git a/rusk/src/bin/main.rs b/rusk/src/bin/main.rs
index bc4780ae0e..1b3a26da7c 100644
--- a/rusk/src/bin/main.rs
+++ b/rusk/src/bin/main.rs
@@ -11,7 +11,7 @@ mod ephemeral;
 mod log;
 
 #[cfg(feature = "chain")]
-use tracing::info;
+use tracing::{info, warn};
 
 use clap::Parser;
 
@@ -69,6 +69,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
         let db_path = config.chain.db_path();
 
         node_builder = node_builder
+            .with_vm_config(config.vm)
             .with_feeder_call_gas(config.http.feeder_call_gas)
             .with_db_path(db_path)
             .with_db_options(config.chain.db_options())
@@ -80,14 +81,32 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
             .with_genesis_timestamp(config.chain.genesis_timestamp())
             .with_mempool(config.mempool.into())
             .with_state_dir(state_dir)
-            .with_generation_timeout(config.chain.generation_timeout())
-            .with_gas_per_deploy_byte(config.chain.gas_per_deploy_byte())
-            .with_min_deployment_gas_price(
-                config.chain.min_deployment_gas_price(),
-            )
-            .with_min_deploy_points(config.chain.min_deploy_points())
-            .with_min_gas_limit(config.chain.min_gas_limit())
-            .with_block_gas_limit(config.chain.block_gas_limit());
+            .with_min_gas_limit(config.chain.min_gas_limit());
+
+        #[allow(deprecated)]
+        {
+            if let Some(gas_byte) = config.chain.gas_per_deploy_byte() {
+                warn!("[chain].gas_per_deploy_byte is deprecated, use [vm].gas_per_deploy_byte");
+                node_builder = node_builder.with_gas_per_deploy_byte(gas_byte);
+            }
+            if let Some(price) = config.chain.min_deployment_gas_price() {
+                warn!("[chain].min_deployment_gas_price is deprecated, use [vm].min_deployment_gas_price");
+                node_builder =
+                    node_builder.with_min_deployment_gas_price(price);
+            }
+            if let Some(timeout) = config.chain.generation_timeout() {
+                warn!("[chain].generation_timeout is deprecated, use [vm].generation_timeout");
+                node_builder = node_builder.with_generation_timeout(timeout);
+            }
+            if let Some(min) = config.chain.min_deploy_points() {
+                warn!("[chain].min_deploy_points is deprecated, use [vm].min_deploy_points");
+                node_builder = node_builder.with_min_deploy_points(min);
+            }
+            if let Some(limit) = config.chain.block_gas_limit() {
+                warn!("[chain].block_gas_limit is deprecated, use [vm].block_gas_limit");
+                node_builder = node_builder.with_block_gas_limit(limit);
+            }
+        }
     };
 
     if config.http.listen {
diff --git a/rusk/src/lib/builder/node.rs b/rusk/src/lib/builder/node.rs
index 6612cb52c6..81b73a9ff6 100644
--- a/rusk/src/lib/builder/node.rs
+++ b/rusk/src/lib/builder/node.rs
@@ -106,30 +106,33 @@ impl RuskNodeBuilder {
         self
     }
 
-    pub fn with_generation_timeout(
+    #[deprecated(since = "1.0.3", note = "please use `with_vm_config` instead")]
+    pub fn with_generation_timeout<O: Into<Option<Duration>>>(
         mut self,
-        generation_timeout: Option<Duration>,
+        generation_timeout: O,
     ) -> Self {
-        self.vm_config.generation_timeout = generation_timeout;
+        self.vm_config.generation_timeout = generation_timeout.into();
         self
     }
 
-    pub fn with_gas_per_deploy_byte(
+    #[deprecated(since = "1.0.3", note = "please use `with_vm_config` instead")]
+    pub fn with_gas_per_deploy_byte<O: Into<Option<u64>>>(
         mut self,
-        gas_per_deploy_byte: Option<u64>,
+        gas_per_deploy_byte: O,
     ) -> Self {
-        if let Some(gas_per_deploy_byte) = gas_per_deploy_byte {
+        if let Some(gas_per_deploy_byte) = gas_per_deploy_byte.into() {
             self.vm_config.gas_per_deploy_byte = gas_per_deploy_byte;
         }
         self
     }
 
-    pub fn with_min_deployment_gas_price(
+    #[deprecated(since = "1.0.3", note = "please use `with_vm_config` instead")]
+    pub fn with_min_deployment_gas_price<O: Into<Option<u64>>>(
         mut self,
-        min_deployment_gas_price: Option<u64>,
+        min_deployment_gas_price: O,
     ) -> Self {
-        if let Some(min_deploy_gas_price) = min_deployment_gas_price {
-            self.vm_config.min_deploy_gas_price = min_deploy_gas_price;
+        if let Some(min_deploy_gas_price) = min_deployment_gas_price.into() {
+            self.vm_config.min_deployment_gas_price = min_deploy_gas_price;
         }
         self
     }
@@ -139,21 +142,23 @@ impl RuskNodeBuilder {
         self
     }
 
-    pub fn with_min_deploy_points(
+    #[deprecated(since = "1.0.3", note = "please use `with_vm_config` instead")]
+    pub fn with_min_deploy_points<O: Into<Option<u64>>>(
         mut self,
-        min_deploy_points: Option<u64>,
+        min_deploy_points: O,
     ) -> Self {
-        if let Some(min_deploy_points) = min_deploy_points {
+        if let Some(min_deploy_points) = min_deploy_points.into() {
             self.vm_config.min_deploy_points = min_deploy_points;
         }
         self
     }
 
-    pub fn with_block_gas_limit(
+    #[deprecated(since = "1.0.3", note = "please use `with_vm_config` instead")]
+    pub fn with_block_gas_limit<O: Into<Option<u64>>>(
         mut self,
-        block_gas_limit: Option<u64>,
+        block_gas_limit: O,
     ) -> Self {
-        if let Some(block_gas_limit) = block_gas_limit {
+        if let Some(block_gas_limit) = block_gas_limit.into() {
             self.vm_config.block_gas_limit = block_gas_limit;
         }
         self
@@ -179,6 +184,11 @@ impl RuskNodeBuilder {
         self
     }
 
+    pub fn with_vm_config(mut self, vm_config: RuskVmConfig) -> Self {
+        self.vm_config = vm_config;
+        self
+    }
+
     /// Build the RuskNode and corresponding services
     pub async fn build_and_run(self) -> anyhow::Result<()> {
         let channel_cap = self
diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs
index 44e5b128c8..dea2fa6c3d 100644
--- a/rusk/src/lib/node/rusk.rs
+++ b/rusk/src/lib/node/rusk.rs
@@ -123,7 +123,7 @@ impl Rusk {
 
         let mut event_bloom = Bloom::new();
 
-        let execution_config = self.vm_config.to_execution_config();
+        let execution_config = self.vm_config.to_execution_config(block_height);
 
         // We always write the faults len in a u32
         let mut size_left = params.max_txs_bytes - u32::SIZE;
@@ -252,7 +252,7 @@ impl Rusk {
         voters: &[Voter],
     ) -> Result<(Vec<SpentTransaction>, VerificationOutput)> {
         let session = self.new_block_session(block_height, prev_commit)?;
-        let execution_config = self.vm_config.to_execution_config();
+        let execution_config = self.vm_config.to_execution_config(block_height);
 
         accept(
             session,
@@ -292,7 +292,7 @@ impl Rusk {
     )> {
         let session = self.new_block_session(block_height, prev_commit)?;
 
-        let execution_config = self.vm_config.to_execution_config();
+        let execution_config = self.vm_config.to_execution_config(block_height);
 
         let (spent_txs, verification_output, session, events) = accept(
             session,
diff --git a/rusk/src/lib/node/vm.rs b/rusk/src/lib/node/vm.rs
index c64c83435c..8b89f52812 100644
--- a/rusk/src/lib/node/vm.rs
+++ b/rusk/src/lib/node/vm.rs
@@ -275,7 +275,7 @@ impl VMExecution for Rusk {
     }
 
     fn min_deployment_gas_price(&self) -> u64 {
-        self.vm_config.min_deploy_gas_price
+        self.vm_config.min_deployment_gas_price
     }
 
     fn min_gas_limit(&self) -> u64 {
diff --git a/rusk/src/lib/node/vm/config.rs b/rusk/src/lib/node/vm/config.rs
index 70012a6605..1ddb269cea 100644
--- a/rusk/src/lib/node/vm/config.rs
+++ b/rusk/src/lib/node/vm/config.rs
@@ -4,29 +4,52 @@
 //
 // Copyright (c) DUSK NETWORK. All rights reserved.
 
+use std::collections::HashMap;
 use std::time::Duration;
 
 use dusk_vm::ExecutionConfig;
+use serde::{Deserialize, Serialize};
 
-const DEFAULT_GAS_PER_DEPLOY_BYTE: u64 = 100;
-const DEFAULT_MIN_DEPLOYMENT_GAS_PRICE: u64 = 2000;
-const DEFAULT_MIN_DEPLOY_POINTS: u64 = 5_000_000;
-const DEFAULT_BLOCK_GAS_LIMIT: u64 = 3 * 1_000_000_000;
+const fn default_gas_per_deploy_byte() -> u64 {
+    100
+}
+const fn default_min_deploy_points() -> u64 {
+    5_000_000
+}
+const fn default_min_deployment_gas_price() -> u64 {
+    2_000
+}
+const fn default_block_gas_limit() -> u64 {
+    3 * 1_000_000_000
+}
 
 /// Configuration for the execution of a transaction.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Config {
     /// The amount of gas points charged for each byte in a contract-deployment
     /// bytecode.
+    #[serde(default = "default_gas_per_deploy_byte")]
     pub gas_per_deploy_byte: u64,
+
     /// The minimum gas points charged for a contract deployment.
+    #[serde(default = "default_min_deploy_points")]
     pub min_deploy_points: u64,
+
     /// The minimum gas price set for a contract deployment
-    pub min_deploy_gas_price: u64,
+    #[serde(default = "default_min_deployment_gas_price")]
+    pub min_deployment_gas_price: u64,
+
     /// The maximum amount of gas points that can be used in a block.
+    #[serde(default = "default_block_gas_limit")]
     pub block_gas_limit: u64,
+
     /// The timeout for a candidate block generation.
+    #[serde(with = "humantime_serde")]
+    #[serde(default)]
     pub generation_timeout: Option<Duration>,
+
+    /// Set of features to activate
+    pub features: HashMap<String, u64>,
 }
 
 impl Default for Config {
@@ -36,13 +59,14 @@ impl Default for Config {
 }
 
 impl Config {
-    pub const fn new() -> Self {
+    pub fn new() -> Self {
         Self {
-            gas_per_deploy_byte: DEFAULT_GAS_PER_DEPLOY_BYTE,
-            min_deploy_gas_price: DEFAULT_MIN_DEPLOYMENT_GAS_PRICE,
-            min_deploy_points: DEFAULT_MIN_DEPLOY_POINTS,
-            block_gas_limit: DEFAULT_BLOCK_GAS_LIMIT,
+            gas_per_deploy_byte: default_gas_per_deploy_byte(),
+            min_deployment_gas_price: default_min_deployment_gas_price(),
+            min_deploy_points: default_min_deploy_points(),
+            block_gas_limit: default_block_gas_limit(),
             generation_timeout: None,
+            features: HashMap::new(),
         }
     }
 
@@ -76,7 +100,7 @@ impl Config {
         mut self,
         min_deploy_gas_price: u64,
     ) -> Self {
-        self.min_deploy_gas_price = min_deploy_gas_price;
+        self.min_deployment_gas_price = min_deploy_gas_price;
         self
     }
 
@@ -90,11 +114,23 @@ impl Config {
     }
 
     /// Create a new `Config` with the given parameters.
-    pub fn to_execution_config(&self) -> ExecutionConfig {
+    pub fn to_execution_config(&self, block_height: u64) -> ExecutionConfig {
+        let with_public_sender = self
+            .feature("ABI_PUBLIC_SENDER")
+            .map(|activation| activation >= block_height)
+            .unwrap_or_default();
         ExecutionConfig {
             gas_per_deploy_byte: self.gas_per_deploy_byte,
             min_deploy_points: self.min_deploy_points,
-            min_deploy_gas_price: self.min_deploy_gas_price,
+            min_deploy_gas_price: self.min_deployment_gas_price,
+            with_public_sender,
         }
     }
+
+    pub fn feature(&self, feature: &str) -> Option<u64> {
+        self.features
+            .iter()
+            .find(|(k, _)| k.eq_ignore_ascii_case(feature))
+            .map(|(_, &v)| v)
+    }
 }
diff --git a/vm/CHANGELOG.md b/vm/CHANGELOG.md
index 15e884156a..07e5976d4d 100644
--- a/vm/CHANGELOG.md
+++ b/vm/CHANGELOG.md
@@ -7,11 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Add `PUBLIC_SENDER` available to session [#3341]
+
 ### Changed
 
 - Change `execution` module to use `execution::Config` [#3437]
+- Change `dusk-core` dependency to `1.0.1-alpha` [#3341]
+- Change `piecrust` dependency to `0.27.1` [#3341]
 
-[1.0.0] - 2025-01-23
+## [1.0.0] - 2025-01-23
 
 ### Changed
 
@@ -25,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <!-- Issues -->
 [#3235]: https://github.com/dusk-network/rusk/issues/3235
+[#3341]: https://github.com/dusk-network/rusk/issues/3341
 [#3405]: https://github.com/dusk-network/rusk/issues/3405
 [#3437]: https://github.com/dusk-network/rusk/issues/3437
 
diff --git a/vm/src/execute.rs b/vm/src/execute.rs
index 09659a9c5d..49d5ba11e9 100644
--- a/vm/src/execute.rs
+++ b/vm/src/execute.rs
@@ -7,10 +7,9 @@
 mod config;
 
 use blake2b_simd::Params;
-use dusk_core::abi::{ContractError, ContractId, CONTRACT_ID_BYTES};
-use dusk_core::transfer::{
-    data::ContractBytecode, Transaction, TRANSFER_CONTRACT,
-};
+use dusk_core::abi::{ContractError, ContractId, Metadata, CONTRACT_ID_BYTES};
+use dusk_core::transfer::data::ContractBytecode;
+use dusk_core::transfer::{Transaction, TRANSFER_CONTRACT};
 use piecrust::{CallReceipt, Error, Session};
 
 pub use config::Config;
@@ -70,14 +69,24 @@ pub fn execute(
     // with gas limit smaller than deploy charge.
     deploy_check(tx, config)?;
 
+    if config.with_public_sender {
+        let _ = session
+            .set_meta(Metadata::PUBLIC_SENDER, tx.moonlight_sender().copied());
+    }
+
     // 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.strip_off_bytecode().as_ref().unwrap_or(tx),
-        tx.gas_limit(),
-    )?;
+    let mut receipt = session
+        .call::<_, Result<Vec<u8>, ContractError>>(
+            TRANSFER_CONTRACT,
+            "spend_and_execute",
+            tx.strip_off_bytecode().as_ref().unwrap_or(tx),
+            tx.gas_limit(),
+        )
+        .map_err(|e| {
+            clear_session(session, config);
+            e
+        })?;
 
     // Deploy if this is a deployment transaction and spend part is successful.
     contract_deploy(session, tx, config, &mut receipt);
@@ -101,9 +110,17 @@ pub fn execute(
 
     receipt.events.extend(refund_receipt.events);
 
+    clear_session(session, config);
+
     Ok(receipt)
 }
 
+fn clear_session(session: &mut Session, config: &Config) {
+    if config.with_public_sender {
+        let _ = session.remove_meta(Metadata::PUBLIC_SENDER);
+    }
+}
+
 fn deploy_check(tx: &Transaction, config: &Config) -> Result<(), Error> {
     if tx.deploy().is_some() {
         let gas_per_deploy_byte = config.gas_per_deploy_byte;
diff --git a/vm/src/execute/config.rs b/vm/src/execute/config.rs
index 2c48fcd9ea..761f54d402 100644
--- a/vm/src/execute/config.rs
+++ b/vm/src/execute/config.rs
@@ -14,6 +14,10 @@ pub struct Config {
     pub min_deploy_points: u64,
     /// The minimum gas price set for a contract deployment
     pub min_deploy_gas_price: u64,
+    /// Enable the public sender metadata in the transaction.
+    ///
+    /// This field may be deprecated after the feature rollout.
+    pub with_public_sender: bool,
 }
 
 impl Default for Config {
@@ -28,5 +32,6 @@ impl Config {
         gas_per_deploy_byte: 0,
         min_deploy_points: 0,
         min_deploy_gas_price: 0,
+        with_public_sender: false,
     };
 }


Test patch:
diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs
index aaab1526a5..de4a753573 100644
--- a/rusk/tests/common/state.rs
+++ b/rusk/tests/common/state.rs
@@ -31,7 +31,6 @@ use tokio::sync::broadcast;
 use tracing::info;
 
 const CHAIN_ID: u8 = 0xFA;
-pub const DEFAULT_VM_CONFIG: RuskVmConfig = RuskVmConfig::new();
 pub const DEFAULT_MIN_GAS_LIMIT: u64 = 75000;
 
 // Creates a Rusk initial state in the given directory
diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs
index e6447e4718..f55c25a656 100644
--- a/rusk/tests/rusk-state.rs
+++ b/rusk/tests/rusk-state.rs
@@ -39,15 +39,13 @@ const CHAIN_ID: u8 = 0xFA;
 const BLOCK_GAS_LIMIT: u64 = 100_000_000_000;
 const INITIAL_BALANCE: u64 = 10_000_000_000;
 
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
-
 // Creates the Rusk initial state for the tests below
 fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("./config/rusk-state.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 fn leaves_from_height(rusk: &Rusk, height: u64) -> Result<Vec<NoteLeaf>> {
diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs
index 7f2d76ecb2..916f2357dd 100644
--- a/rusk/tests/services/contract_deployment.rs
+++ b/rusk/tests/services/contract_deployment.rs
@@ -25,7 +25,6 @@ use tracing::info;
 use crate::common::logger;
 use crate::common::state::{
     generator_procedure, ExecuteResult, DEFAULT_MIN_GAS_LIMIT,
-    DEFAULT_VM_CONFIG,
 };
 use crate::common::wallet::{
     test_wallet as wallet, TestStateClient, TestStore, Wallet,
@@ -105,7 +104,7 @@ fn initial_state<P: AsRef<Path>>(dir: P, deploy_bob: bool) -> Result<Rusk> {
     let rusk = Rusk::new(
         dir,
         CHAIN_ID,
-        DEFAULT_VM_CONFIG,
+        RuskVmConfig::new(),
         DEFAULT_MIN_GAS_LIMIT,
         u64::MAX,
         sender,
diff --git a/rusk/tests/services/contract_stake.rs b/rusk/tests/services/contract_stake.rs
index f5bde81239..9f77788983 100644
--- a/rusk/tests/services/contract_stake.rs
+++ b/rusk/tests/services/contract_stake.rs
@@ -31,8 +31,6 @@ use crate::common::*;
 
 const BLOCK_HEIGHT: u64 = 1;
 const BLOCK_GAS_LIMIT: u64 = 100_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
 const GAS_LIMIT: u64 = 10_000_000_000;
 const GAS_PRICE: u64 = 1;
@@ -42,8 +40,9 @@ fn stake_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot =
         toml::from_str(include_str!("../config/stake_from_contract.toml"))
             .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 #[tokio::test(flavor = "multi_thread")]
diff --git a/rusk/tests/services/conversion.rs b/rusk/tests/services/conversion.rs
index 28c6783ede..96399c8a81 100644
--- a/rusk/tests/services/conversion.rs
+++ b/rusk/tests/services/conversion.rs
@@ -22,8 +22,6 @@ use crate::common::wallet::{
 };
 
 const BLOCK_GAS_LIMIT: u64 = 100_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
 const INITIAL_PHOENIX_BALANCE: u64 = 10_000_000_000;
 const INITIAL_MOONLIGHT_BALANCE: u64 = 10_000_000_000;
@@ -32,8 +30,9 @@ const INITIAL_MOONLIGHT_BALANCE: u64 = 10_000_000_000;
 fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/convert.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 /// Makes a transaction that converts Dusk from Phoenix to Moonlight, and
diff --git a/rusk/tests/services/finalization.rs b/rusk/tests/services/finalization.rs
index c83a6fc44a..fdccf37dfb 100644
--- a/rusk/tests/services/finalization.rs
+++ b/rusk/tests/services/finalization.rs
@@ -12,8 +12,6 @@ use tempfile::tempdir;
 use crate::common::state::{generator_procedure2, new_state};
 
 const BLOCK_GAS_LIMIT: u64 = 24_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 const BLOCKS_NUM: u64 = 10;
 
 // Creates the Rusk initial state for the tests below
@@ -21,8 +19,9 @@ fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot =
         toml::from_str(include_str!("../config/multi_transfer.toml"))
             .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 #[tokio::test(flavor = "multi_thread")]
diff --git a/rusk/tests/services/gas_behavior.rs b/rusk/tests/services/gas_behavior.rs
index 5596bcc0b6..cc3ba2f88c 100644
--- a/rusk/tests/services/gas_behavior.rs
+++ b/rusk/tests/services/gas_behavior.rs
@@ -27,8 +27,6 @@ use crate::common::wallet::{
 
 const BLOCK_HEIGHT: u64 = 1;
 const BLOCK_GAS_LIMIT: u64 = 1_000_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
 const INITIAL_BALANCE: u64 = 10_000_000_000;
 
@@ -41,8 +39,9 @@ const DEPOSIT: u64 = 0;
 fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/gas-behavior.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 const SENDER_INDEX_0: u8 = 0;
diff --git a/rusk/tests/services/moonlight_stake.rs b/rusk/tests/services/moonlight_stake.rs
index e48a04a756..057eac9771 100644
--- a/rusk/tests/services/moonlight_stake.rs
+++ b/rusk/tests/services/moonlight_stake.rs
@@ -25,8 +25,6 @@ use crate::common::*;
 
 const BLOCK_HEIGHT: u64 = 1;
 const BLOCK_GAS_LIMIT: u64 = 100_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 const GAS_LIMIT: u64 = 10_000_000_000;
 const GAS_PRICE: u64 = 1;
 
@@ -34,8 +32,9 @@ const GAS_PRICE: u64 = 1;
 fn stake_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/stake.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 /// Stakes an amount Dusk and produces a block with this single transaction,
diff --git a/rusk/tests/services/multi_transfer.rs b/rusk/tests/services/multi_transfer.rs
index 2532738d70..c80970499e 100644
--- a/rusk/tests/services/multi_transfer.rs
+++ b/rusk/tests/services/multi_transfer.rs
@@ -25,8 +25,6 @@ const BLOCK_HEIGHT: u64 = 1;
 // This is purposefully chosen to be low to trigger the discarding of a
 // perfectly good transaction.
 const BLOCK_GAS_LIMIT: u64 = 24_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
 const GAS_LIMIT: u64 = 12_000_000; // Lowest value for a transfer
 const INITIAL_BALANCE: u64 = 10_000_000_000;
@@ -41,8 +39,9 @@ fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot =
         toml::from_str(include_str!("../config/multi_transfer.toml"))
             .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 // Creates the Rusk initial state for the tests below
@@ -50,8 +49,9 @@ fn initial_state_deploy<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot =
         toml::from_str(include_str!("../config/multi_transfer_deploy.toml"))
             .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 /// Executes three different transactions in the same block, expecting only two
diff --git a/rusk/tests/services/owner_calls.rs b/rusk/tests/services/owner_calls.rs
index 44e7e91f1e..9415616015 100644
--- a/rusk/tests/services/owner_calls.rs
+++ b/rusk/tests/services/owner_calls.rs
@@ -10,6 +10,7 @@ use rand::rngs::StdRng;
 use rand::SeedableRng;
 use rkyv::validation::validators::DefaultValidator;
 use rkyv::{Archive, Deserialize, Infallible, Serialize};
+use rusk::node::RuskVmConfig;
 use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, RwLock};
@@ -27,7 +28,7 @@ use tokio::sync::broadcast;
 use tracing::info;
 
 use crate::common::logger;
-use crate::common::state::{DEFAULT_MIN_GAS_LIMIT, DEFAULT_VM_CONFIG};
+use crate::common::state::DEFAULT_MIN_GAS_LIMIT;
 use crate::common::wallet::{
     test_wallet as wallet, test_wallet::Wallet, TestStateClient, TestStore,
 };
@@ -79,7 +80,7 @@ fn initial_state<P: AsRef<Path>>(
     let rusk = Rusk::new(
         dir,
         CHAIN_ID,
-        DEFAULT_VM_CONFIG,
+        RuskVmConfig::new(),
         DEFAULT_MIN_GAS_LIMIT,
         u64::MAX,
         sender,
diff --git a/rusk/tests/services/phoenix_stake.rs b/rusk/tests/services/phoenix_stake.rs
index c3c5396912..d8b73c282d 100644
--- a/rusk/tests/services/phoenix_stake.rs
+++ b/rusk/tests/services/phoenix_stake.rs
@@ -31,8 +31,6 @@ use crate::common::*;
 
 const BLOCK_HEIGHT: u64 = 1;
 const BLOCK_GAS_LIMIT: u64 = 100_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 const GAS_LIMIT: u64 = 10_000_000_000;
 const GAS_PRICE: u64 = 1;
 const DEPOSIT: u64 = 0;
@@ -41,16 +39,18 @@ const DEPOSIT: u64 = 0;
 fn stake_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/stake.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 // Creates the Rusk initial state for the tests below
 fn slash_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/slash.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 /// Stakes an amount Dusk and produces a block with this single transaction,
diff --git a/rusk/tests/services/transfer.rs b/rusk/tests/services/transfer.rs
index 08ebcd8ac3..e71c3d9c43 100644
--- a/rusk/tests/services/transfer.rs
+++ b/rusk/tests/services/transfer.rs
@@ -23,8 +23,6 @@ use crate::common::wallet::{
 };
 
 const BLOCK_GAS_LIMIT: u64 = 100_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 const INITIAL_BALANCE: u64 = 10_000_000_000;
 const MAX_NOTES: u64 = 10;
 
@@ -32,8 +30,9 @@ const MAX_NOTES: u64 = 10;
 fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/transfer.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 /// Transacts between two accounts on the in the same wallet and produces a
diff --git a/rusk/tests/services/unspendable.rs b/rusk/tests/services/unspendable.rs
index 1ef140c489..0b57fdaf18 100644
--- a/rusk/tests/services/unspendable.rs
+++ b/rusk/tests/services/unspendable.rs
@@ -27,8 +27,6 @@ use crate::common::wallet::{
 
 const BLOCK_HEIGHT: u64 = 1;
 const BLOCK_GAS_LIMIT: u64 = 1_000_000_000_000;
-const VM_CONFIG: RuskVmConfig =
-    RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 const INITIAL_BALANCE: u64 = 10_000_000_000;
 
 const GAS_LIMIT_0: u64 = 20_000_000; // Enough to spend, but OOG during ICC
@@ -41,8 +39,9 @@ const DEPOSIT: u64 = 0;
 fn initial_state<P: AsRef<Path>>(dir: P) -> Result<Rusk> {
     let snapshot = toml::from_str(include_str!("../config/unspendable.toml"))
         .expect("Cannot deserialize config");
+    let vm_config = RuskVmConfig::new().with_block_gas_limit(BLOCK_GAS_LIMIT);
 
-    new_state(dir, &snapshot, VM_CONFIG)
+    new_state(dir, &snapshot, vm_config)
 }
 
 const SENDER_INDEX_0: u8 = 0;