diff --git a/Cargo.toml b/Cargo.toml index 706fe35c63..e6a64dc70a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ kadcast = "0.7.0-rc.11" phoenix-circuits = { version = "=0.4.0", default-features = false } phoenix-core = { version = "=0.32.0", default-features = false } # we leave piecrust open until a stable release is out -piecrust = "0.25.1-rc.0" +piecrust = "=0.26.0-rc.2" piecrust-uplink = "0.17.1" poseidon-merkle = "=0.7.0" zk-citadel = "=0.14.0" diff --git a/consensus/src/user/provisioners.rs b/consensus/src/user/provisioners.rs index 696872f18b..32650dc1eb 100644 --- a/consensus/src/user/provisioners.rs +++ b/consensus/src/user/provisioners.rs @@ -121,6 +121,13 @@ impl Provisioners { self.members.entry(pubkey_bls).or_insert_with(|| stake); } + pub fn get_member_mut( + &mut self, + pubkey_bls: &PublicKey, + ) -> Option<&mut Stake> { + self.members.get_mut(pubkey_bls) + } + pub fn replace_stake( &mut self, pubkey_bls: PublicKey, diff --git a/consensus/src/user/stake.rs b/consensus/src/user/stake.rs index 6e6716f903..40dd09d1cd 100644 --- a/consensus/src/user/stake.rs +++ b/consensus/src/user/stake.rs @@ -8,23 +8,14 @@ pub struct Stake { value: u64, - pub reward: u64, - pub nonce: u64, pub eligible_since: u64, } impl Stake { - pub fn new( - value: u64, - reward: u64, - eligible_since: u64, - nonce: u64, - ) -> Self { + pub fn new(value: u64, eligible_since: u64) -> Self { Self { value, - reward, eligible_since, - nonce, } } @@ -57,4 +48,12 @@ impl Stake { sub } } + + pub fn change_eligibility(&mut self, new_value: u64) { + self.eligible_since = new_value; + } + + pub fn add(&mut self, add: u64) { + self.value += add; + } } diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index fa6d9def3c..24eba03c93 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -11,29 +11,32 @@ use dusk_consensus::commons::TimeoutSet; use dusk_consensus::config::{MAX_STEP_TIMEOUT, MIN_STEP_TIMEOUT}; use dusk_consensus::errors::{ConsensusError, HeaderError}; use dusk_consensus::user::provisioners::{ContextProvisioners, Provisioners}; +use dusk_consensus::user::stake::Stake; use node_data::bls::PublicKey; +use node_data::events::contract::ContractEvent; use node_data::events::{ BlockEvent, Event, TransactionEvent, BLOCK_CONFIRMED, BLOCK_FINALIZED, }; use node_data::ledger::{ - self, to_str, Block, BlockWithLabel, Label, Seed, Slash, SpentTransaction, + self, to_str, Block, BlockWithLabel, Label, Seed, Slash, }; use node_data::message::AsyncQueue; use node_data::message::Payload; +use rkyv::{check_archived_root, Deserialize, Infallible}; use core::panic; use dusk_consensus::operations::Voter; -use execution_core::stake::{Withdraw, STAKE_CONTRACT}; +use execution_core::stake::{SlashEvent, StakeEvent}; use metrics::{counter, gauge, histogram}; use node_data::message::payload::Vote; use node_data::{get_current_timestamp, Serializable, StepName}; use std::collections::BTreeMap; -use std::sync::{Arc, LazyLock}; +use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{cmp, env}; use tokio::sync::mpsc::Sender; use tokio::sync::{RwLock, RwLockReadGuard}; -use tracing::{debug, info, warn}; +use tracing::{info, warn}; use super::consensus::Task; use crate::chain::header_validation::{verify_faults, Validator}; @@ -88,38 +91,57 @@ impl Drop } } -const STAKE: &str = "stake"; -const UNSTAKE: &str = "unstake"; - #[derive(Debug)] enum ProvisionerChange { - Stake(PublicKey), - Unstake(PublicKey), - Slash(PublicKey), - Reward(PublicKey), + Stake(StakeEvent), + Unstake(StakeEvent), + Slash(SlashEvent), + HashSlash(SlashEvent), +} + +fn stake_event(data: &[u8]) -> StakeEvent { + let staking_event_data = check_archived_root::(data) + .expect("Stake event data should deserialize correctly"); + let staking_event_data: StakeEvent = staking_event_data + .deserialize(&mut Infallible) + .expect("Infallible"); + staking_event_data +} + +fn slash_event(data: &[u8]) -> SlashEvent { + let staking_event_data = check_archived_root::(data) + .expect("Stake event data should deserialize correctly"); + let staking_event_data: SlashEvent = staking_event_data + .deserialize(&mut Infallible) + .expect("Infallible"); + staking_event_data } impl ProvisionerChange { - fn into_public_key(self) -> PublicKey { - match self { - ProvisionerChange::Slash(pk) => pk, - ProvisionerChange::Unstake(pk) => pk, - ProvisionerChange::Stake(pk) => pk, - ProvisionerChange::Reward(pk) => pk, - } + pub fn from_event(event: &ContractEvent) -> Option { + let event = match event.topic.as_str() { + "stake" => ProvisionerChange::Stake(stake_event(&event.data)), + "unstake" => ProvisionerChange::Unstake(stake_event(&event.data)), + "slash" => ProvisionerChange::Slash(slash_event(&event.data)), + "hard_slash" => { + ProvisionerChange::HashSlash(slash_event(&event.data)) + } + _ => return None, + }; + Some(event) } - fn is_stake(&self) -> bool { - matches!(self, ProvisionerChange::Stake(_)) + fn to_public_key(&self) -> PublicKey { + let key = match self { + ProvisionerChange::Slash(e) => e.account, + ProvisionerChange::HashSlash(e) => e.account, + ProvisionerChange::Unstake(e) => e.keys.account, + ProvisionerChange::Stake(e) => e.keys.account, + }; + PublicKey::new(key) } } -pub static DUSK_KEY: LazyLock = LazyLock::new(|| { - let dusk_cpk_bytes = include_bytes!("../../../rusk/src/assets/dusk.cpk"); - PublicKey::try_from(*dusk_cpk_bytes) - .expect("Dusk consensus public key to be valid") -}); - impl Acceptor { /// Initializes a new `Acceptor` struct, /// @@ -319,41 +341,52 @@ impl Acceptor { } fn selective_update( - blk: &Block, - txs: &[SpentTransaction], - vm: &tokio::sync::RwLockWriteGuard<'_, VM>, + stake_events: &[ContractEvent], provisioners_list: &mut tokio::sync::RwLockWriteGuard< '_, ContextProvisioners, >, ) -> Result<()> { let src = "selective"; - let changed_prov = Self::changed_provisioners(blk, txs)?; + let changed_prov: Vec<_> = stake_events + .iter() + .filter_map(ProvisionerChange::from_event) + .collect(); if changed_prov.is_empty() { provisioners_list.remove_previous(); } else { let mut new_prov = provisioners_list.current().clone(); for change in changed_prov { - let is_stake = change.is_stake(); - info!(event = "provisioner_update", src, ?change); - let pk = change.into_public_key(); - let prov = pk.to_bs58(); - match vm.get_provisioner(pk.inner())? { - Some(stake) => { - debug!(event = "new_stake", src, prov, ?stake); - let replaced = new_prov.replace_stake(pk, stake); - if replaced.is_none() && !is_stake { - anyhow::bail!("Replaced a not existing stake") + let account = change.to_public_key(); + match &change { + ProvisionerChange::Stake(stake_event) => { + let eligible_since = 0u64; + let replaced = new_prov.replace_stake( + account, + Stake::new(stake_event.value, eligible_since), + ); + if replaced.is_some() { + anyhow::bail!("Staking to an existing stake") }; - debug!(event = "old_stake", src, prov, ?replaced); } - _ => { - let removed = new_prov.remove_stake(&pk).ok_or( - anyhow::anyhow!("Removed a not existing stake"), + ProvisionerChange::Unstake(_) => { + new_prov.remove_stake(&account).ok_or( + anyhow::anyhow!("Unstake a not existing stake"), )?; - debug!(event = "removed_stake", src, prov, ?removed); + } + ProvisionerChange::Slash(slash_event) + | ProvisionerChange::HashSlash(slash_event) => { + let to_slash = new_prov + .get_member_mut(&account) + .ok_or(anyhow::anyhow!( + "Slashing a not existing stake" + ))?; + to_slash.subtract(slash_event.value); + to_slash + .change_eligibility(slash_event.next_eligibility); } } + info!(event = "provisioner_update", src, ?change); } // Update new prov provisioners_list.update_and_swap(new_prov); @@ -361,72 +394,6 @@ impl Acceptor { Ok(()) } - fn changed_provisioners( - blk: &Block, - txs: &[SpentTransaction], - ) -> Result> { - let generator = blk.header().generator_bls_pubkey.0; - let generator = generator - .try_into() - .map_err(|e| anyhow::anyhow!("Cannot deserialize bytes {e:?}"))?; - let reward = ProvisionerChange::Reward(generator); - let dusk_reward = ProvisionerChange::Reward(DUSK_KEY.clone()); - let mut changed_provisioners = vec![reward, dusk_reward]; - - // Update provisioners if a slash has been applied - let slashed = Slash::from_block(blk)? - .into_iter() - .map(|f| ProvisionerChange::Slash(f.provisioner)); - changed_provisioners.extend(slashed); - - // FIX_ME: This relies on the stake contract being called only by the - // transfer contract. We should change this once third-party contracts - // hit the chain. - let stake_calls = - txs.iter().filter(|t| t.err.is_none()).filter_map(|t| { - match &t.inner.inner.call() { - Some(call) - if (call.contract == STAKE_CONTRACT - && (call.fn_name == STAKE - || call.fn_name == UNSTAKE)) => - { - Some((&call.fn_name, &call.fn_args)) - } - _ => None, - } - }); - - for (f, data) in stake_calls { - changed_provisioners.push(Self::parse_stake_call(f, data)?); - } - - Ok(changed_provisioners) - } - - fn parse_stake_call( - fn_name: &str, - calldata: &[u8], - ) -> Result { - let change = match fn_name { - UNSTAKE => { - let unstake: Withdraw = - rkyv::from_bytes(calldata).map_err(|e| { - anyhow::anyhow!("Cannot deserialize unstake rkyv {e:?}") - })?; - ProvisionerChange::Unstake(PublicKey::new(*unstake.account())) - } - STAKE => { - let stake: execution_core::stake::Stake = - rkyv::from_bytes(calldata).map_err(|e| { - anyhow::anyhow!("Cannot deserialize stake rkyv {e:?}") - })?; - ProvisionerChange::Stake(PublicKey::new(*stake.account())) - } - e => unreachable!("Parsing unexpected method: {e}"), - }; - Ok(change) - } - /// Updates tip together with provisioners list. /// /// # Arguments @@ -498,6 +465,7 @@ impl Acceptor { blk: &Block, enable_consensus: bool, ) -> anyhow::Result { + info!(src = "try_accept", event = "init"); let mut events = vec![]; let mut task = self.task.write().await; @@ -516,9 +484,15 @@ impl Acceptor { ) .await?; + let header_verification_elapsed = header_verification_start.elapsed(); + info!( + src = "try_accept", + event = "header verified", + dur = header_verification_elapsed.as_millis() + ); // Elapsed time header verification histogram!("dusk_block_header_elapsed") - .record(header_verification_start.elapsed()); + .record(header_verification_elapsed); let start = std::time::Instant::now(); let mut est_elapsed_time = Duration::default(); @@ -531,27 +505,41 @@ impl Acceptor { let vm = self.vm.write().await; - let (txs, rolling_result) = self.db.read().await.update(|db| { - let (txs, verification_output) = - vm.accept(blk, &prev_block_voters[..])?; - for spent_tx in txs.iter() { - events.push(TransactionEvent::Executed(spent_tx).into()); - } - est_elapsed_time = start.elapsed(); + let (stake_events, rolling_result) = + self.db.read().await.update(|db| { + info!(src = "try_accept", event = "before accept",); + let (txs, verification_output, stake_events) = + vm.accept(blk, &prev_block_voters[..])?; + info!(src = "try_accept", event = "after accept",); + for spent_tx in txs.iter() { + events + .push(TransactionEvent::Executed(spent_tx).into()); + } + est_elapsed_time = start.elapsed(); - assert_eq!(header.state_hash, verification_output.state_root); - assert_eq!(header.event_bloom, verification_output.event_bloom); + assert_eq!( + header.state_hash, + verification_output.state_root + ); + assert_eq!( + header.event_bloom, + verification_output.event_bloom + ); - let rolling_results = - self.rolling_finality::(pni, blk, db, &mut events)?; + info!(src = "try_accept", event = "before rolling",); + let rolling_results = + self.rolling_finality::(pni, blk, db, &mut events)?; + info!(src = "try_accept", event = "after rolling",); - let label = rolling_results.0; - // Store block with updated transactions with Error and GasSpent - block_size_on_disk = - db.store_block(header, &txs, blk.faults(), label)?; + let label = rolling_results.0; + // Store block with updated transactions with Error and + // GasSpent + block_size_on_disk = + db.store_block(header, &txs, blk.faults(), label)?; + info!(src = "try_accept", event = "after store_block",); - Ok((txs, rolling_results)) - })?; + Ok((stake_events, rolling_results)) + })?; self.log_missing_iterations( provisioners_list.current(), @@ -569,9 +557,11 @@ impl Acceptor { ); slashed_count += 1; } + info!(src = "try_accept", event = "before selective_update",); let selective_update = - Self::selective_update(blk, &txs, &vm, &mut provisioners_list); + Self::selective_update(&stake_events, &mut provisioners_list); + info!(src = "try_accept", event = "after selective_update",); if let Err(e) = selective_update { warn!("Resync provisioners due to {e:?}"); @@ -594,6 +584,8 @@ impl Acceptor { .chain([prev_final_state]) .collect::>(); vm.finalize_state(new_final_state, old_finals_to_merge)?; + + info!(src = "try_accept", event = "after finalize",); } anyhow::Ok((label, finalized)) @@ -611,6 +603,7 @@ impl Acceptor { block_size_on_disk, slashed_count, ); + info!(src = "try_accept", event = "after metrics",); // Clean up the database let count = self diff --git a/node/src/vm.rs b/node/src/vm.rs index b31afd7626..fa39b8bf14 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -12,6 +12,7 @@ use dusk_consensus::{ use execution_core::signatures::bls::PublicKey as BlsPublicKey; use execution_core::transfer::data::ContractBytecode; use execution_core::transfer::moonlight::AccountData; +use node_data::events::contract::ContractEvent; use node_data::ledger::{Block, SpentTransaction, Transaction}; #[derive(Default)] @@ -38,7 +39,11 @@ pub trait VMExecution: Send + Sync + 'static { &self, blk: &Block, voters: &[Voter], - ) -> anyhow::Result<(Vec, VerificationOutput)>; + ) -> anyhow::Result<( + Vec, + VerificationOutput, + Vec, + )>; fn finalize_state( &self, diff --git a/rusk/src/bin/args/state.rs b/rusk/src/bin/args/state.rs index c66fba3a7a..27b230f71d 100644 --- a/rusk/src/bin/args/state.rs +++ b/rusk/src/bin/args/state.rs @@ -45,9 +45,8 @@ pub fn recovery_state( let state_id_path = rusk_profile::to_rusk_state_id_path(&state_dir); - let _ = rusk_abi::new_vm(&state_dir)?; - - // if the state already exists in the expected path, stop early. + // if the state already exists in the expected path, and it's valid, stop + // early. if state_dir.exists() && state_id_path.exists() { info!("{} existing state", theme.info("Found")); diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 025ef665e4..4b46ba3f6a 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -31,7 +31,7 @@ use execution_core::{ BlsScalar, ContractError, Dusk, Event, }; use node::vm::bytecode_charge; -use node_data::events::contract::ContractTxEvent; +use node_data::events::contract::{ContractEvent, ContractTxEvent}; use node_data::ledger::{Hash, Slash, SpentTransaction, Transaction}; use rusk_abi::{CallReceipt, PiecrustError, Session}; use rusk_profile::to_rusk_state_id_path; @@ -297,7 +297,11 @@ impl Rusk { consistency_check: Option, slashing: Vec, voters: &[Voter], - ) -> Result<(Vec, VerificationOutput)> { + ) -> Result<( + Vec, + VerificationOutput, + Vec, + )> { let session = self.session(block_height, None)?; let (spent_txs, verification_output, session, events) = accept( @@ -314,6 +318,12 @@ impl Rusk { if let Some(expected_verification) = consistency_check { if expected_verification != verification_output { + tracing::error!( + src = "vm_accept", + event = "invalid verification", + ?expected_verification, + ?verification_output + ); // Drop the session if the resulting is inconsistent // with the callers one. return Err(Error::InconsistentState(Box::new( @@ -322,7 +332,11 @@ impl Rusk { } } - self.set_current_commit(session.commit()?); + info!(src = "vm_accept", event = "before commit"); + let commit = session.commit()?; + info!(src = "vm_accept", event = "after commit"); + self.set_current_commit(commit); + info!(src = "vm_accept", event = "after set_current_commit"); // Sent events to archivist #[cfg(feature = "archive")] @@ -334,13 +348,17 @@ impl Rusk { )); } + let mut stake_events = vec![]; for event in events { + if event.event.target.0 == STAKE_CONTRACT { + stake_events.push(event.event.clone()); + } // Send VN event to RUES let event = RuesEvent::from(event); let _ = self.event_sender.send(event); } - Ok((spent_txs, verification_output)) + Ok((spent_txs, verification_output, stake_events)) } pub fn finalize_state( @@ -512,6 +530,7 @@ fn accept( Session, Vec, )> { + info!(src = "vm_accept", event = "init"); let mut session = session; let mut block_gas_left = block_gas_limit; @@ -525,12 +544,20 @@ fn accept( for unspent_tx in txs { let tx = &unspent_tx.inner; let tx_id = unspent_tx.id(); + info!(src = "vm_accept", event = "before execute"); let receipt = execute( &mut session, tx, gas_per_deploy_byte, min_deployment_gas_price, )?; + info!(src = "vm_accept", event = "after execute"); + + info!( + src = "bloom", + event = "adding events", + len = receipt.events.len() + ); event_bloom.add_events(&receipt.events); @@ -561,6 +588,7 @@ fn accept( }); } + info!(src = "vm_accept", event = "before reward"); let coinbase_events = reward_slash_and_update_root( &mut session, block_height, @@ -569,6 +597,12 @@ fn accept( slashing, voters, )?; + info!(src = "vm_accept", event = "after reward"); + info!( + src = "bloom", + event = "adding reward events", + len = coinbase_events.len() + ); event_bloom.add_events(&coinbase_events); @@ -581,7 +615,9 @@ fn accept( .collect(); events.extend(coinbase_events); + info!(src = "vm_accept", event = "before calculating root"); let state_root = session.root(); + info!(src = "vm_accept", event = "after calculating root"); Ok(( spent_txs, diff --git a/rusk/src/lib/node/vm.rs b/rusk/src/lib/node/vm.rs index a17c05b745..5ff66f348f 100644 --- a/rusk/src/lib/node/vm.rs +++ b/rusk/src/lib/node/vm.rs @@ -6,6 +6,7 @@ mod query; +use node_data::events::contract::ContractEvent; use tracing::info; use dusk_bytes::DeserializableSlice; @@ -72,7 +73,11 @@ impl VMExecution for Rusk { &self, blk: &Block, voters: &[Voter], - ) -> anyhow::Result<(Vec, VerificationOutput)> { + ) -> anyhow::Result<( + Vec, + VerificationOutput, + Vec, + )> { info!("Received accept request"); let generator = blk.header().generator_bls_pubkey; let generator = BlsPublicKey::from_slice(&generator.0) @@ -80,7 +85,7 @@ impl VMExecution for Rusk { let slashing = Slash::from_block(blk)?; - let (txs, verification_output) = self + let (txs, verification_output, stake_events) = self .accept_transactions( blk.header().height, blk.header().gas_limit, @@ -96,7 +101,7 @@ impl VMExecution for Rusk { ) .map_err(|inner| anyhow::anyhow!("Cannot accept txs: {inner}!!"))?; - Ok((txs, verification_output)) + Ok((txs, verification_output, stake_events)) } fn move_to_commit(&self, commit: [u8; 32]) -> anyhow::Result<()> { @@ -306,8 +311,7 @@ impl Rusk { let stake_amount = stake.amount.unwrap_or_default(); let value = stake_amount.value; - let eligibility = stake_amount.eligibility; - Stake::new(value, stake.reward, eligibility, stake.nonce) + Stake::new(value, stake_amount.eligibility) } } diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index 031d3575f9..1a889cf817 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -179,7 +179,7 @@ pub fn generator_procedure( let verify_output = rusk.verify_state_transition(&block, &voters)?; info!("verify_state_transition new verification: {verify_output}",); - let (accept_txs, accept_output) = rusk.accept(&block, &voters)?; + let (accept_txs, accept_output, _) = rusk.accept(&block, &voters)?; assert_eq!(accept_txs.len(), expected.executed, "all txs accepted"); @@ -285,7 +285,7 @@ pub fn generator_procedure2( let verify_output = rusk.verify_state_transition(&block, &voters)?; info!("verify_state_transition new verification: {verify_output}",); - let (accept_txs, accept_output) = rusk.accept(&block, &voters)?; + let (accept_txs, accept_output, _) = rusk.accept(&block, &voters)?; assert_eq!(accept_txs.len(), expected.executed, "all txs accepted");