mod config;
mod query;
use dusk_consensus::errors::StateTransitionError;
use dusk_core::abi::ContractId;
use node_data::events::contract::ContractTxEvent;
use tracing::{debug, info};
use dusk_consensus::operations::{
StateTransitionData, StateTransitionResult, Voter,
};
use dusk_consensus::user::provisioners::Provisioners;
use dusk_consensus::user::stake::Stake;
use dusk_core::signatures::bls::PublicKey as BlsPublicKey;
use dusk_core::stake::StakeData;
use dusk_core::transfer::Transaction as ProtocolTransaction;
use node::vm::{PreverificationResult, VMExecution};
use node_data::bls::PublicKey;
use node_data::hard_fork::{bls_version_at, hard_fork_at};
use node_data::ledger::{Block, Header, SpentTransaction, Transaction};
use super::rusk::plonk_version_at;
use super::{RuesEvent, Rusk};
pub use config::Config as RuskVmConfig;
pub use config::feature::*;
pub use config::known::WellKnownConfig as WellKnownVmConfig;
pub use config::opt::OptionalConfig as RuskOptVmConfig;
use crate::Error as RuskError;
impl VMExecution for Rusk {
fn create_state_transition<I: Iterator<Item = Transaction>>(
&self,
transition_data: &StateTransitionData,
mempool_txs: I,
) -> Result<
(
Vec<SpentTransaction>,
Vec<Transaction>,
StateTransitionResult,
),
StateTransitionError,
> {
self.create_state_transition(transition_data, mempool_txs)
}
fn verify_state_transition(
&self,
prev_state: [u8; 32],
blk: &Block,
cert_voters: &[Voter],
) -> Result<(), StateTransitionError> {
debug!("Verifying state transition");
let (_, transition_result, _, _) =
self.execute_state_transition(prev_state, blk, cert_voters)?;
check_transition_result(&transition_result, blk.header())?;
Ok(())
}
fn accept_state_transition(
&self,
prev_state: [u8; 32],
blk: &Block,
cert_voters: &[Voter],
) -> Result<
(Vec<SpentTransaction>, Vec<ContractTxEvent>),
StateTransitionError,
> {
debug!("Accepting state transition");
let (executed_txs, transition_result, contract_events, session) =
self.execute_state_transition(prev_state, blk, cert_voters)?;
check_transition_result(&transition_result, blk.header())?;
self.commit_session(session).map_err(|err| {
StateTransitionError::PersistenceError(format!("{err}"))
})?;
for event in contract_events.clone() {
let rues_event = RuesEvent::from(event);
let _ = self.event_sender.send(rues_event);
}
Ok((executed_txs, contract_events))
}
fn move_to_commit(&self, commit: [u8; 32]) -> anyhow::Result<()> {
self.query_session(Some(commit))
.map_err(|e| anyhow::anyhow!("Cannot open session {e}"))?;
self.set_current_commit(commit);
Ok(())
}
fn finalize_state(
&self,
commit: [u8; 32],
to_merge: Vec<[u8; 32]>,
) -> anyhow::Result<()> {
debug!("Received finalize request");
self.finalize_state(commit, to_merge)
.map_err(|e| anyhow::anyhow!("Cannot finalize state: {e}"))
}
fn preverify(
&self,
tx: &Transaction,
tip_height: u64,
) -> anyhow::Result<PreverificationResult> {
info!("Received preverify request");
let tx = &tx.inner;
match tx {
ProtocolTransaction::Phoenix(tx) => {
let tx_nullifiers = tx.nullifiers().to_vec();
let existing_nullifiers =
self.existing_nullifiers(&tx_nullifiers).map_err(|e| {
anyhow::anyhow!("Cannot check nullifiers: {e}")
})?;
if !existing_nullifiers.is_empty() {
let err =
RuskError::RepeatingNullifiers(existing_nullifiers);
return Err(anyhow::anyhow!("{err}"));
}
if !has_unique_elements(tx_nullifiers) {
let err = RuskError::DoubleNullifiers;
return Err(anyhow::anyhow!("{err}"));
}
let next_block_height = tip_height.saturating_add(1);
let version = plonk_version_at(
&self.vm_config,
next_block_height,
hard_fork_at(next_block_height),
);
match crate::verifier::verify_proof_with_version(tx, version) {
Ok(true) => Ok(PreverificationResult::Valid),
Ok(false) => Err(anyhow::anyhow!("Invalid proof")),
Err(e) => {
Err(anyhow::anyhow!("Cannot verify the proof: {e}"))
}
}
}
ProtocolTransaction::Moonlight(tx) => {
let next_block_height = tip_height.saturating_add(1);
let account_data = self.account(tx.sender()).map_err(|e| {
anyhow::anyhow!("Cannot check account: {e}")
})?;
let max_value = tx
.gas_limit()
.checked_mul(tx.gas_price())
.and_then(|v| v.checked_add(tx.value()))
.and_then(|v| v.checked_add(tx.deposit()))
.ok_or(anyhow::anyhow!("Value spent will overflow"))?;
if max_value > account_data.balance {
return Err(anyhow::anyhow!(
"Value spent larger than account holds"
));
}
if tx.nonce() <= account_data.nonce {
let err = RuskError::RepeatingNonce(
(*tx.sender()).into(),
tx.nonce(),
);
return Err(anyhow::anyhow!("{err}"));
}
let result = if tx.nonce() > account_data.nonce + 1 {
PreverificationResult::FutureNonce {
account: *tx.sender(),
state: account_data,
nonce_used: tx.nonce(),
}
} else {
PreverificationResult::Valid
};
let blob_converted = tx.blob_to_memo();
let verify_tx = blob_converted.as_ref().unwrap_or(tx);
let verify_result = dusk_core::signatures::bls::verify(
verify_tx.sender(),
verify_tx.signature(),
&verify_tx.signature_message(),
bls_version_at(next_block_height),
);
match verify_result {
Ok(()) => Ok(result),
Err(_) => Err(anyhow::anyhow!("Invalid signature")),
}
}
}
}
fn get_provisioners(
&self,
base_commit: [u8; 32],
) -> anyhow::Result<Provisioners> {
self.query_provisioners(Some(base_commit))
}
fn get_changed_provisioners(
&self,
base_commit: [u8; 32],
) -> anyhow::Result<Vec<(PublicKey, Option<Stake>)>> {
self.query_provisioners_change(Some(base_commit))
}
fn get_provisioner(
&self,
pk: &BlsPublicKey,
) -> anyhow::Result<Option<Stake>> {
let stake = self
.provisioner(pk)
.map_err(|e| anyhow::anyhow!("Cannot get provisioner {e}"))?
.map(Self::to_stake);
Ok(stake)
}
fn get_state_root(&self) -> anyhow::Result<[u8; 32]> {
Ok(self.state_root())
}
fn get_finalized_state_root(&self) -> anyhow::Result<[u8; 32]> {
Ok(self.base_root())
}
fn revert(&self, state_hash: [u8; 32]) -> anyhow::Result<[u8; 32]> {
let state_hash = self
.revert(state_hash)
.map_err(|inner| anyhow::anyhow!("Cannot revert: {inner}"))?;
Ok(state_hash)
}
fn revert_to_finalized(&self) -> anyhow::Result<[u8; 32]> {
let state_hash = self.revert_to_base_root().map_err(|inner| {
anyhow::anyhow!("Cannot revert to finalized: {inner}")
})?;
Ok(state_hash)
}
fn get_block_gas_limit(&self) -> u64 {
self.vm_config.block_gas_limit
}
fn gas_per_deploy_byte(&self) -> u64 {
self.vm_config.gas_per_deploy_byte
}
fn min_deployment_gas_price(&self) -> u64 {
self.vm_config.min_deployment_gas_price
}
fn min_gas_limit(&self) -> u64 {
self.min_gas_limit
}
fn min_deploy_points(&self) -> u64 {
self.vm_config.min_deploy_points
}
fn gas_per_blob(&self) -> u64 {
self.vm_config.gas_per_blob
}
fn blob_active(&self, block_height: u64) -> bool {
self.vm_config
.feature(FEATURE_BLOB)
.map(|activation| activation.is_active_at(block_height))
.unwrap_or(false)
}
fn wasm64_disabled(&self, block_height: u64) -> bool {
self.vm_config
.feature(FEATURE_DISABLE_WASM64)
.map(|activation| activation.is_active_at(block_height))
.unwrap_or(false)
}
fn wasm32_disabled(&self, block_height: u64) -> bool {
self.vm_config
.feature(FEATURE_DISABLE_WASM32)
.map(|activation| activation.is_active_at(block_height))
.unwrap_or(false)
}
fn third_party_disabled(&self, block_height: u64) -> bool {
self.vm_config
.feature(FEATURE_DISABLE_3RD_PARTY)
.map(|activation| activation.is_active_at(block_height))
.unwrap_or(false)
}
fn phoenix_refund_check_active(&self, block_height: u64) -> bool {
self.vm_config
.feature(FEATURE_HARDFORK_AEGIS)
.map(|activation| activation.is_active_at(block_height))
.unwrap_or(false)
}
fn shade_3rd_party(&self, contract_id: ContractId) -> anyhow::Result<()> {
self.shade_3rd_party(contract_id).map_err(|inner| {
anyhow::anyhow!("Cannot remove 3rd party: {inner}")
})
}
fn enable_3rd_party(&self, contract_id: ContractId) -> anyhow::Result<()> {
self.recompile_3rd_party(contract_id).map_err(|inner| {
anyhow::anyhow!("Cannot enable 3rd party: {inner}")
})
}
}
fn has_unique_elements<T>(iter: T) -> bool
where
T: IntoIterator,
T::Item: Eq + std::hash::Hash,
{
let mut uniq = std::collections::HashSet::new();
iter.into_iter().all(move |x| uniq.insert(x))
}
impl Rusk {
fn query_provisioners(
&self,
base_commit: Option<[u8; 32]>,
) -> anyhow::Result<Provisioners> {
info!("Received get_provisioners request");
let provisioners = self
.provisioners(base_commit)
.map_err(|e| anyhow::anyhow!("Cannot get provisioners {e}"))?
.map(|(pk, stake)| {
(PublicKey::new(pk.account), Self::to_stake(stake))
});
let mut ret = Provisioners::empty();
for (pubkey_bls, stake) in provisioners {
if stake.value() > 0 {
ret.add_provisioner(pubkey_bls, stake);
}
}
Ok(ret)
}
fn query_provisioners_change(
&self,
base_commit: Option<[u8; 32]>,
) -> anyhow::Result<Vec<(PublicKey, Option<Stake>)>> {
info!("Received get_provisioners_change request");
Ok(self
.last_provisioners_change(base_commit)
.map_err(|e| {
anyhow::anyhow!("Cannot get provisioners change: {e}")
})?
.into_iter()
.map(|(pk, stake)| (PublicKey::new(pk), stake.map(Self::to_stake)))
.collect())
}
fn to_stake(stake: StakeData) -> Stake {
let stake_amount = stake.amount.unwrap_or_default();
let value = stake_amount.value;
Stake::new(value, stake_amount.eligibility)
}
}
fn check_transition_result(
transition_result: &StateTransitionResult,
header: &Header,
) -> Result<(), StateTransitionError> {
if transition_result.state_root != header.state_hash {
return Err(StateTransitionError::StateRootMismatch(
transition_result.state_root,
header.state_hash,
));
}
if transition_result.event_bloom != header.event_bloom {
return Err(StateTransitionError::EventBloomMismatch(
Box::new(transition_result.event_bloom),
Box::new(header.event_bloom),
));
}
Ok(())
}