Skip to content

Commit

Permalink
rusk: support Moonlight transactions
Browse files Browse the repository at this point in the history
To support Moonlight transactions, we leverage the changes made to
`execution-core` to unify the handling of both transaction models using
the exact same code path as before.
  • Loading branch information
Eduardo Leegwater Simões committed Jul 20, 2024
1 parent 5a4beeb commit 20a4a55
Show file tree
Hide file tree
Showing 27 changed files with 429 additions and 289 deletions.
27 changes: 18 additions & 9 deletions rusk/benches/block_ingestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use criterion::{
criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion,
};
use execution_core::{
transfer::Transaction as PhoenixTransaction, StakePublicKey, StakeSecretKey,
transfer::Transaction as ProtocolTransaction, BlsPublicKey, BlsSecretKey,
};
use node_data::ledger::Transaction;
use rand::prelude::StdRng;
Expand All @@ -37,7 +37,7 @@ fn load_txs() -> Vec<Transaction> {
for line in BufReader::new(TXS_BYTES).lines() {
let line = line.unwrap();
let tx_bytes = hex::decode(line).unwrap();
let tx = PhoenixTransaction::from_slice(&tx_bytes).unwrap();
let tx = ProtocolTransaction::from_slice(&tx_bytes).unwrap();
txs.push(Transaction {
version: 1,
r#type: 0,
Expand All @@ -46,12 +46,21 @@ fn load_txs() -> Vec<Transaction> {
}

for tx in txs.iter() {
match rusk::verifier::verify_proof(&tx.inner) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow::anyhow!("Invalid proof")),
Err(e) => Err(anyhow::anyhow!("Cannot verify the proof: {e}")),
match &tx.inner {
ProtocolTransaction::Phoenix(tx) => {
match rusk::verifier::verify_proof(tx) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow::anyhow!("Invalid proof")),
Err(e) => {
Err(anyhow::anyhow!("Cannot verify the proof: {e}"))
}
}
.unwrap()
}
ProtocolTransaction::Moonlight(_) => {
panic!("All transactions must be phoenix")
}
}
.unwrap()
}

txs
Expand Down Expand Up @@ -80,8 +89,8 @@ pub fn accept_benchmark(c: &mut Criterion) {

let generator = {
let mut rng = StdRng::seed_from_u64(0xbeef);
let sk = StakeSecretKey::random(&mut rng);
StakePublicKey::from(&sk)
let sk = BlsSecretKey::random(&mut rng);
BlsPublicKey::from(&sk)
};

const BLOCK_HEIGHT: u64 = 1;
Expand Down
61 changes: 32 additions & 29 deletions rusk/src/lib/chain/rusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ use dusk_consensus::operations::{
use execution_core::bytecode::Bytecode;
use execution_core::transfer::ContractDeploy;
use execution_core::{
stake::StakeData, transfer::Transaction as PhoenixTransaction, BlsScalar,
StakePublicKey,
stake::StakeData,
transfer::{AccountData, Transaction as ProtocolTransaction},
BlsPublicKey, BlsScalar,
};
use node_data::ledger::{Slash, SpentTransaction, Transaction};
use rusk_abi::dusk::Dusk;
Expand All @@ -44,9 +45,9 @@ use crate::http::RuesEvent;
use crate::Error::InvalidCreditsCount;
use crate::{Error, Result};

pub static DUSK_KEY: LazyLock<StakePublicKey> = LazyLock::new(|| {
pub static DUSK_KEY: LazyLock<BlsPublicKey> = LazyLock::new(|| {
let dusk_cpk_bytes = include_bytes!("../../assets/dusk.cpk");
StakePublicKey::from_slice(dusk_cpk_bytes)
BlsPublicKey::from_slice(dusk_cpk_bytes)
.expect("Dusk consensus public key to be valid")
});

Expand Down Expand Up @@ -129,7 +130,7 @@ impl Rusk {
}
}
let tx_id = hex::encode(unspent_tx.id());
if unspent_tx.inner.payload().fee.gas_limit > block_gas_left {
if unspent_tx.inner.gas_limit() > block_gas_left {
info!("Skipping {tx_id} due gas_limit greater than left: {block_gas_left}");
continue;
}
Expand Down Expand Up @@ -169,7 +170,7 @@ impl Rusk {
update_hasher(&mut event_hasher, &receipt.events);

block_gas_left -= gas_spent;
let gas_price = unspent_tx.inner.payload().fee.gas_price;
let gas_price = unspent_tx.inner.gas_price();
dusk_spent += gas_spent * gas_price;
spent_txs.push(SpentTransaction {
inner: unspent_tx,
Expand Down Expand Up @@ -215,7 +216,7 @@ impl Rusk {
&self,
block_height: u64,
block_gas_limit: u64,
generator: &StakePublicKey,
generator: &BlsPublicKey,
txs: &[Transaction],
slashing: Vec<Slash>,
voters: Option<&[VoterWithCredits]>,
Expand Down Expand Up @@ -245,7 +246,7 @@ impl Rusk {
&self,
block_height: u64,
block_gas_limit: u64,
generator: StakePublicKey,
generator: BlsPublicKey,
txs: Vec<Transaction>,
consistency_check: Option<VerificationOutput>,
slashing: Vec<Slash>,
Expand Down Expand Up @@ -332,16 +333,21 @@ impl Rusk {
pub fn provisioners(
&self,
base_commit: Option<[u8; 32]>,
) -> Result<impl Iterator<Item = (StakePublicKey, StakeData)>> {
) -> Result<impl Iterator<Item = (BlsPublicKey, StakeData)>> {
let (sender, receiver) = mpsc::channel();
self.feeder_query(STAKE_CONTRACT, "stakes", &(), sender, base_commit)?;
Ok(receiver.into_iter().map(|bytes| {
rkyv::from_bytes::<(StakePublicKey, StakeData)>(&bytes).expect(
rkyv::from_bytes::<(BlsPublicKey, StakeData)>(&bytes).expect(
"The contract should only return (pk, stake_data) tuples",
)
}))
}

/// Returns an account's information.
pub fn account(&self, pk: &BlsPublicKey) -> Result<AccountData> {
self.query(TRANSFER_CONTRACT, "account", pk)
}

/// Fetches the previous state data for stake changes in the contract.
///
/// Communicates with the stake contract to obtain information about the
Expand All @@ -356,12 +362,12 @@ impl Rusk {
/// # Returns
///
/// Returns a Result containing an iterator over tuples. Each tuple consists
/// of a `StakePublicKey` and an optional `StakeData`, representing the
/// of a `BlsPublicKey` and an optional `StakeData`, representing the
/// state data before the last changes in the stake contract.
pub fn last_provisioners_change(
&self,
base_commit: Option<[u8; 32]>,
) -> Result<Vec<(StakePublicKey, Option<StakeData>)>> {
) -> Result<Vec<(BlsPublicKey, Option<StakeData>)>> {
let (sender, receiver) = mpsc::channel();
self.feeder_query(
STAKE_CONTRACT,
Expand All @@ -371,16 +377,13 @@ impl Rusk {
base_commit,
)?;
Ok(receiver.into_iter().map(|bytes| {
rkyv::from_bytes::<(StakePublicKey, Option<StakeData>)>(&bytes).expect(
rkyv::from_bytes::<(BlsPublicKey, Option<StakeData>)>(&bytes).expect(
"The contract should only return (pk, Option<stake_data>) tuples",
)
}).collect())
}

pub fn provisioner(
&self,
pk: &StakePublicKey,
) -> Result<Option<StakeData>> {
pub fn provisioner(&self, pk: &BlsPublicKey) -> Result<Option<StakeData>> {
self.query(STAKE_CONTRACT, "get_stake", pk)
}

Expand Down Expand Up @@ -433,7 +436,7 @@ fn accept(
session: Session,
block_height: u64,
block_gas_limit: u64,
generator: &StakePublicKey,
generator: &BlsPublicKey,
txs: &[Transaction],
slashing: Vec<Slash>,
voters: Option<&[VoterWithCredits]>,
Expand Down Expand Up @@ -463,7 +466,7 @@ fn accept(

let gas_spent = receipt.gas_spent;

dusk_spent += gas_spent * tx.payload().fee.gas_price;
dusk_spent += gas_spent * tx.gas_price();
block_gas_left = block_gas_left
.checked_sub(gas_spent)
.ok_or(Error::OutOfGas)?;
Expand Down Expand Up @@ -588,15 +591,15 @@ fn contract_deploy(
/// failed to fit the block.
fn execute(
session: &mut Session,
tx: &PhoenixTransaction,
tx: &ProtocolTransaction,
gas_per_deploy_byte: Option<u64>,
) -> Result<CallReceipt<Result<Vec<u8>, ContractError>>, PiecrustError> {
// Transaction will be discarded if it is a deployment transaction
// with gas limit smaller than deploy charge.
if let Some(deploy) = tx.payload().contract_deploy() {
if let Some(deploy) = tx.deploy() {
let deploy_charge =
bytecode_charge(&deploy.bytecode, &gas_per_deploy_byte);
if tx.payload().fee.gas_limit < deploy_charge {
if tx.gas_limit() < deploy_charge {
return Err(PiecrustError::Panic(
"not enough gas to deploy".into(),
));
Expand All @@ -610,16 +613,16 @@ fn execute(
TRANSFER_CONTRACT,
"spend_and_execute",
tx_stripped.as_ref().unwrap_or(tx),
tx.payload().fee.gas_limit,
tx.gas_limit(),
)?;

// Deploy if this is a deployment transaction and spend part is successful.
if let Some(deploy) = tx.payload().contract_deploy() {
if let Some(deploy) = tx.deploy() {
if receipt.data.is_ok() {
contract_deploy(
session,
deploy,
tx.payload().fee.gas_limit,
tx.gas_limit(),
gas_per_deploy_byte,
&mut receipt,
);
Expand All @@ -638,7 +641,7 @@ fn execute(
.call::<_, ()>(
TRANSFER_CONTRACT,
"refund",
&(tx.payload().fee, receipt.gas_spent),
&receipt.gas_spent,
u64::MAX,
)
.expect("Refunding must succeed");
Expand All @@ -660,9 +663,9 @@ fn reward_slash_and_update_root(
session: &mut Session,
block_height: u64,
dusk_spent: Dusk,
generator: &StakePublicKey,
generator: &BlsPublicKey,
slashing: Vec<Slash>,
voters: Option<&[(StakePublicKey, usize)]>,
voters: Option<&[(BlsPublicKey, usize)]>,
) -> Result<Vec<Event>> {
let (
dusk_value,
Expand Down Expand Up @@ -768,7 +771,7 @@ fn calc_generator_extra_reward(
credits.saturating_sub(sum as u64) * reward_per_quota
}

fn to_bs58(pk: &StakePublicKey) -> String {
fn to_bs58(pk: &BlsPublicKey) -> String {
let mut pk = bs58::encode(&pk.to_bytes()).into_string();
pk.truncate(16);
pk
Expand Down
92 changes: 71 additions & 21 deletions rusk/src/lib/chain/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use dusk_consensus::operations::{
};
use dusk_consensus::user::provisioners::Provisioners;
use dusk_consensus::user::stake::Stake;
use execution_core::{stake::StakeData, StakePublicKey};
use execution_core::{
stake::StakeData, transfer::Transaction as ProtocolTransaction,
BlsPublicKey,
};
use node::vm::VMExecution;
use node_data::ledger::{Block, Slash, SpentTransaction, Transaction};

Expand Down Expand Up @@ -47,7 +50,7 @@ impl VMExecution for Rusk {
) -> anyhow::Result<VerificationOutput> {
info!("Received verify_state_transition request");
let generator = blk.header().generator_bls_pubkey;
let generator = StakePublicKey::from_slice(&generator.0)
let generator = BlsPublicKey::from_slice(&generator.0)
.map_err(|e| anyhow::anyhow!("Error in from_slice {e:?}"))?;

let slashing = Slash::from_block(blk)?;
Expand All @@ -73,7 +76,7 @@ impl VMExecution for Rusk {
) -> anyhow::Result<(Vec<SpentTransaction>, VerificationOutput)> {
info!("Received accept request");
let generator = blk.header().generator_bls_pubkey;
let generator = StakePublicKey::from_slice(&generator.0)
let generator = BlsPublicKey::from_slice(&generator.0)
.map_err(|e| anyhow::anyhow!("Error in from_slice {e:?}"))?;

let slashing = Slash::from_block(blk)?;
Expand Down Expand Up @@ -109,18 +112,64 @@ impl VMExecution for Rusk {
fn preverify(&self, tx: &Transaction) -> anyhow::Result<()> {
info!("Received preverify request");
let tx = &tx.inner;
let existing_nullifiers = self
.existing_nullifiers(&tx.payload().tx_skeleton.nullifiers)
.map_err(|e| anyhow::anyhow!("Cannot check nullifiers: {e}"))?;

if !existing_nullifiers.is_empty() {
let err = crate::Error::RepeatingNullifiers(existing_nullifiers);
return Err(anyhow::anyhow!("Invalid tx: {err}"));
}
match crate::verifier::verify_proof(tx) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow::anyhow!("Invalid proof")),
Err(e) => Err(anyhow::anyhow!("Cannot verify the proof: {e}")),
match tx {
ProtocolTransaction::Phoenix(tx) => {
let payload = tx.payload();

let existing_nullifiers = self
.existing_nullifiers(&payload.tx_skeleton.nullifiers)
.map_err(|e| {
anyhow::anyhow!("Cannot check nullifiers: {e}")
})?;

if !existing_nullifiers.is_empty() {
let err =
crate::Error::RepeatingNullifiers(existing_nullifiers);
return Err(anyhow::anyhow!("Invalid tx: {err}"));
}

match crate::verifier::verify_proof(tx) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow::anyhow!("Invalid proof")),
Err(e) => {
Err(anyhow::anyhow!("Cannot verify the proof: {e}"))
}
}
}
ProtocolTransaction::Moonlight(tx) => {
let payload = tx.payload();

let account_data =
self.account(&payload.from).map_err(|e| {
anyhow::anyhow!("Cannot check account: {e}")
})?;

let max_value = payload.value
+ payload.deposit
+ payload.gas_limit * payload.gas_price;
if max_value > account_data.balance {
return Err(anyhow::anyhow!(
"Value spent larger than account holds"
));
}

if payload.nonce <= account_data.nonce {
let err = crate::Error::RepeatingNonce(
payload.from.into(),
payload.nonce,
);
return Err(anyhow::anyhow!("Invalid tx: {err}"));
}

match crate::verifier::verify_signature(tx) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow::anyhow!("Invalid signature")),
Err(e) => {
Err(anyhow::anyhow!("Cannot verify the signature: {e}"))
}
}
}
}
}

Expand All @@ -140,15 +189,12 @@ impl VMExecution for Rusk {

fn get_provisioner(
&self,
pk: &StakePublicKey,
pk: &BlsPublicKey,
) -> anyhow::Result<Option<Stake>> {
let stake = self
.provisioner(pk)
.map_err(|e| anyhow::anyhow!("Cannot get provisioner {e}"))?
.map(|stake| {
let (value, eligibility) = stake.amount.unwrap_or_default();
Stake::new(value, stake.reward, eligibility, stake.counter)
});
.map(Self::to_stake);
Ok(stake)
}

Expand Down Expand Up @@ -218,7 +264,11 @@ impl Rusk {
}

fn to_stake(stake: StakeData) -> Stake {
let (value, eligibility) = stake.amount.unwrap_or_default();
Stake::new(value, stake.reward, eligibility, stake.counter)
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)
}
}
Loading

0 comments on commit 20a4a55

Please sign in to comment.