From c25a77382b9c808eff295c341831b878da77d933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:07:51 +0200 Subject: [PATCH 01/12] execution-core: add Moonlight and rework withdrawals With this commit we add `MoonlightTransaction`, and rename the existing transaction to `PhoenixTransaction`, both of which are incorporated into an enum named `Transaction`. We also rework the withdrawals system, under the assumption that there must be a strong assurance that the transacting party own the addresses being withdraw to. This enables cross-withdrawals from Moonlight to Phoenix (and vice-versa), as well as withdrawals to the same transaction model. The liberty is taken to change the stake data structures slighty, to improve on clarity, as well as make use of the new withdrawal system. --- execution-core/CHANGELOG.md | 38 +- execution-core/src/lib.rs | 14 +- execution-core/src/stake.rs | 401 ++++++++++------ execution-core/src/transfer.rs | 313 ++++++++++--- execution-core/src/transfer/transaction.rs | 428 +++++++----------- .../src/transfer/transaction/moonlight.rs | 321 +++++++++++++ .../src/transfer/transaction/phoenix.rs | 358 +++++++++++++++ execution-core/tests/serialization.rs | 226 ++++++--- 8 files changed, 1555 insertions(+), 544 deletions(-) create mode 100644 execution-core/src/transfer/transaction/moonlight.rs create mode 100644 execution-core/src/transfer/transaction/phoenix.rs diff --git a/execution-core/CHANGELOG.md b/execution-core/CHANGELOG.md index 34ea70bbda..98f37f0d07 100644 --- a/execution-core/CHANGELOG.md +++ b/execution-core/CHANGELOG.md @@ -12,7 +12,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved deserialization [#1963] - Change payload to support contract deployment [#1882] - - Re-export - `dusk-bls12_381::BlsScalar` - `dusk-jubjub::{ @@ -49,38 +48,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 OUTPUT_NOTES }` - Add type-alias: - - `pub type StakeSecretKey = BlsSecretKey` - - `pub type StakePublicKey = BlsPublicKey` - - `pub type StakeSignature = BlsSignature` - - `pub type StakeAggPublicKey = BlsAggPublicKey` - - `pub type NoteSecretKey = SchnorrSecretKey` - `pub type NotePublicKey = SchnorrPublicKey` + - `pub type NoteSecretKey = SchnorrSecretKey` - `pub type NoteSignature = SchnorrSignature` - Add modules, types and functionality: - `transfer::{ - ContractId, - TRANSFER_TREE_DEPTH, - TreeLeaf, - Mint, + AccountData, ContractCall, Fee, - SenderAccount, - Payload, + MoonlightPayload, + MoonlightTransaction, + PhoenixPayload, + PhoenixTransaction, + TreeLeaf, + Withdraw, + WithdrawReceiver, + WithdrawSignature, + WithdrawSecretKey, + WithdrawReplayToken, Transaction, + TRANSFER_TREE_DEPTH, + ContractId, }` - `stake::{ + Stake, + StakeAmount, + StakeData, + StakeEvent, + Withdraw, EPOCH, STAKE_WARNINGS, next_epoch, - Stake, - Unstake, - Withdraw, - StakingEvent, - StakeData, }` [#1963]: https://github.com/dusk-network/rusk/issues/1963 +[#1963]: https://github.com/dusk-network/rusk/issues/1856 [#1882]: https://github.com/dusk-network/rusk/issues/1882 +[#1723]: https://github.com/dusk-network/rusk/issues/1723 [Unreleased]: https://github.com/dusk-network/rusk/compare/execution-core-0.1.0...HEAD [0.1.0]: https://github.com/dusk-network/dusk-abi/releases/tag/execution-core-0.1.0 diff --git a/execution-core/src/lib.rs b/execution-core/src/lib.rs index be412bf863..3f7ba649a6 100644 --- a/execution-core/src/lib.rs +++ b/execution-core/src/lib.rs @@ -9,9 +9,9 @@ #![no_std] #![deny(missing_docs)] #![deny(clippy::pedantic)] +#![allow(clippy::module_name_repetitions)] -/// Block height type alias -pub type BlockHeight = u64; +extern crate alloc; pub mod bytecode; pub mod reader; @@ -31,19 +31,11 @@ pub use bls12_381_bls::{ Signature as BlsSignature, APK as BlsAggPublicKey, }; -/// Secret key associated to a stake. -pub type StakeSecretKey = BlsSecretKey; -/// Public key associated to a stake. -pub type StakePublicKey = BlsPublicKey; -/// Signature associated with a stake. -pub type StakeSignature = BlsSignature; -/// Aggregated public key for multisignatures -pub type StakeAggPublicKey = BlsAggPublicKey; - pub use jubjub_schnorr::{ PublicKey as SchnorrPublicKey, SecretKey as SchnorrSecretKey, Signature as SchnorrSignature, SignatureDouble as SchnorrSignatureDouble, }; + /// Secret key associated with a note. pub type NoteSecretKey = SchnorrSecretKey; /// Public key associated with a note. diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs index 7cca502988..66eea2c04b 100644 --- a/execution-core/src/stake.rs +++ b/execution-core/src/stake.rs @@ -6,14 +6,15 @@ //! Types used by Dusk's stake contract. -extern crate alloc; +use alloc::vec::Vec; use bytecheck::CheckBytes; -use dusk_bytes::Serializable; +use dusk_bytes::{DeserializableSlice, Serializable, Write}; use rkyv::{Archive, Deserialize, Serialize}; use crate::{ - BlockHeight, BlsScalar, StakePublicKey, StakeSignature, StealthAddress, + transfer::{Withdraw as TransferWithdraw, WithdrawReceiver}, + BlsPublicKey, BlsSecretKey, BlsSignature, }; /// Epoch used for stake operations @@ -24,107 +25,148 @@ pub const STAKE_WARNINGS: u8 = 1; /// Calculate the block height at which the next epoch takes effect. #[must_use] -pub const fn next_epoch(block_height: BlockHeight) -> u64 { +pub const fn next_epoch(block_height: u64) -> u64 { let to_next_epoch = EPOCH - (block_height % EPOCH); block_height + to_next_epoch } /// Stake a value on the stake contract. #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] -#[archive_attr(derive(bytecheck::CheckBytes))] +#[archive_attr(derive(CheckBytes))] pub struct Stake { - /// Public key to which the stake will belong. - pub public_key: StakePublicKey, - /// Signature belonging to the given public key. - pub signature: StakeSignature, - /// Value to stake. - pub value: u64, + account: BlsPublicKey, + value: u64, + nonce: u64, + signature: BlsSignature, } impl Stake { - const MESSAGE_SIZE: usize = u64::SIZE + u64::SIZE; - /// Return the digest to be signed in the `stake` function of the stake - /// contract. + const MESSAGE_SIZE: usize = BlsPublicKey::SIZE + u64::SIZE + u64::SIZE; + + /// Create a new stake. #[must_use] - pub fn signature_message( - counter: u64, - value: u64, - ) -> [u8; Self::MESSAGE_SIZE] { - let mut bytes = [0u8; Self::MESSAGE_SIZE]; + pub fn new(sk: &BlsSecretKey, value: u64, nonce: u64) -> Self { + let account = BlsPublicKey::from(sk); + + let mut stake = Stake { + account, + value, + nonce, + signature: BlsSignature::default(), + }; - bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); - bytes[u64::SIZE..].copy_from_slice(&value.to_bytes()); + let msg = stake.signature_message(); + stake.signature = sk.sign(&account, &msg); - bytes + stake } -} -/// Unstake a value from the stake contract. -#[derive(Debug, Clone, PartialEq, Archive, Deserialize, Serialize)] -#[archive_attr(derive(CheckBytes))] -pub struct Unstake { - /// Public key to unstake. - pub public_key: StakePublicKey, - /// Signature belonging to the given public key. - pub signature: StakeSignature, - /// The address to mint to. - pub address: StealthAddress, -} + /// Account to which the stake will belong. + #[must_use] + pub fn account(&self) -> &BlsPublicKey { + &self.account + } -impl Unstake { - const MESSAGE_SIZE: usize = u64::SIZE + u64::SIZE + StealthAddress::SIZE; - /// Signature message used for [`Unstake`]. + /// Value to stake. #[must_use] - pub fn signature_message( - counter: u64, - value: u64, - address: StealthAddress, - ) -> [u8; Self::MESSAGE_SIZE] { + pub fn value(&self) -> u64 { + self.value + } + + /// Nonce used for replay protection. Nonces are strictly increasing, + /// meaning that once a transaction has been settled, only a higher + /// nonce can be used. + /// + /// The current nonce is queryable via the stake contract in the form of + /// [`StakeData`] and best practice is to use `nonce + 1` for a single + /// transaction. + #[must_use] + pub fn nonce(&self) -> u64 { + self.nonce + } + + /// Signature of the stake. + #[must_use] + pub fn signature(&self) -> &BlsSignature { + &self.signature + } + + /// Return the message that is used as the input to the signature. + #[must_use] + pub fn signature_message(&self) -> [u8; Self::MESSAGE_SIZE] { let mut bytes = [0u8; Self::MESSAGE_SIZE]; - bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); - bytes[u64::SIZE..u64::SIZE + u64::SIZE] - .copy_from_slice(&value.to_bytes()); - bytes[u64::SIZE + u64::SIZE - ..u64::SIZE + u64::SIZE + StealthAddress::SIZE] - .copy_from_slice(&address.to_bytes()); + let mut offset = 0; + + bytes[offset..offset + BlsPublicKey::SIZE] + .copy_from_slice(&self.account.to_bytes()); + offset += BlsPublicKey::SIZE; + + bytes[offset..offset + u64::SIZE] + .copy_from_slice(&self.value.to_bytes()); + offset += u64::SIZE; + + bytes[offset..offset + u64::SIZE] + .copy_from_slice(&self.nonce.to_bytes()); bytes } } -/// Withdraw the accumulated reward. -#[derive(Debug, Clone, Archive, Deserialize, Serialize)] +/// Withdraw some value from the stake contract. +/// +/// This is used in both `unstake` and `withdraw`. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct Withdraw { - /// Public key to withdraw the rewards. - pub public_key: StakePublicKey, - /// Signature belonging to the given public key. - pub signature: StakeSignature, - /// The address to mint to. - pub address: StealthAddress, - /// A nonce to prevent replay. - pub nonce: BlsScalar, + account: BlsPublicKey, + withdraw: TransferWithdraw, + signature: BlsSignature, } impl Withdraw { - const MESSAGE_SIZE: usize = - u64::SIZE + StealthAddress::SIZE + BlsScalar::SIZE; + /// Create a new withdraw call. + #[must_use] + pub fn new(sk: &BlsSecretKey, withdraw: TransferWithdraw) -> Self { + let account = BlsPublicKey::from(sk); + + let mut stake_withdraw = Withdraw { + account, + withdraw, + signature: BlsSignature::default(), + }; + + let msg = stake_withdraw.signature_message(); + stake_withdraw.signature = sk.sign(&account, &msg); + + stake_withdraw + } + + /// The public key to withdraw from. + #[must_use] + pub fn account(&self) -> &BlsPublicKey { + &self.account + } + + /// The inner withdrawal to pass to the transfer contract. + #[must_use] + pub fn transfer_withdraw(&self) -> &TransferWithdraw { + &self.withdraw + } + + /// Signature of the withdraw. + #[must_use] + pub fn signature(&self) -> &BlsSignature { + &self.signature + } /// Signature message used for [`Withdraw`]. #[must_use] - pub fn signature_message( - counter: u64, - address: StealthAddress, - nonce: BlsScalar, - ) -> [u8; Self::MESSAGE_SIZE] { - let mut bytes = [0u8; Self::MESSAGE_SIZE]; + pub fn signature_message(&self) -> Vec { + let mut bytes = Vec::new(); - bytes[..u64::SIZE].copy_from_slice(&counter.to_bytes()); - bytes[u64::SIZE..u64::SIZE + StealthAddress::SIZE] - .copy_from_slice(&address.to_bytes()); - bytes[u64::SIZE + StealthAddress::SIZE..] - .copy_from_slice(&nonce.to_bytes()); + bytes.extend(self.account.to_bytes()); + bytes.extend(self.withdraw.wrapped_signature_message()); bytes } @@ -133,12 +175,15 @@ impl Withdraw { /// Event emitted after a stake contract operation is performed. #[derive(Debug, Clone, Archive, Deserialize, Serialize)] #[archive_attr(derive(CheckBytes))] -pub struct StakingEvent { - /// Public key which is relevant to the event. - pub public_key: StakePublicKey, - /// Value of the relevant operation, be it stake, unstake, withdrawal, - /// reward, or slash. +pub struct StakeEvent { + /// Account associated to the event. + pub account: BlsPublicKey, + /// Value of the relevant operation, be it `stake`, `unstake`, `withdraw`, + /// `reward`, or `slash`. pub value: u64, + /// The receiver of the action, relevant in `withdraw` and `unstake` + /// operations. + pub receiver: Option, } /// The representation of a public key's stake. @@ -153,17 +198,21 @@ pub struct StakingEvent { /// signature could be used to prove ownership of the secret key in two /// different transactions. #[derive( - Debug, Default, Clone, PartialEq, Eq, Archive, Deserialize, Serialize, + Debug, Default, Clone, Copy, PartialEq, Eq, Archive, Deserialize, Serialize, )] #[archive_attr(derive(CheckBytes))] -#[allow(clippy::module_name_repetitions)] pub struct StakeData { /// Amount staked and eligibility. - pub amount: Option<(u64, BlockHeight)>, + pub amount: Option, /// The reward for participating in consensus. pub reward: u64, - /// The signature counter to prevent replay. - pub counter: u64, + /// Nonce used for replay protection. Nonces are strictly increasing, + /// meaning that once a transaction has been settled, only a higher + /// nonce can be used. + /// + /// The current nonce is queryable via the stake contract and best + /// practice is to use `nonce + 1` for a single transaction. + pub nonce: u64, /// Faults pub faults: u8, /// Hard Faults @@ -171,14 +220,19 @@ pub struct StakeData { } impl StakeData { + /// An empty stake. + pub const EMPTY: Self = Self { + amount: None, + reward: 0, + nonce: 0, + faults: 0, + hard_faults: 0, + }; + /// Create a new stake given its initial `value` and `reward`, together with /// the `block_height` of its creation. #[must_use] - pub const fn new( - value: u64, - reward: u64, - block_height: BlockHeight, - ) -> Self { + pub const fn new(value: u64, reward: u64, block_height: u64) -> Self { let eligibility = Self::eligibility_from_height(block_height); Self::with_eligibility(value, reward, eligibility) } @@ -189,92 +243,165 @@ impl StakeData { pub const fn with_eligibility( value: u64, reward: u64, - eligibility: BlockHeight, + eligibility: u64, ) -> Self { let amount = match value { 0 => None, - _ => Some((value, eligibility)), + _ => Some(StakeAmount { value, eligibility }), }; Self { amount, reward, - counter: 0, + nonce: 0, faults: 0, hard_faults: 0, } } - /// Returns the value the user is staking, together with its eligibility. + /// Returns true if the stake is valid - meaning there is an `amount` staked + /// and the given `block_height` is larger or equal to the stake's + /// eligibility. If there is no `amount` staked this is false. #[must_use] - pub const fn amount(&self) -> Option<&(u64, BlockHeight)> { - self.amount.as_ref() + pub fn is_valid(&self, block_height: u64) -> bool { + match &self.amount { + Some(amount) => block_height >= amount.eligibility, + None => false, + } } - /// Returns the value of the reward. + /// Compute the eligibility of a stake from the starting block height. #[must_use] - pub const fn reward(&self) -> u64 { - self.reward + pub const fn eligibility_from_height(block_height: u64) -> u64 { + StakeAmount::eligibility_from_height(block_height) } +} - /// Returns the interaction count of the stake. - #[must_use] - pub const fn counter(&self) -> u64 { - self.counter - } +const STAKE_DATA_SIZE: usize = + u8::SIZE + StakeAmount::SIZE + u64::SIZE + u64::SIZE + u8::SIZE + u8::SIZE; + +impl Serializable for StakeData { + type Error = dusk_bytes::Error; + + fn from_bytes(buf: &[u8; Self::SIZE]) -> Result + where + Self: Sized, + { + let mut buf = &buf[..]; + + // if the tag is zero we skip the bytes + let tag = u8::from_reader(&mut buf)?; + let amount = match tag { + 0 => { + buf = &buf[..StakeAmount::SIZE]; + None + } + _ => Some(StakeAmount::from_reader(&mut buf)?), + }; - /// Insert a stake [`amount`] with a particular `value`, starting from a - /// particular `block_height`. - /// - /// # Panics - /// If the value is zero or the stake already contains an amount. - pub fn insert_amount(&mut self, value: u64, block_height: BlockHeight) { - assert_ne!(value, 0, "A stake can't have zero value"); - assert!(self.amount.is_none(), "Can't stake twice for the same key!"); + let reward = u64::from_reader(&mut buf)?; + let nonce = u64::from_reader(&mut buf)?; - let eligibility = Self::eligibility_from_height(block_height); - self.amount = Some((value, eligibility)); - } + let faults = u8::from_reader(&mut buf)?; + let hard_faults = u8::from_reader(&mut buf)?; - /// Increases the held reward by the given `value`. - pub fn increase_reward(&mut self, value: u64) { - self.reward += value; + Ok(Self { + amount, + reward, + nonce, + faults, + hard_faults, + }) } - /// Removes the total [`amount`] staked. - /// - /// # Panics - /// If the stake has no amount. - pub fn remove_amount(&mut self) -> (u64, BlockHeight) { - self.amount - .take() - .expect("Can't withdraw non-existing amount!") - } + #[allow(unused_must_use)] + fn to_bytes(&self) -> [u8; Self::SIZE] { + const ZERO_AMOUNT: [u8; StakeAmount::SIZE] = [0u8; StakeAmount::SIZE]; + + let mut buf = [0u8; Self::SIZE]; + let mut writer = &mut buf[..]; + + match &self.amount { + None => { + writer.write(&0u8.to_bytes()); + writer.write(&ZERO_AMOUNT); + } + Some(amount) => { + writer.write(&1u8.to_bytes()); + writer.write(&amount.to_bytes()); + } + } + + writer.write(&self.reward.to_bytes()); + writer.write(&self.nonce.to_bytes()); - /// Sets the reward to zero. - pub fn deplete_reward(&mut self) { - self.reward = 0; + writer.write(&self.faults.to_bytes()); + writer.write(&self.hard_faults.to_bytes()); + + buf } +} - /// Increment the interaction [`counter`]. - pub fn increment_counter(&mut self) { - self.counter += 1; +/// Value staked and eligibility. +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, Archive, Deserialize, Serialize, +)] +#[archive_attr(derive(CheckBytes))] +pub struct StakeAmount { + /// The value staked. + pub value: u64, + /// The eligibility of the stake. + pub eligibility: u64, +} + +impl StakeAmount { + /// Create a new stake amount. + #[must_use] + pub const fn new(value: u64, block_height: u64) -> Self { + let eligibility = Self::eligibility_from_height(block_height); + Self::with_eligibility(value, eligibility) } - /// Returns true if the stake is valid - meaning there is an amount staked - /// and the given `block_height` is larger or equal to the stake's - /// eligibility. If there is no `amount` staked this is false. + /// Create a new stake given its initial `value` and `reward`, together with + /// the `eligibility`. #[must_use] - pub fn is_valid(&self, block_height: BlockHeight) -> bool { - self.amount - .map(|(_, eligibility)| block_height >= eligibility) - .unwrap_or_default() + pub const fn with_eligibility(value: u64, eligibility: u64) -> Self { + Self { value, eligibility } } /// Compute the eligibility of a stake from the starting block height. #[must_use] - pub const fn eligibility_from_height(block_height: BlockHeight) -> u64 { + pub const fn eligibility_from_height(block_height: u64) -> u64 { let maturity_blocks = EPOCH; next_epoch(block_height) + maturity_blocks } } + +const STAKE_AMOUNT_SIZE: usize = u64::SIZE + u64::SIZE; + +impl Serializable for StakeAmount { + type Error = dusk_bytes::Error; + + fn from_bytes(buf: &[u8; Self::SIZE]) -> Result + where + Self: Sized, + { + let mut buf = &buf[..]; + + let value = u64::from_reader(&mut buf)?; + let eligibility = u64::from_reader(&mut buf)?; + + Ok(Self { value, eligibility }) + } + + #[allow(unused_must_use)] + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + let mut writer = &mut buf[..]; + + writer.write(&self.value.to_bytes()); + writer.write(&self.eligibility.to_bytes()); + + buf + } +} diff --git a/execution-core/src/transfer.rs b/execution-core/src/transfer.rs index 2b1dd39a2d..db021f408c 100644 --- a/execution-core/src/transfer.rs +++ b/execution-core/src/transfer.rs @@ -7,7 +7,6 @@ //! Types related to Dusk's transfer contract that are shared across the //! network. -extern crate alloc; use alloc::string::String; use alloc::vec::Vec; @@ -24,12 +23,15 @@ use rkyv::{ }; use crate::{ - BlsPublicKey, BlsScalar, JubJubAffine, JubJubScalar, Note, PublicKey, - Sender, StealthAddress, + BlsPublicKey, BlsScalar, BlsSecretKey, BlsSignature, JubJubScalar, Note, + PublicKey, SchnorrSecretKey, SchnorrSignature, Sender, StealthAddress, }; mod transaction; -pub use transaction::{Payload, Transaction}; +pub use transaction::{ + MoonlightPayload, MoonlightTransaction, PhoenixPayload, PhoenixTransaction, + Transaction, +}; use crate::bytecode::Bytecode; use crate::reader::{read_arr, read_str, read_vec}; @@ -50,49 +52,247 @@ pub struct TreeLeaf { pub note: Note, } -/// Data to mint a new phoenix-note with a given value to a stealth address. -#[derive(Debug, Clone, Archive, Deserialize, Serialize)] +/// A Moonlight account's information. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] -pub struct Mint { - /// The address to mint to. - pub address: StealthAddress, - /// The value to mint to the address. - pub value: u64, - /// The account that sent the `mint` request. - pub sender: BlsPublicKey, +pub struct AccountData { + /// Number used for replay protection. + pub nonce: u64, + /// Account balance. + pub balance: u64, } -impl Serializable<{ StealthAddress::SIZE + u64::SIZE + BlsPublicKey::SIZE }> - for Mint -{ - type Error = BytesError; +/// Withdrawal information, proving the intent of a user to withdraw from a +/// contract. +/// +/// This structure is meant to be passed to a contract by a caller. The contract +/// is then responsible for calling `withdraw` in the transfer contract to +/// settle it, if it wants to allow the withdrawal. +/// +/// e.g. the stake contract uses it as a call argument for the `unstake` +/// function +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Withdraw { + contract: ContractId, + value: u64, + receiver: WithdrawReceiver, + token: WithdrawReplayToken, + signature: WithdrawSignature, +} - /// Converts a Fee into it's byte representation - fn to_bytes(&self) -> [u8; Self::SIZE] { - let mut buf = [0u8; Self::SIZE]; +impl Withdraw { + /// Create a new contract withdrawal. + /// + /// # Panics + /// When the receiver does not match the secret key passed. + #[must_use] + pub fn new<'a, R: RngCore + CryptoRng>( + rng: &mut R, + sk: impl Into>, + contract: ContractId, + value: u64, + receiver: WithdrawReceiver, + token: WithdrawReplayToken, + ) -> Self { + let mut withdraw = Self { + contract, + value, + receiver, + token, + signature: WithdrawSignature::Moonlight(BlsSignature::default()), + }; - buf[..StealthAddress::SIZE].copy_from_slice(&self.address.to_bytes()); - let mut start = StealthAddress::SIZE; - buf[start..start + u64::SIZE].copy_from_slice(&self.value.to_bytes()); - start += u64::SIZE; - buf[start..start + BlsPublicKey::SIZE] - .copy_from_slice(&self.sender.to_bytes()); - buf + let sk = sk.into(); + + match (&sk, &receiver) { + (WithdrawSecretKey::Phoenix(_), WithdrawReceiver::Moonlight(_)) => { + panic!("Moonlight receiver with phoenix signer"); + } + (WithdrawSecretKey::Moonlight(_), WithdrawReceiver::Phoenix(_)) => { + panic!("Phoenix receiver with moonlight signer"); + } + _ => {} + } + + let msg = withdraw.signature_message(); + + match sk { + WithdrawSecretKey::Phoenix(sk) => { + let digest = BlsScalar::hash_to_scalar(&msg); + let signature = sk.sign(rng, digest); + withdraw.signature = signature.into(); + } + WithdrawSecretKey::Moonlight(sk) => { + let pk = BlsPublicKey::from(sk); + let signature = sk.sign(&pk, &msg); + withdraw.signature = signature.into(); + } + } + + withdraw } - /// Attempts to convert a byte representation of a fee into a `Fee`, - /// failing if the input is invalid - fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result { - let mut buf = &bytes[..]; - let address = StealthAddress::from_reader(&mut buf)?; - let value = u64::from_reader(&mut buf)?; - let sender = BlsPublicKey::from_reader(&mut buf)?; + /// The contract to withraw from. + #[must_use] + pub fn contract(&self) -> &ContractId { + &self.contract + } - Ok(Mint { - address, - value, - sender, - }) + /// The amount to withdraw. + #[must_use] + pub fn value(&self) -> u64 { + self.value + } + + /// The receiver of the value. + #[must_use] + pub fn receiver(&self) -> &WithdrawReceiver { + &self.receiver + } + + /// The unique token to prevent replay. + #[must_use] + pub fn token(&self) -> &WithdrawReplayToken { + &self.token + } + + /// Signature of the withdrawal. + #[must_use] + pub fn signature(&self) -> &WithdrawSignature { + &self.signature + } + + /// Return the message that is used as the input to the signature. + /// + /// This message is *not* the one that is meant to be signed on making a + /// withdrawal. Instead it is meant to be used by structures wrapping + /// withdrawals to offer additional functionality. + /// + /// To see the signature used to sign a withdrawal, see + /// [`WithdrawPayload::signature_message`]. + #[must_use] + pub fn signature_message(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.contract); + bytes.extend(self.value.to_bytes()); + + match self.receiver { + WithdrawReceiver::Phoenix(address) => { + bytes.extend(address.to_bytes()); + } + WithdrawReceiver::Moonlight(account) => { + bytes.extend(account.to_bytes()); + } + } + + match &self.token { + WithdrawReplayToken::Phoenix(nullifiers) => { + for n in nullifiers { + bytes.extend(n.to_bytes()); + } + } + WithdrawReplayToken::Moonlight(nonce) => { + bytes.extend(nonce.to_bytes()); + } + } + + bytes + } + + /// Returns the message that should be "mixed in" as input for a signature + /// of an item that wraps a [`Withdraw`]. + /// + /// One example of this is [`stake::Withdraw`]. + #[must_use] + pub fn wrapped_signature_message(&self) -> Vec { + let mut bytes = self.signature_message(); + bytes.extend(self.signature.to_var_bytes()); + bytes + } +} + +/// The receiver of the [`Withdraw`] value. +#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum WithdrawReceiver { + /// The stealth address to withdraw to, when the withdrawal is into Phoenix + /// notes. + Phoenix(StealthAddress), + /// The account to withdraw to, when the withdrawal is to a Moonlight + /// account. + Moonlight(BlsPublicKey), +} + +/// The token used for replay protection in a [`Withdraw`]. This is the same as +/// the encapsulating transaction's fields. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum WithdrawReplayToken { + /// The nullifiers of the encapsulating Phoenix transaction, when the + /// transaction is paid for using Phoenix notes. + Phoenix(Vec), + /// The nonce of the encapsulating Moonlight transaction, when the + /// transaction is paid for using a Moonlight account. + Moonlight(u64), +} + +/// The secret key used for signing a [`Withdraw`]. +/// +/// When the withdrawal is into Phoenix notes, a [`SchnorrSecretKey`] should be +/// used. When the withdrawal is into a Moonlight account an +/// [`BlsSecretKey`] should be used. +#[derive(Debug, Clone, PartialEq)] +pub enum WithdrawSecretKey<'a> { + /// The secret key used to sign a withdrawal into Phoenix notes. + Phoenix(&'a SchnorrSecretKey), + /// The secret key used to sign a withdrawal into a Moonlight account. + Moonlight(&'a BlsSecretKey), +} + +impl<'a> From<&'a SchnorrSecretKey> for WithdrawSecretKey<'a> { + fn from(sk: &'a SchnorrSecretKey) -> Self { + Self::Phoenix(sk) + } +} + +impl<'a> From<&'a BlsSecretKey> for WithdrawSecretKey<'a> { + fn from(sk: &'a BlsSecretKey) -> Self { + Self::Moonlight(sk) + } +} + +/// The signature used for a [`Withdraw`]. +#[derive(Debug, Clone, PartialEq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub enum WithdrawSignature { + /// A transaction withdrawing to Phoenix must sign using their + /// [`SecretKey`]. + Phoenix(SchnorrSignature), + /// A transaction withdrawing to Moonlight - must sign using their + /// [`BlsSecretKey`]. + Moonlight(BlsSignature), +} + +impl WithdrawSignature { + fn to_var_bytes(&self) -> Vec { + match self { + WithdrawSignature::Phoenix(sig) => sig.to_bytes().to_vec(), + WithdrawSignature::Moonlight(sig) => sig.to_bytes().to_vec(), + } + } +} + +impl From for WithdrawSignature { + fn from(sig: SchnorrSignature) -> Self { + Self::Phoenix(sig) + } +} + +impl From for WithdrawSignature { + fn from(sig: BlsSignature) -> Self { + Self::Moonlight(sig) } } @@ -106,6 +306,18 @@ pub enum ContractExec { Deploy(ContractDeploy), } +impl From for ContractExec { + fn from(c: ContractCall) -> Self { + ContractExec::Call(c) + } +} + +impl From for ContractExec { + fn from(d: ContractDeploy) -> Self { + ContractExec::Deploy(d) + } +} + /// Data for performing a contract deployment #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] @@ -218,7 +430,7 @@ impl ContractCall { /// Deserialize a `ContractCall` from a byte buffer. /// /// # Errors - /// Errors when the bytes are not cannonical. + /// Errors when the bytes are not canonical. pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; @@ -385,24 +597,3 @@ impl Serializable for Fee { }) } } - -/// Additional data used to identify the origin of a [`Note`] when the -/// [`Sender`] is a `Contract`. -#[derive(Debug, Clone, Copy, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct SenderAccount { - /// The unique identifier of a contract. - pub contract: ContractId, - /// The unique identifier of the account on that contract. - pub account: BlsPublicKey, -} - -impl From for Sender { - fn from(sender: SenderAccount) -> Self { - let mut contract_info = [0u8; 4 * JubJubAffine::SIZE]; - contract_info[0..32].copy_from_slice(&sender.contract[..]); - contract_info[32..].copy_from_slice(&sender.account.to_bytes()); - - Sender::ContractInfo(contract_info) - } -} diff --git a/execution-core/src/transfer/transaction.rs b/execution-core/src/transfer/transaction.rs index efaf43732a..a0cf142285 100644 --- a/execution-core/src/transfer/transaction.rs +++ b/execution-core/src/transfer/transaction.rs @@ -7,343 +7,243 @@ //! Types related to Dusk's transfer contract that are shared across the //! network. -extern crate alloc; use alloc::vec::Vec; use bytecheck::CheckBytes; -use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; +use dusk_bytes::{DeserializableSlice, Error as BytesError}; +use phoenix_core::{Note, StealthAddress}; use rkyv::{Archive, Deserialize, Serialize}; -use crate::bytecode::Bytecode; -use crate::reader::read_vec; -use crate::transfer::ContractExec::{Call, Deploy}; -use crate::transfer::{ContractDeploy, ContractExec}; use crate::{ - transfer::{ContractCall, Fee}, - BlsScalar, JubJubAffine, Sender, TxSkeleton, + transfer::{ContractCall, ContractDeploy}, + BlsPublicKey, BlsScalar, BlsSignature, Sender, }; -/// The transaction payload -#[derive(Debug, Clone, Archive, Serialize, Deserialize)] +mod moonlight; +mod phoenix; + +pub use moonlight::{ + Payload as MoonlightPayload, Transaction as MoonlightTransaction, +}; +pub use phoenix::{ + Payload as PhoenixPayload, Transaction as PhoenixTransaction, +}; + +/// The transaction used by the transfer contract. +#[derive(Debug, Clone, Archive, PartialEq, Eq, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] -pub struct Payload { - /// Transaction skeleton used for the phoenix transaction. - pub tx_skeleton: TxSkeleton, - /// Data used to calculate the transaction fee. - pub fee: Fee, - /// Data to do a contract call or deployment. - pub contract_exec: Option, +#[allow(clippy::large_enum_variant)] +pub enum Transaction { + /// A phoenix transaction. + Phoenix(PhoenixTransaction), + /// A moonlight transaction. + Moonlight(MoonlightTransaction), } -impl PartialEq for Payload { - fn eq(&self, other: &Self) -> bool { - self.hash() == other.hash() +impl Transaction { + /// Create a new phoenix transaction. + #[must_use] + pub fn phoenix(payload: PhoenixPayload, proof: impl Into>) -> Self { + Self::Phoenix(PhoenixTransaction::new(payload, proof)) } -} -impl Eq for Payload {} + /// Create a new moonlight transaction. + #[must_use] + pub fn moonlight( + payload: MoonlightPayload, + signature: BlsSignature, + ) -> Self { + Self::Moonlight(MoonlightTransaction::new(payload, signature)) + } -impl Payload { - /// Return the contract-call data, if there is any. + /// Return the sender of the account for Moonlight transactions. #[must_use] - pub fn contract_call(&self) -> Option<&ContractCall> { - match self.contract_exec.as_ref() { - Some(Call(call)) => Some(call), - _ => None, + pub fn from(&self) -> Option<&BlsPublicKey> { + match self { + Self::Phoenix(_) => None, + Self::Moonlight(tx) => Some(&tx.payload.from), } } - /// Return the contract-deploy data, if there is any. + /// Return the receiver of the transaction for Moonlight transactions, if it + /// exists. #[must_use] - pub fn contract_deploy(&self) -> Option<&ContractDeploy> { - match self.contract_exec.as_ref() { - Some(Deploy(deploy)) => Some(deploy), - _ => None, + pub fn to(&self) -> Option<&BlsPublicKey> { + match self { + Self::Phoenix(_) => None, + Self::Moonlight(tx) => tx.payload.to.as_ref(), } } - /// Serialize the `Payload` into a variable length byte buffer. + /// Return the value transferred in a Moonlight transaction. #[must_use] - pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); - - // serialize the tx-skeleton - let skeleton_bytes = self.tx_skeleton.to_var_bytes(); - bytes.extend((skeleton_bytes.len() as u64).to_bytes()); - bytes.extend(skeleton_bytes); - - // serialize the fee - bytes.extend(self.fee.to_bytes()); - - // serialize the contract-call - match &self.contract_exec { - Some(ContractExec::Deploy(deploy)) => { - bytes.push(2); - bytes.extend(deploy.to_var_bytes()); - } - Some(ContractExec::Call(call)) => { - bytes.push(1); - bytes.extend(call.to_var_bytes()); - } - _ => bytes.push(0), + pub fn value(&self) -> Option { + match self { + Self::Phoenix(_) => None, + Self::Moonlight(tx) => Some(tx.payload.value), } - - bytes } - /// Deserialize the Payload from a bytes buffer. - /// - /// # Errors - /// Errors when the bytes are not canonical. - pub fn from_slice(buf: &[u8]) -> Result { - let mut buf = buf; - - // deserialize the tx-skeleton - #[allow(clippy::cast_possible_truncation)] - let skeleton_len = usize::try_from(u64::from_reader(&mut buf)?) - .map_err(|_| BytesError::InvalidData)?; - let tx_skeleton = TxSkeleton::from_slice(buf)?; - buf = &buf[skeleton_len..]; - - // deserialize fee - let fee = Fee::from_reader(&mut buf)?; - - // deserialize contract execution data - let contract_exec = match u8::from_reader(&mut buf)? { - 0 => None, - 1 => Some(ContractExec::Call(ContractCall::from_slice(buf)?)), - 2 => Some(ContractExec::Deploy(ContractDeploy::from_slice(buf)?)), - _ => { - return Err(BytesError::InvalidData); - } - }; - - Ok(Self { - tx_skeleton, - fee, - contract_exec, - }) + /// Returns the nullifiers of the transaction, if the transaction is a + /// moonlight transaction, the result will be empty. + #[must_use] + pub fn nullifiers(&self) -> &[BlsScalar] { + match self { + Self::Phoenix(tx) => &tx.payload.tx_skeleton.nullifiers, + Self::Moonlight(_) => &[], + } } - /// Return input bytes to hash the payload. - /// - /// Note: The result of this function is *only* meant to be used as an input - /// for hashing and *cannot* be used to deserialize the `Payload` again. + /// Return the root of the UTXO tree for Phoenix transactions. #[must_use] - pub fn to_hash_input_bytes(&self) -> Vec { - let mut bytes = self.tx_skeleton.to_hash_input_bytes(); + pub fn root(&self) -> Option<&BlsScalar> { + match self { + Self::Phoenix(tx) => Some(&tx.payload.tx_skeleton.root), + Self::Moonlight(_) => None, + } + } - if let Some(call) = self.contract_call() { - bytes.extend(call.contract); - bytes.extend(call.fn_name.as_bytes()); - bytes.extend(&call.fn_args); - } else if let Some(deploy) = self.contract_deploy() { - bytes.extend(&deploy.bytecode.to_hash_input_bytes()); - bytes.extend(&deploy.owner); - if let Some(constructor_args) = &deploy.constructor_args { - bytes.extend(constructor_args); - } + /// Return the UTXO outputs of the transaction. + #[must_use] + pub fn outputs(&self) -> &[Note] { + match self { + Self::Phoenix(tx) => &tx.payload.tx_skeleton.outputs, + Self::Moonlight(_) => &[], } + } - bytes + /// Return the stealth address for returning funds for Phoenix transactions. + #[must_use] + pub fn stealth_address(&self) -> Option<&StealthAddress> { + match self { + Self::Phoenix(tx) => Some(&tx.payload.fee.stealth_address), + Self::Moonlight(_) => None, + } } - /// Create the `Payload`-hash to be used as an input to the - /// pheonix-transaction circuit. + /// Returns the sender data for Phoenix transactions. #[must_use] - pub fn hash(&self) -> BlsScalar { - BlsScalar::hash_to_scalar(&self.to_hash_input_bytes()) + pub fn sender(&self) -> Option<&Sender> { + match self { + Self::Phoenix(tx) => Some(&tx.payload.fee.sender), + Self::Moonlight(_) => None, + } } -} -/// The transaction used by the transfer contract -#[derive(Debug, Clone, Archive, Serialize, Deserialize)] -#[archive_attr(derive(CheckBytes))] -pub struct Transaction { - payload: Payload, - proof: Vec, -} + /// Returns the deposit of the transaction. + #[must_use] + pub fn deposit(&self) -> u64 { + match self { + Self::Phoenix(tx) => tx.payload.tx_skeleton.deposit, + Self::Moonlight(tx) => tx.payload.deposit, + } + } -impl PartialEq for Transaction { - fn eq(&self, other: &Self) -> bool { - self.hash() == other.hash() + /// Returns the gas limit of the transaction. + #[must_use] + pub fn gas_limit(&self) -> u64 { + match self { + Self::Phoenix(tx) => tx.payload.fee.gas_limit, + Self::Moonlight(tx) => tx.payload.gas_limit, + } } -} -impl Eq for Transaction {} + /// Returns the gas price of the transaction. + #[must_use] + pub fn gas_price(&self) -> u64 { + match self { + Self::Phoenix(tx) => tx.payload.fee.gas_price, + Self::Moonlight(tx) => tx.payload.gas_price, + } + } -impl Transaction { - /// Create a new transaction. + /// Return the contract call data, if there is any. #[must_use] - pub fn new(payload: Payload, proof: impl Into>) -> Self { - Self { - payload, - proof: proof.into(), + pub fn call(&self) -> Option<&ContractCall> { + match self { + Self::Phoenix(tx) => tx.call(), + Self::Moonlight(tx) => tx.call(), } } - /// The payload of the transaction. + /// Return the contract deploy data, if there is any. #[must_use] - pub fn payload(&self) -> &Payload { - &self.payload + pub fn deploy(&self) -> Option<&ContractDeploy> { + match self { + Self::Phoenix(tx) => tx.deploy(), + Self::Moonlight(tx) => tx.deploy(), + } } - /// The proof of the transaction. + /// Creates a modified clone of this transaction if it contains data for + /// deployment, clones all fields except for the bytecode' 'bytes' part. + /// Returns none if the transaction is not a deployment transaction. #[must_use] - pub fn proof(&self) -> &[u8] { - &self.proof + pub fn strip_off_bytecode(&self) -> Option { + Some(match self { + Transaction::Phoenix(tx) => { + Transaction::Phoenix(tx.strip_off_bytecode()?) + } + Transaction::Moonlight(tx) => { + Transaction::Moonlight(tx.strip_off_bytecode()?) + } + }) } - /// Serialize the `Transaction` into a variable length byte buffer. + /// Serialize the transaction into a byte buffer. #[must_use] pub fn to_var_bytes(&self) -> Vec { let mut bytes = Vec::new(); - let payload_bytes = self.payload.to_var_bytes(); - bytes.extend((payload_bytes.len() as u64).to_bytes()); - bytes.extend(payload_bytes); - - bytes.extend((self.proof.len() as u64).to_bytes()); - bytes.extend(&self.proof); + match self { + Self::Phoenix(tx) => { + bytes.push(0); + bytes.extend(tx.to_var_bytes()); + } + Self::Moonlight(tx) => { + bytes.push(1); + bytes.extend(tx.to_var_bytes()); + } + } bytes } - /// Deserialize the Transaction from a bytes buffer. + /// Deserialize the transaction from a byte slice. /// /// # Errors - /// Errors when the bytes are not cannonical. + /// Errors when the bytes are not canonical. pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; - let payload_len = usize::try_from(u64::from_reader(&mut buf)?) - .map_err(|_| BytesError::InvalidData)?; - if buf.len() < payload_len { - return Err(BytesError::InvalidData); - } - let payload = Payload::from_slice(&buf[..payload_len])?; - buf = &buf[payload_len..]; - - let proof = read_vec(&mut buf)?; - - Ok(Self { payload, proof }) + Ok(match u8::from_reader(&mut buf)? { + 0 => Self::Phoenix(PhoenixTransaction::from_slice(buf)?), + 1 => Self::Moonlight(MoonlightTransaction::from_slice(buf)?), + _ => return Err(BytesError::InvalidData), + }) } - /// Return input bytes to hash the Transaction. + /// Return input bytes to hash the payload. /// /// Note: The result of this function is *only* meant to be used as an input - /// for hashing and *cannot* be used to deserialize the `Transaction` again. + /// for hashing and *cannot* be used to deserialize the transaction again. #[must_use] pub fn to_hash_input_bytes(&self) -> Vec { - let mut bytes = self.payload.to_hash_input_bytes(); - bytes.extend(&self.proof); - - bytes - } - - /// Create the `Transaction`-hash. - #[must_use] - pub fn hash(&self) -> BlsScalar { - BlsScalar::hash_to_scalar(&self.to_hash_input_bytes()) + match self { + Self::Phoenix(tx) => tx.to_hash_input_bytes(), + Self::Moonlight(tx) => tx.to_hash_input_bytes(), + } } +} - /// Return the public input to be used in the phoenix-transaction circuit - /// verification - /// - /// These are: - /// - `payload_hash` - /// - `root` - /// - `[nullifier; I]` - /// - `[output_value_commitment; 2]` - /// - `max_fee` - /// - `deposit` - /// - `(npk_0, npk_1)` - /// - `(enc_A_npk_0, enc_B_npk_0)` - /// - `(enc_A_npk_1, enc_B_npk_1)` - /// - /// # Panics - /// Panics if one of the output-notes doesn't have the sender set to - /// [`Sender::Encryption`]. - #[must_use] - pub fn public_inputs(&self) -> Vec { - let tx_skeleton = &self.payload.tx_skeleton; - - // retrieve the number of input and output notes - let input_len = tx_skeleton.nullifiers.len(); - let output_len = tx_skeleton.outputs.len(); - - let size = - // payload-hash and root - 1 + 1 - // nullifiers - + input_len - // output-notes value-commitment - + 2 * output_len - // max-fee and deposit - + 1 + 1 - // output-notes public-keys - + 2 * output_len - // sender-encryption for both output-notes - + 2 * 4 * output_len; - // build the public input vector - let mut pis = Vec::::with_capacity(size); - pis.push(self.payload.hash()); - pis.push(tx_skeleton.root); - pis.extend(tx_skeleton.nullifiers().iter()); - tx_skeleton.outputs().iter().for_each(|note| { - let value_commitment = note.value_commitment(); - pis.push(value_commitment.get_u()); - pis.push(value_commitment.get_v()); - }); - pis.push(tx_skeleton.max_fee().into()); - pis.push(tx_skeleton.deposit().into()); - tx_skeleton.outputs().iter().for_each(|note| { - let note_pk = - JubJubAffine::from(note.stealth_address().note_pk().as_ref()); - pis.push(note_pk.get_u()); - pis.push(note_pk.get_v()); - }); - tx_skeleton.outputs().iter().for_each(|note| { - match note.sender() { - Sender::Encryption(sender_enc) => { - pis.push(sender_enc[0].0.get_u()); - pis.push(sender_enc[0].0.get_v()); - pis.push(sender_enc[0].1.get_u()); - pis.push(sender_enc[0].1.get_v()); - pis.push(sender_enc[1].0.get_u()); - pis.push(sender_enc[1].0.get_v()); - pis.push(sender_enc[1].1.get_u()); - pis.push(sender_enc[1].1.get_v()); - } - Sender::ContractInfo(_) => { - panic!("All output-notes must provide a sender-encryption") - } - }; - }); - - pis +impl From for Transaction { + fn from(tx: PhoenixTransaction) -> Self { + Self::Phoenix(tx) } +} - /// Creates a modified clone of this transaction if it contains data for - /// deployment, clones all fields except for the bytecode' 'bytes' part. - /// Returns none if the transaction is not a deployment transaction. - #[must_use] - pub fn strip_off_bytecode(&self) -> Option { - let deploy = self.payload().contract_deploy()?; - Some(Self::new( - Payload { - tx_skeleton: self.payload().tx_skeleton.clone(), - fee: self.payload().fee, - contract_exec: Some(ContractExec::Deploy(ContractDeploy { - owner: deploy.owner.clone(), - constructor_args: deploy.constructor_args.clone(), - bytecode: Bytecode { - hash: deploy.bytecode.hash, - bytes: Vec::new(), - }, - })), - }, - self.proof(), - )) +impl From for Transaction { + fn from(tx: MoonlightTransaction) -> Self { + Self::Moonlight(tx) } } diff --git a/execution-core/src/transfer/transaction/moonlight.rs b/execution-core/src/transfer/transaction/moonlight.rs new file mode 100644 index 0000000000..dac7b49867 --- /dev/null +++ b/execution-core/src/transfer/transaction/moonlight.rs @@ -0,0 +1,321 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use alloc::vec::Vec; + +use bytecheck::CheckBytes; +use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::{ + transfer::{Bytecode, ContractCall, ContractDeploy, ContractExec}, + BlsPublicKey, BlsScalar, BlsSignature, +}; + +/// Moonlight transaction. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Transaction { + pub(crate) payload: Payload, + pub(crate) signature: BlsSignature, +} + +impl Transaction { + /// Create a new transaction. + #[must_use] + pub fn new(payload: Payload, signature: BlsSignature) -> Self { + Self { payload, signature } + } + + /// The payload of the transaction. + #[must_use] + pub fn payload(&self) -> &Payload { + &self.payload + } + + /// The proof of the transaction. + #[must_use] + pub fn signature(&self) -> &BlsSignature { + &self.signature + } + + /// Return the contract call data, if there is any. + #[must_use] + pub fn call(&self) -> Option<&ContractCall> { + #[allow(clippy::match_wildcard_for_single_variants)] + match self.exec()? { + ContractExec::Call(ref c) => Some(c), + _ => None, + } + } + + /// Return the contract deploy data, if there is any. + #[must_use] + pub fn deploy(&self) -> Option<&ContractDeploy> { + #[allow(clippy::match_wildcard_for_single_variants)] + match self.exec()? { + ContractExec::Deploy(ref d) => Some(d), + _ => None, + } + } + + /// Returns the contract execution, if it exists. + #[must_use] + fn exec(&self) -> Option<&ContractExec> { + self.payload.exec.as_ref() + } + + /// Creates a modified clone of this transaction if it contains data for + /// deployment, clones all fields except for the bytecode' 'bytes' part. + /// Returns none if the transaction is not a deployment transaction. + #[must_use] + pub fn strip_off_bytecode(&self) -> Option { + let deploy = self.deploy()?; + + Some(Self::new( + Payload { + from: self.payload.from, + to: self.payload.to, + value: self.payload.value, + deposit: self.payload.deposit, + gas_limit: self.payload.gas_limit, + gas_price: self.payload.gas_price, + nonce: self.payload.nonce, + exec: Some(ContractExec::Deploy(ContractDeploy { + owner: deploy.owner.clone(), + constructor_args: deploy.constructor_args.clone(), + bytecode: Bytecode { + hash: deploy.bytecode.hash, + bytes: Vec::new(), + }, + })), + }, + *self.signature(), + )) + } + + /// Serialize a transaction into a byte buffer. + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + let payload_bytes = self.payload.to_var_bytes(); + bytes.extend((payload_bytes.len() as u64).to_bytes()); + bytes.extend(payload_bytes); + + bytes.extend(self.signature.to_bytes()); + + bytes + } + + /// Deserialize the Transaction from a bytes buffer. + /// + /// # Errors + /// Errors when the bytes are not canonical. + pub fn from_slice(buf: &[u8]) -> Result { + let mut buf = buf; + + let payload_len = usize::try_from(u64::from_reader(&mut buf)?) + .map_err(|_| BytesError::InvalidData)?; + + if buf.len() < payload_len { + return Err(BytesError::InvalidData); + } + let (payload_buf, new_buf) = buf.split_at(payload_len); + + let payload = Payload::from_slice(payload_buf)?; + buf = new_buf; + + let signature = BlsSignature::from_bytes( + buf.try_into().map_err(|_| BytesError::InvalidData)?, + ) + .map_err(|_| BytesError::InvalidData)?; + + Ok(Self { payload, signature }) + } + + /// Return input bytes to hash the payload. + /// + /// Note: The result of this function is *only* meant to be used as an input + /// for hashing and *cannot* be used to deserialize the transaction again. + #[must_use] + pub fn to_hash_input_bytes(&self) -> Vec { + let mut bytes = self.payload.to_hash_input_bytes(); + bytes.extend(self.signature.to_bytes()); + bytes + } + + /// Return the message that is meant to be signed over to make the + /// transaction a valid one. + #[must_use] + pub fn signature_message(&self) -> Vec { + self.payload.to_hash_input_bytes() + } + + /// Create the payload hash. + #[must_use] + pub fn hash(&self) -> BlsScalar { + BlsScalar::hash_to_scalar(&self.to_hash_input_bytes()) + } +} + +/// The payload for a moonlight transaction. +#[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Payload { + /// Key of the sender of this transaction. + pub from: BlsPublicKey, + /// Key of the receiver of the funds. + pub to: Option, + /// Value to be transferred. + pub value: u64, + /// Deposit for a contract. + pub deposit: u64, + /// Limit on the gas to be spent. + pub gas_limit: u64, + /// Price for each unit of gas. + pub gas_price: u64, + /// Nonce used for replay protection. Nonces are strictly increasing, + /// meaning that once a transaction has been settled, only a higher + /// nonce can be used. + /// + /// The current nonce is queryable via the transfer contract and best + /// practice is to use `nonce + 1` for a single transaction. + pub nonce: u64, + /// Data to do a contract call or deployment. + pub exec: Option, +} + +impl Payload { + /// Serialize the payload into a byte buffer. + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.from.to_bytes()); + + // serialize the recipient + match self.to { + Some(to) => { + bytes.push(1); + bytes.extend(to.to_bytes()); + } + None => { + bytes.push(0); + } + } + + bytes.extend(self.value.to_bytes()); + bytes.extend(self.deposit.to_bytes()); + bytes.extend(self.gas_limit.to_bytes()); + bytes.extend(self.gas_price.to_bytes()); + bytes.extend(self.nonce.to_bytes()); + + // serialize the contract call/deployment + match &self.exec { + Some(ContractExec::Deploy(deploy)) => { + bytes.push(2); + bytes.extend(deploy.to_var_bytes()); + } + Some(ContractExec::Call(call)) => { + bytes.push(1); + bytes.extend(call.to_var_bytes()); + } + _ => bytes.push(0), + } + + bytes + } + + /// Deserialize the payload from bytes slice. + /// + /// # Errors + /// Errors when the bytes are not canonical. + pub fn from_slice(buf: &[u8]) -> Result { + let mut buf = buf; + + let from = BlsPublicKey::from_reader(&mut buf)?; + + // deserialize recipient + let to = match u8::from_reader(&mut buf)? { + 0 => None, + 1 => Some(BlsPublicKey::from_reader(&mut buf)?), + _ => { + return Err(BytesError::InvalidData); + } + }; + + let value = u64::from_reader(&mut buf)?; + let deposit = u64::from_reader(&mut buf)?; + let gas_limit = u64::from_reader(&mut buf)?; + let gas_price = u64::from_reader(&mut buf)?; + let nonce = u64::from_reader(&mut buf)?; + + // deserialize contract call/deploy data + let exec = match u8::from_reader(&mut buf)? { + 0 => None, + 1 => Some(ContractExec::Call(ContractCall::from_slice(buf)?)), + 2 => Some(ContractExec::Deploy(ContractDeploy::from_slice(buf)?)), + _ => { + return Err(BytesError::InvalidData); + } + }; + + Ok(Self { + from, + to, + value, + deposit, + gas_limit, + gas_price, + nonce, + exec, + }) + } + + /// Return input bytes to hash the payload. + /// + /// Note: The result of this function is *only* meant to be used as an input + /// for hashing and *cannot* be used to deserialize the payload again. + #[must_use] + pub fn to_hash_input_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + bytes.extend(self.from.to_bytes()); + if let Some(to) = &self.to { + bytes.extend(to.to_bytes()); + } + bytes.extend(self.value.to_bytes()); + bytes.extend(self.deposit.to_bytes()); + bytes.extend(self.gas_limit.to_bytes()); + bytes.extend(self.gas_price.to_bytes()); + bytes.extend(self.nonce.to_bytes()); + + match &self.exec { + Some(ContractExec::Deploy(d)) => { + bytes.extend(&d.bytecode.to_hash_input_bytes()); + bytes.extend(&d.owner); + if let Some(constructor_args) = &d.constructor_args { + bytes.extend(constructor_args); + } + } + Some(ContractExec::Call(c)) => { + bytes.extend(c.contract); + bytes.extend(c.fn_name.as_bytes()); + bytes.extend(&c.fn_args); + } + _ => {} + } + + bytes + } + + /// Create the payload hash. + #[must_use] + pub fn hash(&self) -> BlsScalar { + BlsScalar::hash_to_scalar(&self.to_hash_input_bytes()) + } +} diff --git a/execution-core/src/transfer/transaction/phoenix.rs b/execution-core/src/transfer/transaction/phoenix.rs new file mode 100644 index 0000000000..637f2565aa --- /dev/null +++ b/execution-core/src/transfer/transaction/phoenix.rs @@ -0,0 +1,358 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use alloc::vec::Vec; + +use bytecheck::CheckBytes; +use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; +use rkyv::{Archive, Deserialize, Serialize}; + +use crate::{ + transfer::{Bytecode, ContractCall, ContractDeploy, ContractExec, Fee}, + BlsScalar, JubJubAffine, Sender, TxSkeleton, +}; + +/// Phoenix transaction. +#[derive(Debug, Clone, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Transaction { + pub(crate) payload: Payload, + pub(crate) proof: Vec, +} + +impl PartialEq for Transaction { + fn eq(&self, other: &Self) -> bool { + self.hash() == other.hash() + } +} + +impl Eq for Transaction {} + +impl Transaction { + /// Create a new transaction. + #[must_use] + pub fn new(payload: Payload, proof: impl Into>) -> Self { + Self { + payload, + proof: proof.into(), + } + } + + /// The payload of the transaction. + #[must_use] + pub fn payload(&self) -> &Payload { + &self.payload + } + + /// The proof of the transaction. + #[must_use] + pub fn proof(&self) -> &[u8] { + &self.proof + } + + /// Return the contract call data, if there is any. + #[must_use] + pub fn call(&self) -> Option<&ContractCall> { + #[allow(clippy::match_wildcard_for_single_variants)] + match self.exec()? { + ContractExec::Call(ref c) => Some(c), + _ => None, + } + } + + /// Return the contract deploy data, if there is any. + #[must_use] + pub fn deploy(&self) -> Option<&ContractDeploy> { + #[allow(clippy::match_wildcard_for_single_variants)] + match self.exec()? { + ContractExec::Deploy(ref d) => Some(d), + _ => None, + } + } + + /// Returns the contract execution, if it exists. + #[must_use] + fn exec(&self) -> Option<&ContractExec> { + self.payload.exec.as_ref() + } + + /// Creates a modified clone of this transaction if it contains data for + /// deployment, clones all fields except for the bytecode' 'bytes' part. + /// Returns none if the transaction is not a deployment transaction. + #[must_use] + pub fn strip_off_bytecode(&self) -> Option { + let deploy = self.deploy()?; + + Some(Self::new( + Payload { + tx_skeleton: self.payload().tx_skeleton.clone(), + fee: self.payload().fee, + exec: Some(ContractExec::Deploy(ContractDeploy { + owner: deploy.owner.clone(), + constructor_args: deploy.constructor_args.clone(), + bytecode: Bytecode { + hash: deploy.bytecode.hash, + bytes: Vec::new(), + }, + })), + }, + self.proof(), + )) + } + + /// Serialize the `Transaction` into a variable length byte buffer. + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + let payload_bytes = self.payload.to_var_bytes(); + bytes.extend((payload_bytes.len() as u64).to_bytes()); + bytes.extend(payload_bytes); + + bytes.extend((self.proof.len() as u64).to_bytes()); + bytes.extend(&self.proof); + + bytes + } + + /// Deserialize the Transaction from a bytes buffer. + /// + /// # Errors + /// Errors when the bytes are not canonical. + pub fn from_slice(buf: &[u8]) -> Result { + let mut buf = buf; + + let payload_len = usize::try_from(u64::from_reader(&mut buf)?) + .map_err(|_| BytesError::InvalidData)?; + + if buf.len() < payload_len { + return Err(BytesError::InvalidData); + } + let (payload_buf, new_buf) = buf.split_at(payload_len); + + let payload = Payload::from_slice(payload_buf)?; + buf = new_buf; + + let proof_len = usize::try_from(u64::from_reader(&mut buf)?) + .map_err(|_| BytesError::InvalidData)?; + let proof = buf[..proof_len].into(); + + Ok(Self { payload, proof }) + } + + /// Return input bytes to hash the Transaction. + /// + /// Note: The result of this function is *only* meant to be used as an input + /// for hashing and *cannot* be used to deserialize the `Transaction` again. + #[must_use] + pub fn to_hash_input_bytes(&self) -> Vec { + let mut bytes = self.payload.to_hash_input_bytes(); + bytes.extend(&self.proof); + bytes + } + + /// Create the `Transaction`-hash. + #[must_use] + pub fn hash(&self) -> BlsScalar { + BlsScalar::hash_to_scalar(&self.to_hash_input_bytes()) + } + + /// Return the public input to be used in the phoenix-transaction circuit + /// verification + /// + /// These are: + /// - `payload_hash` + /// - `root` + /// - `[nullifier; I]` + /// - `[output_value_commitment; 2]` + /// - `max_fee` + /// - `deposit` + /// - `(npk_0, npk_1)` + /// - `(enc_A_npk_0, enc_B_npk_0)` + /// - `(enc_A_npk_1, enc_B_npk_1)` + /// + /// # Panics + /// Panics if one of the output-notes doesn't have the sender set to + /// [`Sender::Encryption`]. + #[must_use] + pub fn public_inputs(&self) -> Vec { + let tx_skeleton = &self.payload.tx_skeleton; + + // retrieve the number of input and output notes + let input_len = tx_skeleton.nullifiers.len(); + let output_len = tx_skeleton.outputs.len(); + + let size = + // payload-hash and root + 1 + 1 + // nullifiers + + input_len + // output-notes value-commitment + + 2 * output_len + // max-fee and deposit + + 1 + 1 + // output-notes public-keys + + 2 * output_len + // sender-encryption for both output-notes + + 2 * 4 * output_len; + // build the public input vector + let mut pis = Vec::::with_capacity(size); + pis.push(self.payload.hash()); + pis.push(tx_skeleton.root); + pis.extend(tx_skeleton.nullifiers().iter()); + tx_skeleton.outputs().iter().for_each(|note| { + let value_commitment = note.value_commitment(); + pis.push(value_commitment.get_u()); + pis.push(value_commitment.get_v()); + }); + pis.push(tx_skeleton.max_fee().into()); + pis.push(tx_skeleton.deposit().into()); + tx_skeleton.outputs().iter().for_each(|note| { + let note_pk = + JubJubAffine::from(note.stealth_address().note_pk().as_ref()); + pis.push(note_pk.get_u()); + pis.push(note_pk.get_v()); + }); + tx_skeleton.outputs().iter().for_each(|note| { + match note.sender() { + Sender::Encryption(sender_enc) => { + pis.push(sender_enc[0].0.get_u()); + pis.push(sender_enc[0].0.get_v()); + pis.push(sender_enc[0].1.get_u()); + pis.push(sender_enc[0].1.get_v()); + pis.push(sender_enc[1].0.get_u()); + pis.push(sender_enc[1].0.get_v()); + pis.push(sender_enc[1].1.get_u()); + pis.push(sender_enc[1].1.get_v()); + } + Sender::ContractInfo(_) => { + panic!("All output-notes must provide a sender-encryption") + } + }; + }); + + pis + } +} + +/// The transaction payload +#[derive(Debug, Clone, Archive, Serialize, Deserialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Payload { + /// Transaction skeleton used for the phoenix transaction. + pub tx_skeleton: TxSkeleton, + /// Data used to calculate the transaction fee. + pub fee: Fee, + /// Data to do a contract call or deployment. + pub exec: Option, +} + +impl PartialEq for Payload { + fn eq(&self, other: &Self) -> bool { + self.hash() == other.hash() + } +} + +impl Eq for Payload {} + +impl Payload { + /// Serialize the `Payload` into a variable length byte buffer. + #[must_use] + pub fn to_var_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + + // serialize the tx-skeleton + let skeleton_bytes = self.tx_skeleton.to_var_bytes(); + bytes.extend((skeleton_bytes.len() as u64).to_bytes()); + bytes.extend(skeleton_bytes); + + // serialize the fee + bytes.extend(self.fee.to_bytes()); + + // serialize the contract call/deployment + match &self.exec { + Some(ContractExec::Deploy(deploy)) => { + bytes.push(2); + bytes.extend(deploy.to_var_bytes()); + } + Some(ContractExec::Call(call)) => { + bytes.push(1); + bytes.extend(call.to_var_bytes()); + } + _ => bytes.push(0), + } + + bytes + } + + /// Deserialize the Payload from a bytes buffer. + /// + /// # Errors + /// Errors when the bytes are not canonical. + pub fn from_slice(buf: &[u8]) -> Result { + let mut buf = buf; + + // deserialize the tx-skeleton + #[allow(clippy::cast_possible_truncation)] + let skeleton_len = usize::try_from(u64::from_reader(&mut buf)?) + .map_err(|_| BytesError::InvalidData)?; + let tx_skeleton = TxSkeleton::from_slice(buf)?; + buf = &buf[skeleton_len..]; + + // deserialize fee + let fee = Fee::from_reader(&mut buf)?; + + // deserialize contract call/deploy data + let exec = match u8::from_reader(&mut buf)? { + 0 => None, + 1 => Some(ContractExec::Call(ContractCall::from_slice(buf)?)), + 2 => Some(ContractExec::Deploy(ContractDeploy::from_slice(buf)?)), + _ => { + return Err(BytesError::InvalidData); + } + }; + + Ok(Self { + tx_skeleton, + fee, + exec, + }) + } + + /// Return input bytes to hash the payload. + /// + /// Note: The result of this function is *only* meant to be used as an input + /// for hashing and *cannot* be used to deserialize the `Payload` again. + #[must_use] + pub fn to_hash_input_bytes(&self) -> Vec { + let mut bytes = self.tx_skeleton.to_hash_input_bytes(); + + match &self.exec { + Some(ContractExec::Deploy(d)) => { + bytes.extend(&d.bytecode.to_hash_input_bytes()); + bytes.extend(&d.owner); + if let Some(constructor_args) = &d.constructor_args { + bytes.extend(constructor_args); + } + } + Some(ContractExec::Call(c)) => { + bytes.extend(c.contract); + bytes.extend(c.fn_name.as_bytes()); + bytes.extend(&c.fn_args); + } + _ => {} + } + + bytes + } + + /// Create the `Payload`-hash to be used as an input to the + /// pheonix-transaction circuit. + #[must_use] + pub fn hash(&self) -> BlsScalar { + BlsScalar::hash_to_scalar(&self.to_hash_input_bytes()) + } +} diff --git a/execution-core/tests/serialization.rs b/execution-core/tests/serialization.rs index a569b86179..a178f3050d 100644 --- a/execution-core/tests/serialization.rs +++ b/execution-core/tests/serialization.rs @@ -5,36 +5,42 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use dusk_bls12_381::BlsScalar; -use dusk_bytes::{Error, Serializable}; +use dusk_bytes::Error; use dusk_jubjub::JubJubScalar; use execution_core::bytecode::Bytecode; use execution_core::transfer::{ - ContractCall, ContractDeploy, ContractExec, Fee, Payload, Transaction, + ContractCall, ContractDeploy, ContractExec, Fee, Transaction, +}; +use execution_core::transfer::{ + MoonlightPayload, MoonlightTransaction, PhoenixPayload, PhoenixTransaction, +}; +use execution_core::{ + BlsPublicKey, BlsSecretKey, Note, PublicKey, SecretKey, TxSkeleton, }; -use execution_core::{Note, PublicKey, SecretKey, TxSkeleton}; use ff::Field; use rand::rngs::StdRng; -use rand::{RngCore, SeedableRng}; - -fn build_skeleton_fee_deposit() -> (TxSkeleton, Fee, u64) { - let mut rng = StdRng::seed_from_u64(42); +use rand::{CryptoRng, Rng, RngCore, SeedableRng}; +fn new_phoenix_tx( + rng: &mut R, + exec: Option, +) -> Transaction { // set the general parameters - let sender_pk = PublicKey::from(&SecretKey::random(&mut rng)); - let receiver_pk = PublicKey::from(&SecretKey::random(&mut rng)); + let sender_pk = PublicKey::from(&SecretKey::random(rng)); + let receiver_pk = PublicKey::from(&SecretKey::random(rng)); let gas_limit = 500; let gas_price = 42; // build the tx-skeleton let value = 25; - let value_blinder = JubJubScalar::random(&mut rng); + let value_blinder = JubJubScalar::random(&mut *rng); let sender_blinder = [ - JubJubScalar::random(&mut rng), - JubJubScalar::random(&mut rng), + JubJubScalar::random(&mut *rng), + JubJubScalar::random(&mut *rng), ]; let note = Note::obfuscated( - &mut rng, + rng, &sender_pk, &receiver_pk, value, @@ -62,85 +68,197 @@ fn build_skeleton_fee_deposit() -> (TxSkeleton, Fee, u64) { }; // build the fee - let fee = Fee::new(&mut rng, &sender_pk, gas_limit, gas_price); - (tx_skeleton, fee, deposit) -} - -#[test] -fn transaction_serialization_call() -> Result<(), Error> { - let (tx_skeleton, fee, deposit) = build_skeleton_fee_deposit(); - - // build the contract-call - let contract = [42; 32]; - let call = ContractCall { - contract, - fn_name: String::from("deposit"), - fn_args: deposit.to_bytes().to_vec(), - }; + let fee = Fee::new(rng, &sender_pk, gas_limit, gas_price); // build the payload - let payload = Payload { + let payload = PhoenixPayload { tx_skeleton, fee, - contract_exec: Some(ContractExec::Call(call)), + exec, }; // set a random proof let proof = [42; 42].to_vec(); - let transaction = Transaction::new(payload, proof); + PhoenixTransaction::new(payload, proof).into() +} + +fn new_moonlight_tx( + rng: &mut R, + exec: Option, +) -> Transaction { + let sk = BlsSecretKey::random(rng); + let pk = BlsPublicKey::from(&sk); + + let payload = MoonlightPayload { + from: pk, + to: None, + value: rng.gen(), + deposit: rng.gen(), + gas_limit: rng.gen(), + gas_price: rng.gen(), + nonce: rng.gen(), + exec, + }; + + let msg = payload.to_hash_input_bytes(); + let signature = sk.sign(&pk, &msg); + + MoonlightTransaction::new(payload, signature).into() +} + +#[test] +fn phoenix() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(42); + + let transaction = new_phoenix_tx(&mut rng, None); let transaction_bytes = transaction.to_var_bytes(); let deserialized = Transaction::from_slice(&transaction_bytes)?; + assert_eq!(transaction, deserialized); + Ok(()) } #[test] -fn transaction_serialization_deploy() -> Result<(), Error> { - let (tx_skeleton, fee, _) = build_skeleton_fee_deposit(); +fn phoenix_with_call() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(42); + + // build the contract call + let mut contract = [0; 32]; + rng.fill_bytes(&mut contract); + + let mut fn_args = vec![0; 100]; + rng.fill_bytes(&mut fn_args); - // build the contract-deploy - let bytecode = Bytecode { - hash: [1u8; 32], - bytes: vec![1, 2, 3, 4, 5], + let call = ContractCall { + contract, + fn_name: String::from("deposit"), + fn_args, }; - let owner = [1; 32]; - let constructor_args = vec![5]; + + let transaction = new_phoenix_tx(&mut rng, Some(ContractExec::Call(call))); + + let transaction_bytes = transaction.to_var_bytes(); + let deserialized = Transaction::from_slice(&transaction_bytes)?; + + assert_eq!(transaction, deserialized); + + Ok(()) +} + +#[test] +fn phoenix_with_deploy() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(42); + + // build a contract deployment + let mut hash = [0; 32]; + rng.fill_bytes(&mut hash); + let mut bytes = vec![0; 100]; + rng.fill_bytes(&mut bytes); + let bytecode = Bytecode { hash, bytes }; + + let mut owner = [0; 32].to_vec(); + rng.fill_bytes(&mut owner); + + let mut constructor_args = vec![0; 20]; + rng.fill_bytes(&mut constructor_args); + let deploy = ContractDeploy { bytecode, - owner: owner.to_vec(), + owner, constructor_args: Some(constructor_args), }; - // build the payload - let payload = Payload { - tx_skeleton, - fee, - contract_exec: Some(ContractExec::Deploy(deploy)), + let transaction = + new_phoenix_tx(&mut rng, Some(ContractExec::Deploy(deploy))); + + let transaction_bytes = transaction.to_var_bytes(); + let deserialized = Transaction::from_slice(&transaction_bytes)?; + + assert_eq!(transaction, deserialized); + + Ok(()) +} + +#[test] +fn moonlight() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(42); + + let transaction = new_moonlight_tx(&mut rng, None); + + let transaction_bytes = transaction.to_var_bytes(); + let deserialized = Transaction::from_slice(&transaction_bytes)?; + + assert_eq!(transaction, deserialized); + + Ok(()) +} + +#[test] +fn moonlight_with_call() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(42); + + // build the contract call + let mut contract = [0; 32]; + rng.fill_bytes(&mut contract); + + let mut fn_args = vec![0; 100]; + rng.fill_bytes(&mut fn_args); + + let call = ContractCall { + contract, + fn_name: String::from("deposit"), + fn_args, }; - // set a random proof - let proof = [42; 42].to_vec(); + let transaction = + new_moonlight_tx(&mut rng, Some(ContractExec::Call(call))); - // bytecode not stripped off - let transaction = Transaction::new(payload.clone(), proof.clone()); let transaction_bytes = transaction.to_var_bytes(); let deserialized = Transaction::from_slice(&transaction_bytes)?; + assert_eq!(transaction, deserialized); - // bytecode stripped off - let transaction = Transaction::new(payload, proof) - .strip_off_bytecode() - .expect("transaction contains deployment data"); + Ok(()) +} + +#[test] +fn moonlight_with_deploy() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(42); + + let mut hash = [0; 32]; + rng.fill_bytes(&mut hash); + let mut bytes = vec![0; 100]; + rng.fill_bytes(&mut bytes); + let bytecode = Bytecode { hash, bytes }; + + let mut owner = [0; 32].to_vec(); + rng.fill_bytes(&mut owner); + + let mut constructor_args = vec![0; 20]; + rng.fill_bytes(&mut constructor_args); + + let deploy = ContractDeploy { + bytecode, + owner, + constructor_args: Some(constructor_args), + }; + + let transaction = + new_moonlight_tx(&mut rng, Some(ContractExec::Deploy(deploy))); + let transaction_bytes = transaction.to_var_bytes(); let deserialized = Transaction::from_slice(&transaction_bytes)?; + assert_eq!(transaction, deserialized); + Ok(()) } #[test] -fn transaction_deserialization_failing() -> Result<(), Error> { +fn nonsense_bytes_fails() -> Result<(), Error> { let mut data = [0u8; 2 ^ 16]; for exp in 3..16 { rand::thread_rng().fill_bytes(&mut data[..2 ^ exp]); From 5768fb87351905a22a3e2ca283d5df7e7754b1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:41:19 +0200 Subject: [PATCH 02/12] rusk-abi: small name changes --- rusk-abi/tests/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rusk-abi/tests/lib.rs b/rusk-abi/tests/lib.rs index 9e8070c23a..1b738c51bf 100644 --- a/rusk-abi/tests/lib.rs +++ b/rusk-abi/tests/lib.rs @@ -14,8 +14,8 @@ use rand_core::OsRng; use dusk_bytes::{ParseHexStr, Serializable}; use dusk_plonk::prelude::*; use execution_core::{ - BlsScalar, NotePublicKey, NoteSecretKey, PublicKey, SecretKey, - StakePublicKey, StakeSecretKey, + BlsPublicKey, BlsScalar, BlsSecretKey, NotePublicKey, NoteSecretKey, + PublicKey, SecretKey, }; use ff::Field; use rusk_abi::hash::Hasher; @@ -182,8 +182,8 @@ fn stake_signature() { let message = b"some-message".to_vec(); - let stake_sk = StakeSecretKey::random(&mut OsRng); - let stake_pk = StakePublicKey::from(&stake_sk); + let stake_sk = BlsSecretKey::random(&mut OsRng); + let stake_pk = BlsPublicKey::from(&stake_sk); let stake_sig = stake_sk.sign(&stake_pk, &message); @@ -195,8 +195,8 @@ fn stake_signature() { assert!(valid, "Stake Signature verification expected to succeed"); - let wrong_sk = StakeSecretKey::random(&mut OsRng); - let wrong_pk = StakePublicKey::from(&wrong_sk); + let wrong_sk = BlsSecretKey::random(&mut OsRng); + let wrong_pk = BlsPublicKey::from(&wrong_sk); let arg = (arg.0, wrong_pk, arg.2); let valid: bool = session From 9d824678490aacf5bd26fa5c9d65731258e8ac4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:15:10 +0200 Subject: [PATCH 03/12] transfer-contract: support for moonlight and expanded withdrawals We add support for Moonlight transactions by adding a map between public keys and their respective account data as defined by `execution-core`. This means also adding a query function for this account data - named `account`, taking as argument a public key. Support for the new withdrawal system is also added, in a way that we can reuse code between `withdraw` and `mint`, effectively minimizing the amount of code written. One downside of this new system is that contracts are no longer able to set data in the `Sender` of a Phoenix note. Instead the tranfer contract is responsible for setting it, and currently we set the contract ID withdrawn from, together with a suffixed string. We take the liberty to rework the transient data held during a call ray, effectively minimizing data being passed back and forth - for instance during a `refund` call. We also rename `balance` to `contract_balance` contract-wide, to avoid confusion with Moonlight accounts. --- contracts/transfer/src/lib.rs | 32 +- contracts/transfer/src/state.rs | 481 +++++++++++++++++++++------ contracts/transfer/src/transitory.rs | 114 +++++++ contracts/transfer/tests/common.rs | 90 +++-- contracts/transfer/tests/transfer.rs | 263 ++++++++++++--- 5 files changed, 795 insertions(+), 185 deletions(-) create mode 100644 contracts/transfer/src/transitory.rs diff --git a/contracts/transfer/src/lib.rs b/contracts/transfer/src/lib.rs index aa1a033967..915f8a7e6d 100644 --- a/contracts/transfer/src/lib.rs +++ b/contracts/transfer/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; mod error; mod state; +mod transitory; mod tree; mod verifier_data; @@ -47,9 +48,14 @@ unsafe fn root(arg_len: u32) -> u32 { rusk_abi::wrap_call(arg_len, |_: ()| STATE.root()) } +#[no_mangle] +unsafe fn account(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |key| STATE.account(&key)) +} + #[no_mangle] unsafe fn contract_balance(arg_len: u32) -> u32 { - rusk_abi::wrap_call(arg_len, |contract| STATE.balance(&contract)) + rusk_abi::wrap_call(arg_len, |contract| STATE.contract_balance(&contract)) } #[no_mangle] @@ -93,9 +99,9 @@ unsafe fn spend_and_execute(arg_len: u32) -> u32 { #[no_mangle] unsafe fn refund(arg_len: u32) -> u32 { - rusk_abi::wrap_call(arg_len, |(fee, gas_spent)| { + rusk_abi::wrap_call(arg_len, |gas_spent| { assert_external_caller(); - STATE.refund(fee, gas_spent) + STATE.refund(gas_spent) }) } @@ -115,11 +121,27 @@ unsafe fn update_root(arg_len: u32) -> u32 { }) } +#[no_mangle] +unsafe fn add_account_balance(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |(key, value)| { + assert_external_caller(); + STATE.add_account_balance(&key, value) + }) +} + +#[no_mangle] +unsafe fn sub_account_balance(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |(key, value)| { + assert_external_caller(); + STATE.sub_account_balance(&key, value) + }) +} + #[no_mangle] unsafe fn add_contract_balance(arg_len: u32) -> u32 { rusk_abi::wrap_call(arg_len, |(module, value)| { assert_external_caller(); - STATE.add_balance(module, value) + STATE.add_contract_balance(module, value) }) } @@ -130,7 +152,7 @@ unsafe fn sub_contract_balance(arg_len: u32) -> u32 { panic!("Can only be called by the stake contract!") } STATE - .sub_balance(&module, value) + .sub_contract_balance(&module, value) .expect("Cannot subtract balance") }) } diff --git a/contracts/transfer/src/state.rs b/contracts/transfer/src/state.rs index 5d3d04383a..0a848a8b2f 100644 --- a/contracts/transfer/src/state.rs +++ b/contracts/transfer/src/state.rs @@ -12,26 +12,55 @@ use alloc::collections::btree_map::Entry; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::vec::Vec; +use dusk_bytes::Serializable; use poseidon_merkle::Opening as PoseidonOpening; use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; use rusk_abi::{ContractError, ContractId, PublicInput, STAKE_CONTRACT}; use execution_core::{ transfer::{ - Fee, Mint, SenderAccount, Transaction, TreeLeaf, TRANSFER_TREE_DEPTH, + AccountData, MoonlightTransaction, PhoenixTransaction, Transaction, + TreeLeaf, Withdraw, WithdrawReceiver, WithdrawReplayToken, + WithdrawSignature, TRANSFER_TREE_DEPTH, }, - BlsScalar, Note, + BlsPublicKey, BlsScalar, Note, Sender, }; +use crate::transitory; +use transitory::Deposit; + /// Number of roots stored pub const MAX_ROOTS: usize = 5000; +/// An empty account, used as the default return and for instantiating new +/// entries. +const EMPTY_ACCOUNT: AccountData = AccountData { + nonce: 0, + balance: 0, +}; + +fn contract_fn_sender(fn_name: &str, contract: ContractId) -> Sender { + let mut bytes = [0u8; 128]; + + let mut offset = 0; + + bytes[offset..offset + fn_name.len()].copy_from_slice(fn_name.as_bytes()); + offset += fn_name.len(); + + bytes[offset..offset + 32].copy_from_slice(&contract.to_bytes()); + + Sender::ContractInfo(bytes) +} + pub struct TransferState { tree: Tree, nullifiers: BTreeSet, roots: ConstGenericRingBuffer, - balances: BTreeMap, - deposit: Option<(ContractId, u64)>, + // NOTE: we should never remove entries from this list, since the entries + // contain the nonce of the given account. Doing so opens the account + // up to replay attacks. + accounts: BTreeMap<[u8; BlsPublicKey::SIZE], AccountData>, + contract_balances: BTreeMap, } impl TransferState { @@ -40,63 +69,136 @@ impl TransferState { tree: Tree::new(), nullifiers: BTreeSet::new(), roots: ConstGenericRingBuffer::new(), - balances: BTreeMap::new(), - deposit: None, + accounts: BTreeMap::new(), + contract_balances: BTreeMap::new(), + } + } + + /// Checks the [`Withdraw`] is correct, and mints the amount of the + /// withdrawal. + fn mint_withdrawal(&mut self, fn_name: &str, withdraw: Withdraw) { + let contract = withdraw.contract(); + let value = withdraw.value(); + + let msg = withdraw.signature_message(); + let signature = withdraw.signature(); + + match withdraw.token() { + WithdrawReplayToken::Phoenix(nullifiers) => { + let tx_payload = transitory::unwrap_phoenix_tx().payload(); + + for n in &tx_payload.tx_skeleton.nullifiers { + if !nullifiers.contains(n) { + panic!("Incorrect nullifiers signed"); + } + } + } + WithdrawReplayToken::Moonlight(nonce) => { + let tx_payload = transitory::unwrap_moonlight_tx().payload(); + + if nonce != &tx_payload.nonce { + panic!("Incorrect nonce signed"); + } + } + } + + match withdraw.receiver() { + WithdrawReceiver::Phoenix(address) => { + let signature = match signature { + WithdrawSignature::Phoenix(s) => s, + _ => panic!( + "Withdrawal to Phoenix must be signed with Schnorr" + ), + }; + + let hash = rusk_abi::hash(msg); + let pk = address.note_pk(); + + if !rusk_abi::verify_schnorr(hash, *pk, *signature) { + panic!("Invalid signature"); + } + + let sender = contract_fn_sender( + fn_name, + ContractId::from_bytes(*contract), + ); + + let note = Note::transparent_stealth(*address, value, sender); + self.push_note_current_height(note); + } + WithdrawReceiver::Moonlight(account) => { + let signature = match signature { + WithdrawSignature::Moonlight(s) => s, + _ => panic!( + "Withdrawal to Moonlight must be signed with BLS" + ), + }; + + if !rusk_abi::verify_bls(msg, *account, *signature) { + panic!("Invalid signature"); + } + + let account_bytes = account.to_bytes(); + let account = + self.accounts.entry(account_bytes).or_insert(EMPTY_ACCOUNT); + + account.balance += value; + } } } - /// Mint a new phoenix note. + /// Mint more Dusk. /// - /// This can only be called by the stake-contracts. The method will increase - /// the total amount of circulating dusk. This happens when the reward for - /// staking and participating in the consensus is withdrawn. - pub fn mint(&mut self, mint: Mint) { - let caller = rusk_abi::caller(); - if caller != STAKE_CONTRACT { + /// This can only be called by the stake contract, and will increase the + /// total amount of circulating Dusk. It is intended to be called during the + /// execution of the `withdraw` function, and the amount minted should + /// conform to the consensus emission schedule. + /// + /// # Safety + /// We assume on trust that the value sent by the stake contract is + /// according to consensus rules. + pub fn mint(&mut self, mint: Withdraw) { + if rusk_abi::caller() != STAKE_CONTRACT { panic!("Can only be called by the stake contract!") } - let sender = SenderAccount { - contract: caller.to_bytes(), - account: mint.sender, - }; - let note = Note::transparent_stealth(mint.address, mint.value, sender); + let contract = mint.contract(); + + if mint.contract() != contract { + panic!("Withdrawal should from the stake contract"); + } - self.push_note_current_height(note); + self.mint_withdrawal("MINT", mint); } - /// Withdraw from a contract's balance into a phoenix-note. + /// Withdraw from a contract's balance to a Phoenix note or a Moonlight + /// account. /// - /// Even though a new phoenix-note is minted, the funds are only moved there - /// from the contract's balance. This means that, unlike [`mint`], calling - /// this function will not increase the total amount of circulating dusk. + /// Users sign the `Withdraw` data, which the contract being called + /// (withdrawn from) is then responsible for making available to this + /// contract via a call to this function. The function allows for + /// withdrawals to both Phoenix notes and Moonlight accounts. /// /// # Panics - /// This can only be called by a contract that with sufficient balance. - pub fn withdraw(&mut self, withdraw: Mint) { - // check if the request comes from a contract - let contract = rusk_abi::caller(); - if contract.is_uninitialized() { - panic!("The \"withdraw\" method can only be called by another contract.") + /// This can only be called by the contract specified, and only if said + /// contract has enough balance. + pub fn withdraw(&mut self, withdraw: Withdraw) { + let contract = ContractId::from_bytes(*withdraw.contract()); + + if contract != rusk_abi::caller() { + panic!("The \"withdraw\" function can only be called by the contract specified in the payload"); } - // check if the contract has enough balance - if self.balance(&contract) < withdraw.value { - panic!("The contract doesn't have enough balance."); + let value = withdraw.value(); + + if self.contract_balance(&contract) < value { + panic!("The contract doesn't have enough balance"); } - // subtract the withdraw-value from the contract's balance - self.sub_balance(&contract, withdraw.value) - .expect("The contract should have enough balance"); + self.sub_contract_balance(&contract, value) + .expect("Subtracting balance from contract should succeed"); - // push a new phoenix-note with the given data to the tree - let sender = SenderAccount { - contract: contract.to_bytes(), - account: withdraw.sender, - }; - let note = - Note::transparent_stealth(withdraw.address, withdraw.value, sender); - self.push_note_current_height(note); + self.mint_withdrawal("WITHDRAW", withdraw); } /// Deposit funds to a contract's balance. @@ -115,31 +217,53 @@ impl TransferState { panic!("Only a contract is authorized to claim a deposit.") } - let deposit = self.deposit.take(); + let deposit = transitory::deposit_info_mut(); match deposit { - Some((deposit_contract, deposit_value)) => { + Deposit::Available(deposit_contract, deposit_value) => { + let deposit_contract = *deposit_contract; + let deposit_value = *deposit_value; + if deposit_value != value { panic!( - "The value to deposit doesn't match the previously deposited value" - ); - } else if deposit_contract != caller { - panic!( - "The caller is not authorized to claim the deposit." + "The value to deposit doesn't match the value in the transaction" ); - } else { - self.add_balance(deposit_contract, deposit_value); } + + if deposit_contract != caller { + panic!("The calling contract doesn't match the contract in the transaction"); + } + + // add to the contract's balance and set the deposit as taken + self.add_contract_balance(deposit_contract, deposit_value); + *deposit = Deposit::Taken(deposit_contract, deposit_value); } - None => { - panic!("There is no deposit on the state."); + Deposit::Taken(_, _) => { + panic!("The deposit has already been taken") } + Deposit::None => panic!("There is no deposit in the transaction"), } } - /// Spends the inputs and creates the given UTXO, and executes the contract - /// call if present. It performs all checks necessary to ensure the - /// transaction is valid - hash matches, anchor has been a root of the - /// tree, proof checks out, etc... + /// The top level transaction execution function. + /// + /// Delegates to [`Self::spend_and_execute_phoenix`] and + /// [`Self::spend_and_execute_moonlight`], depending on if the transaction + /// uses the Phoenix or the Moonlight models, respectively. + pub fn spend_and_execute( + &mut self, + tx: Transaction, + ) -> Result, ContractError> { + match tx { + Transaction::Phoenix(tx) => self.spend_and_execute_phoenix(tx), + Transaction::Moonlight(tx) => self.spend_and_execute_moonlight(tx), + } + } + + /// Spends the inputs and creates the given UTXO within the given phoenix + /// transaction, and executes the contract call if present. It performs + /// all checks necessary to ensure the transaction is valid - hash + /// matches, anchor has been a root of the tree, proof checks out, + /// etc... /// /// This will emplace the deposit in the state, if it exists - making it /// available for any contracts called. @@ -153,10 +277,13 @@ impl TransferState { /// change in state. /// /// [`refund`]: [`TransferState::refund`] - pub fn spend_and_execute( + fn spend_and_execute_phoenix( &mut self, - tx: Transaction, + tx: PhoenixTransaction, ) -> Result, ContractError> { + transitory::put_transaction(tx); + let tx = transitory::unwrap_phoenix_tx(); + let tx_skeleton = &tx.payload().tx_skeleton; // panic if the root is invalid @@ -173,7 +300,7 @@ impl TransferState { self.nullifiers.extend(&tx_skeleton.nullifiers); // verify the phoenix-circuit - if !verify_tx_proof(&tx) { + if !verify_tx_proof(tx) { panic!("Invalid transaction proof!"); } @@ -182,26 +309,113 @@ impl TransferState { self.tree .extend_notes(block_height, tx_skeleton.outputs.clone()); - // if present, place the contract deposit on the state - if tx.payload().tx_skeleton.deposit > 0 { - let contract = match &tx.payload().contract_call() { - Some(call) => ContractId::from_bytes(call.contract), - None => { - panic!("There needs to be a contract call when depositing funds"); + // perform contract call if present + let mut result = Ok(Vec::new()); + if let Some(call) = tx.call() { + result = rusk_abi::call_raw( + ContractId::from_bytes(call.contract), + &call.fn_name, + &call.fn_args, + ); + } + + result + } + + /// Spends the amount available to the moonlight transaction, and executes + /// the contract call if present. It performs all checks necessary to ensure + /// the transaction is valid - signature check, available funds, etc... + /// + /// This will emplace the deposit in the state, if it exists - making it + /// available for any contracts called. + /// + /// [`refund`] **must** be called if this function succeeds, otherwise we + /// will have an inconsistent state. + /// + /// # Panics + /// Any failure in the checks performed in processing the transaction will + /// result in a panic. The contract expects the environment to roll back any + /// change in state. + /// + /// [`refund`]: [`TransferState::refund`] + fn spend_and_execute_moonlight( + &mut self, + tx: MoonlightTransaction, + ) -> Result, ContractError> { + transitory::put_transaction(tx); + let tx = transitory::unwrap_moonlight_tx(); + + // check the signature is valid and made by `from` + let payload = tx.payload(); + let signature = *tx.signature(); + + let from = payload.from; + let digest = tx.signature_message(); + + if !rusk_abi::verify_bls(digest, from, signature) { + panic!("Invalid signature!"); + } + + // check `from` has the funds necessary to suppress the total value + // available in this transaction, and that the `nonce` is higher than + // the currently held number. If these conditions are violated we panic + // since the transaction is invalid - either because the account doesn't + // have (enough) funds, or because they're possibly trying to reuse a + // previously used signature (i.e. a replay attack). + // + // Afterwards, we simply deduct the total amount of the transaction from + // the balance, increment the nonce, and rely on `refund` to be called + // after a successful exit. + let from_bytes = from.to_bytes(); // TODO: this is expensive. maybe we should address the + // fact that `BlsPublicKey` doesn't impl `Ord` + // so we can just use it directly as a key in the + // `BTreeMap` + + // the total value carried by a transaction is the sum of the value, the + // deposit, and gas_limit * gas_price. + let total_value = payload.value + + payload.deposit + + payload.gas_limit * payload.gas_price; + + match self.accounts.get_mut(&from_bytes) { + Some(account) => { + if total_value > account.balance { + panic!("Account doesn't have enough funds"); + } + if payload.nonce <= account.nonce { + panic!("Replayed nonce"); } + + account.balance -= total_value; + account.nonce = payload.nonce; + } + None => panic!("Account has no funds"), + } + + // if there is a value carried by the transaction but no key specified + // in the `to` field, we just give the value back to `from`. + if payload.value > 0 { + let key = match payload.to { + Some(to) => to.to_bytes(), + None => from_bytes, }; - self.deposit = Some((contract, tx.payload().tx_skeleton.deposit)); + + // if the key has no entry, we simply instantiate a new one with a + // zero nonce and balance. + let account = self.accounts.entry(key).or_insert(EMPTY_ACCOUNT); + account.balance += payload.value; } // perform contract call if present let mut result = Ok(Vec::new()); - if let Some(call) = &tx.payload().contract_call() { + if let Some(call) = tx.call() { result = rusk_abi::call_raw( ContractId::from_bytes(call.contract), &call.fn_name, &call.fn_args, ); } + result } @@ -211,17 +425,52 @@ impl TransferState { /// in the fee structure. /// /// This function guarantees that it will not panic. - pub fn refund(&mut self, fee: Fee, gas_spent: u64) { - let deposit = self.deposit.map(|(_, d)| d); + pub fn refund(&mut self, gas_spent: u64) { + let tx = transitory::unwrap_tx(); + + // If there is a deposit still available on the call to this function, + // we refund it to the called. + let deposit = match transitory::deposit_info() { + Deposit::Available(_, deposit) => Some(*deposit), + _ => None, + }; - let remainder_note = fee.gen_remainder_note(gas_spent, deposit); + // in phoenix, a refund note is with the unspent amount to the stealth + // address in the `Fee` structure, while in moonlight we simply refund + // the `from` account for what it didn't spend + // + // any eventual deposit that failed to be "picked up" is refunded in the + // same way - in phoenix the same note is reused, in moonlight the + // 'key's balance gets increased. + match tx { + Transaction::Phoenix(tx) => { + let fee = &tx.payload().fee; + + let remainder_note = fee.gen_remainder_note(gas_spent, deposit); + + let remainder_value = remainder_note + .value(None) + .expect("Should always succeed for a transparent note"); + + if remainder_value > 0 { + self.push_note_current_height(remainder_note); + } + } + Transaction::Moonlight(tx) => { + let payload = tx.payload(); - let remainder_value = remainder_note - .value(None) - .expect("Should always succeed for a transparent note"); + let from_bytes = payload.from.to_bytes(); - if remainder_value > 0 { - self.push_note_current_height(remainder_note); + let remaining_gas = payload.gas_limit - gas_spent; + let remaining = remaining_gas * payload.gas_price + + deposit.unwrap_or_default(); + + let account = self.accounts.get_mut(&from_bytes).expect( + "The account that just transacted must have an entry", + ); + + account.balance += remaining; + } } } @@ -289,14 +538,38 @@ impl TransferState { .collect() } + pub fn account(&self, key: &BlsPublicKey) -> AccountData { + let key_bytes = key.to_bytes(); + self.accounts + .get(&key_bytes) + .cloned() + .unwrap_or(EMPTY_ACCOUNT) + } + + pub fn add_account_balance(&mut self, key: &BlsPublicKey, value: u64) { + let key_bytes = key.to_bytes(); + let account = self.accounts.entry(key_bytes).or_insert(EMPTY_ACCOUNT); + account.balance = account.balance.saturating_add(value); + } + + pub fn sub_account_balance(&mut self, key: &BlsPublicKey, value: u64) { + let key_bytes = key.to_bytes(); + if let Some(account) = self.accounts.get_mut(&key_bytes) { + account.balance = account.balance.saturating_sub(value); + } + } + /// Return the balance of a given contract. - pub fn balance(&self, contract_id: &ContractId) -> u64 { - self.balances.get(contract_id).copied().unwrap_or_default() + pub fn contract_balance(&self, contract_id: &ContractId) -> u64 { + self.contract_balances + .get(contract_id) + .copied() + .unwrap_or_default() } /// Add balance to the given contract - pub fn add_balance(&mut self, contract: ContractId, value: u64) { - match self.balances.entry(contract) { + pub fn add_contract_balance(&mut self, contract: ContractId, value: u64) { + match self.contract_balances.entry(contract) { Entry::Vacant(ve) => { ve.insert(value); } @@ -307,6 +580,28 @@ impl TransferState { } } + pub(crate) fn sub_contract_balance( + &mut self, + address: &ContractId, + value: u64, + ) -> Result<(), Error> { + match self.contract_balances.get_mut(address) { + Some(balance) => { + let (bal, underflow) = balance.overflowing_sub(value); + + if underflow { + Err(Error::NotEnoughBalance) + } else { + *balance = bal; + + Ok(()) + } + } + + _ => Err(Error::NotEnoughBalance), + } + } + fn get_note(&self, pos: u64) -> Option { self.tree.get(pos).map(|l| l.note) } @@ -329,31 +624,9 @@ impl TransferState { let block_height = rusk_abi::block_height(); self.push_note(block_height, note) } - - pub(crate) fn sub_balance( - &mut self, - address: &ContractId, - value: u64, - ) -> Result<(), Error> { - match self.balances.get_mut(address) { - Some(balance) => { - let (bal, underflow) = balance.overflowing_sub(value); - - if underflow { - Err(Error::NotEnoughBalance) - } else { - *balance = bal; - - Ok(()) - } - } - - _ => Err(Error::NotEnoughBalance), - } - } } -fn verify_tx_proof(tx: &Transaction) -> bool { +fn verify_tx_proof(tx: &PhoenixTransaction) -> bool { let pis: Vec = tx.public_inputs().iter().map(|pi| pi.into()).collect(); diff --git a/contracts/transfer/src/transitory.rs b/contracts/transfer/src/transitory.rs new file mode 100644 index 0000000000..75297445ad --- /dev/null +++ b/contracts/transfer/src/transitory.rs @@ -0,0 +1,114 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use rusk_abi::ContractId; + +use execution_core::transfer::{ + MoonlightTransaction, PhoenixTransaction, Transaction, +}; + +/// The state of a deposit while a transaction is executing. +pub enum Deposit { + /// There is a deposit and its still available for pick up. + Available(ContractId, u64), + /// There is a deposit and it has already been picked up. + Taken(ContractId, u64), + /// There is no deposit. + None, +} + +/// The fields kept here are transitory, in the sense that they only "live" +/// while the transaction is being executed. +struct CurrentTransaction { + tx: Transaction, + deposit: Deposit, +} + +static mut CURRENT_TX: Option = None; + +/// Get a reference to the deposit information for the currently ongoing +/// transaction. +pub fn deposit_info() -> &'static Deposit { + unsafe { + &CURRENT_TX + .as_ref() + .expect("There must be an ongoing transaction") + .deposit + } +} + +/// Get a mutable reference to the deposit information for the currently ongoing +/// transaction. +pub fn deposit_info_mut() -> &'static mut Deposit { + unsafe { + &mut CURRENT_TX + .as_mut() + .expect("There must be an ongoing transaction") + .deposit + } +} + +/// Insert the transaction into the state. +pub fn put_transaction(tx: impl Into) { + unsafe { + let tx = tx.into(); + + let d = tx.deposit(); + + let mut deposit = Deposit::None; + if d > 0 { + let contract = tx + .call() + .map(|call| ContractId::from_bytes(call.contract)) + .expect("There must be a contract when depositing funds"); + + // When a transaction is initially inserted, any deposit is + // available for pick up. + deposit = Deposit::Available(contract, d); + } + + CURRENT_TX = Some(CurrentTransaction { tx, deposit }); + } +} + +pub fn unwrap_tx() -> &'static Transaction { + unsafe { + &CURRENT_TX + .as_ref() + .expect("There must be an ongoing transaction") + .tx + } +} + +/// Unwrap ongoing transaction in the state, assuming it's Moonlight. +pub fn unwrap_moonlight_tx() -> &'static MoonlightTransaction { + unsafe { + let tx = &CURRENT_TX + .as_ref() + .expect("There must be an ongoing transaction") + .tx; + + match tx { + Transaction::Moonlight(ref tx) => tx, + _ => panic!("Expected Moonlight TX, found Phoenix"), + } + } +} + +/// Unwrap ongoing transaction in the state, assuming it's Phoenix. +pub fn unwrap_phoenix_tx() -> &'static PhoenixTransaction { + unsafe { + let tx = &CURRENT_TX + .as_ref() + .expect("There must be an ongoing transaction") + .tx; + + match tx { + Transaction::Phoenix(ref tx) => tx, + _ => panic!("Expected Phoenix TX, found Moonlight"), + } + } +} diff --git a/contracts/transfer/tests/common.rs b/contracts/transfer/tests/common.rs index 61d6f16d1d..b5c8aad7e7 100644 --- a/contracts/transfer/tests/common.rs +++ b/contracts/transfer/tests/common.rs @@ -8,10 +8,12 @@ use std::sync::mpsc; use execution_core::{ transfer::{ - ContractCall, Fee, Payload, Transaction, TreeLeaf, TRANSFER_TREE_DEPTH, + AccountData, ContractCall, Fee, MoonlightPayload, MoonlightTransaction, + PhoenixPayload, PhoenixTransaction, Transaction, TreeLeaf, + TRANSFER_TREE_DEPTH, }, - value_commitment, BlsScalar, JubJubScalar, Note, PublicKey, - SchnorrSecretKey, SecretKey, Sender, TxSkeleton, ViewKey, + value_commitment, BlsPublicKey, BlsScalar, BlsSecretKey, JubJubScalar, + Note, PublicKey, SchnorrSecretKey, SecretKey, Sender, TxSkeleton, ViewKey, }; use rusk_abi::{ContractError, ContractId, Error, Session, TRANSFER_CONTRACT}; @@ -24,7 +26,7 @@ use poseidon_merkle::Opening as PoseidonOpening; use rand::rngs::StdRng; use rand::SeedableRng; -const POINT_LIMIT: u64 = 0x10_000_000; +const GAS_LIMIT: u64 = 0x10_000_000; pub fn leaves_from_height( session: &mut Session, @@ -36,7 +38,7 @@ pub fn leaves_from_height( TRANSFER_CONTRACT, "leaves_from_height", &height, - POINT_LIMIT, + GAS_LIMIT, feeder, )?; @@ -56,7 +58,7 @@ pub fn leaves_from_pos( TRANSFER_CONTRACT, "leaves_from_pos", &pos, - POINT_LIMIT, + GAS_LIMIT, feeder, )?; @@ -74,13 +76,22 @@ pub fn num_notes(session: &mut Session) -> Result { pub fn update_root(session: &mut Session) -> Result<(), Error> { session - .call(TRANSFER_CONTRACT, "update_root", &(), POINT_LIMIT) + .call(TRANSFER_CONTRACT, "update_root", &(), GAS_LIMIT) .map(|r| r.data) } pub fn root(session: &mut Session) -> Result { session - .call(TRANSFER_CONTRACT, "root", &(), POINT_LIMIT) + .call(TRANSFER_CONTRACT, "root", &(), GAS_LIMIT) + .map(|r| r.data) +} + +pub fn account( + session: &mut Session, + pk: &BlsPublicKey, +) -> Result { + session + .call(TRANSFER_CONTRACT, "account", pk, GAS_LIMIT) .map(|r| r.data) } @@ -89,12 +100,7 @@ pub fn contract_balance( contract: ContractId, ) -> Result { session - .call( - TRANSFER_CONTRACT, - "contract_balance", - &contract, - POINT_LIMIT, - ) + .call(TRANSFER_CONTRACT, "contract_balance", &contract, GAS_LIMIT) .map(|r| r.data) } @@ -103,7 +109,7 @@ pub fn opening( pos: u64, ) -> Result>, Error> { session - .call(TRANSFER_CONTRACT, "opening", &pos, POINT_LIMIT) + .call(TRANSFER_CONTRACT, "opening", &pos, GAS_LIMIT) .map(|r| r.data) } @@ -132,12 +138,17 @@ pub fn prover_verifier(input_notes: usize) -> (Prover, Verifier) { /// Executes a transaction. /// Returns result containing gas spent. -pub fn execute(session: &mut Session, tx: Transaction) -> Result { +pub fn execute( + session: &mut Session, + tx: impl Into, +) -> Result { + let tx = tx.into(); + let mut receipt = session.call::<_, Result, ContractError>>( TRANSFER_CONTRACT, "spend_and_execute", &tx, - tx.payload().fee.gas_limit, + tx.gas_limit(), )?; // Ensure all gas is consumed if there's an error in the contract call @@ -149,7 +160,7 @@ pub fn execute(session: &mut Session, tx: Transaction) -> Result { .call::<_, ()>( TRANSFER_CONTRACT, "refund", - &(tx.payload().fee, receipt.gas_spent), + &receipt.gas_spent, u64::MAX, ) .expect("Refunding must succeed"); @@ -169,9 +180,42 @@ pub fn filter_notes_owned_by>( .collect() } +pub fn create_moonlight_transaction( + session: &mut Session, + from_sk: &BlsSecretKey, + to: Option, + value: u64, + deposit: u64, + gas_limit: u64, + gas_price: u64, + exec: Option>, +) -> MoonlightTransaction { + let from = BlsPublicKey::from(from_sk); + + let account = + account(session, &from).expect("Getting the account should work"); + let nonce = account.nonce + 1; + + let payload = MoonlightPayload { + from, + to, + value, + deposit, + gas_limit, + gas_price, + nonce, + exec: exec.map(Into::into), + }; + + let digest = payload.to_hash_input_bytes(); + let signature = from_sk.sign(&from, &digest); + + MoonlightTransaction::new(payload, signature) +} + /// Generate a TxCircuit given the sender secret-key, receiver public-key, the /// input note positions in the transaction tree and the new output-notes. -pub fn create_transaction( +pub fn create_phoenix_transaction( session: &mut Session, sender_sk: &SecretKey, receiver_pk: &PublicKey, @@ -182,7 +226,7 @@ pub fn create_transaction( is_obfuscated: bool, deposit: u64, contract_call: Option, -) -> Transaction { +) -> PhoenixTransaction { let mut rng = StdRng::seed_from_u64(0xfeeb); let sender_vk = ViewKey::from(sender_sk); let sender_pk = PublicKey::from(sender_sk); @@ -284,10 +328,10 @@ pub fn create_transaction( deposit, }; - let tx_payload = Payload { + let tx_payload = PhoenixPayload { tx_skeleton, fee, - contract_exec: (contract_call.map(|c| ContractExec::Call(c))), + exec: (contract_call.map(|c| ContractExec::Call(c))), }; let payload_hash = tx_payload.hash(); @@ -374,5 +418,5 @@ pub fn create_transaction( .expect("creating a proof should succeed"); // build the transaction from the payload and proof - Transaction::new(tx_payload, proof.to_bytes()) + PhoenixTransaction::new(tx_payload, proof.to_bytes()) } diff --git a/contracts/transfer/tests/transfer.rs b/contracts/transfer/tests/transfer.rs index d4bc397358..c145687703 100644 --- a/contracts/transfer/tests/transfer.rs +++ b/contracts/transfer/tests/transfer.rs @@ -7,7 +7,8 @@ pub mod common; use crate::common::{ - contract_balance, create_transaction, execute, filter_notes_owned_by, + account, contract_balance, create_moonlight_transaction, + create_phoenix_transaction, execute, filter_notes_owned_by, leaves_from_height, leaves_from_pos, num_notes, update_root, }; @@ -17,15 +18,20 @@ use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; use execution_core::{ - transfer::{ContractCall, Mint}, - BlsPublicKey, BlsScalar, BlsSecretKey, JubJubScalar, Note, PublicKey, - SecretKey, ViewKey, + transfer::{ + ContractCall, ContractExec, Withdraw, WithdrawReceiver, + WithdrawReplayToken, + }, + BlsPublicKey, BlsSecretKey, JubJubScalar, Note, PublicKey, SecretKey, + ViewKey, }; use rusk_abi::dusk::{dusk, LUX}; use rusk_abi::{ContractData, ContractId, Session, TRANSFER_CONTRACT, VM}; -const GENESIS_VALUE: u64 = dusk(1_000.0); -const POINT_LIMIT: u64 = 0x10000000; +const PHOENIX_GENESIS_VALUE: u64 = dusk(1_000.0); +const MOONLIGHT_GENESIS_VALUE: u64 = dusk(1_000.0); + +const GAS_LIMIT: u64 = 0x10000000; const ALICE_ID: ContractId = { let mut bytes = [0u8; 32]; @@ -45,7 +51,8 @@ const OWNER: [u8; 32] = [0; 32]; fn instantiate( rng: &mut Rng, vm: &VM, - pk: &PublicKey, + phoenix_pk: &PublicKey, + moonlight_pk: &BlsPublicKey, ) -> Session { let transfer_bytecode = include_bytes!( "../../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm" @@ -65,7 +72,7 @@ fn instantiate( ContractData::builder() .owner(OWNER) .contract_id(TRANSFER_CONTRACT), - POINT_LIMIT, + GAS_LIMIT, ) .expect("Deploying the transfer contract should succeed"); @@ -73,7 +80,7 @@ fn instantiate( .deploy( alice_bytecode, ContractData::builder().owner(OWNER).contract_id(ALICE_ID), - POINT_LIMIT, + GAS_LIMIT, ) .expect("Deploying the alice contract should succeed"); @@ -81,7 +88,7 @@ fn instantiate( .deploy( bob_bytecode, ContractData::builder().owner(OWNER).contract_id(BOB_ID), - POINT_LIMIT, + GAS_LIMIT, ) .expect("Deploying the bob contract should succeed"); @@ -89,21 +96,36 @@ fn instantiate( JubJubScalar::random(&mut *rng), JubJubScalar::random(&mut *rng), ]; - let genesis_note = - Note::transparent(rng, pk, pk, GENESIS_VALUE, sender_blinder); + let genesis_note = Note::transparent( + rng, + phoenix_pk, + phoenix_pk, + PHOENIX_GENESIS_VALUE, + sender_blinder, + ); - // push genesis note to the contract + // push genesis phoenix note to the contract session .call::<_, Note>( TRANSFER_CONTRACT, "push_note", &(0u64, genesis_note), - POINT_LIMIT, + GAS_LIMIT, ) .expect("Pushing genesis note should succeed"); update_root(&mut session).expect("Updating the root should succeed"); + // insert genesis moonlight account + session + .call::<_, ()>( + TRANSFER_CONTRACT, + "add_account_balance", + &(*moonlight_pk, MOONLIGHT_GENESIS_VALUE), + GAS_LIMIT, + ) + .expect("Inserting genesis account should succeed"); + // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); @@ -112,7 +134,7 @@ fn instantiate( } #[test] -fn transfer() { +fn phoenix_transfer() { const TRANSFER_FEE: u64 = dusk(1.0); let rng = &mut StdRng::seed_from_u64(0xfeeb); @@ -120,12 +142,15 @@ fn transfer() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let sender_sk = SecretKey::random(rng); - let sender_pk = PublicKey::from(&sender_sk); + let phoenix_sender_sk = SecretKey::random(rng); + let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + + let phoenix_receiver_pk = PublicKey::from(&SecretKey::random(rng)); - let receiver_pk = PublicKey::from(&SecretKey::random(rng)); + let moonlight_sk = BlsSecretKey::random(rng); + let moonlight_pk = BlsPublicKey::from(&moonlight_sk); - let session = &mut instantiate(rng, vm, &sender_pk); + let session = &mut instantiate(rng, vm, &phoenix_sender_pk, &moonlight_pk); let leaves = leaves_from_height(session, 0) .expect("Getting leaves in the given range should succeed"); @@ -149,10 +174,10 @@ fn transfer() { let deposit = 0; let contract_call = None; - let tx = create_transaction( + let tx = create_phoenix_transaction( session, - &sender_sk, - &receiver_pk, + &phoenix_sender_sk, + &phoenix_receiver_pk, gas_limit, gas_price, [input_note_pos], @@ -193,7 +218,71 @@ fn transfer() { } #[test] -fn alice_ping() { +fn moonlight_transfer() { + const TRANSFER_VALUE: u64 = dusk(1.0); + + let rng = &mut StdRng::seed_from_u64(0xfeeb); + + let vm = &mut rusk_abi::new_ephemeral_vm() + .expect("Creating ephemeral VM should work"); + + let phoenix_pk = PublicKey::from(&SecretKey::random(rng)); + + let moonlight_sender_sk = BlsSecretKey::random(rng); + let moonlight_sender_pk = BlsPublicKey::from(&moonlight_sender_sk); + + let moonlight_receiver_pk = BlsPublicKey::from(&BlsSecretKey::random(rng)); + + let session = &mut instantiate(rng, vm, &phoenix_pk, &moonlight_sender_pk); + + let sender_account = account(session, &moonlight_sender_pk) + .expect("Getting the sender account should succeed"); + let receiver_account = account(session, &moonlight_receiver_pk) + .expect("Getting the receiver account should succeed"); + + assert_eq!( + sender_account.balance, MOONLIGHT_GENESIS_VALUE, + "The sender account should have the genesis value" + ); + assert_eq!( + receiver_account.balance, 0, + "The receiver account should be empty" + ); + + let transaction = create_moonlight_transaction( + session, + &moonlight_sender_sk, + Some(moonlight_receiver_pk), + TRANSFER_VALUE, + 0, + GAS_LIMIT, + LUX, + None::, + ); + + let gas_spent = + execute(session, transaction).expect("Transaction should succeed"); + + println!("MOONLIGHT TRANSFER: {} gas", gas_spent); + + let sender_account = account(session, &moonlight_sender_pk) + .expect("Getting the sender account should succeed"); + let receiver_account = account(session, &moonlight_receiver_pk) + .expect("Getting the receiver account should succeed"); + + assert_eq!( + sender_account.balance, + MOONLIGHT_GENESIS_VALUE - gas_spent - TRANSFER_VALUE, + "The sender account should decrease by the amount spent" + ); + assert_eq!( + receiver_account.balance, TRANSFER_VALUE, + "The receiver account should have the transferred value" + ); +} + +#[test] +fn phoenix_alice_ping() { const PING_FEE: u64 = dusk(1.0); let rng = &mut StdRng::seed_from_u64(0xfeeb); @@ -201,10 +290,13 @@ fn alice_ping() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let sender_sk = SecretKey::random(rng); - let sender_pk = PublicKey::from(&sender_sk); + let phoenix_sender_sk = SecretKey::random(rng); + let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + + let moonlight_sk = BlsSecretKey::random(rng); + let moonlight_pk = BlsPublicKey::from(&moonlight_sk); - let session = &mut instantiate(rng, vm, &sender_pk); + let session = &mut instantiate(rng, vm, &phoenix_sender_pk, &moonlight_pk); let leaves = leaves_from_height(session, 0) .expect("Getting leaves in the given range should succeed"); @@ -224,10 +316,10 @@ fn alice_ping() { fn_args: vec![], }); - let tx = create_transaction( + let tx = create_phoenix_transaction( session, - &sender_sk, - &sender_pk, + &phoenix_sender_sk, + &phoenix_sender_pk, gas_limit, gas_price, [input_note_pos], @@ -254,7 +346,61 @@ fn alice_ping() { } #[test] -fn deposit_and_withdraw() { +fn moonlight_alice_ping() { + let rng = &mut StdRng::seed_from_u64(0xfeeb); + + let vm = &mut rusk_abi::new_ephemeral_vm() + .expect("Creating ephemeral VM should work"); + + let phoenix_pk = PublicKey::from(&SecretKey::random(rng)); + + let moonlight_sk = BlsSecretKey::random(rng); + let moonlight_pk = BlsPublicKey::from(&moonlight_sk); + + let session = &mut instantiate(rng, vm, &phoenix_pk, &moonlight_pk); + + let acc = account(session, &moonlight_pk) + .expect("Getting the sender account should succeed"); + + let contract_call = Some(ContractCall { + contract: ALICE_ID.to_bytes(), + fn_name: String::from("ping"), + fn_args: vec![], + }); + + assert_eq!( + acc.balance, MOONLIGHT_GENESIS_VALUE, + "The account should have the genesis value" + ); + + let transaction = create_moonlight_transaction( + session, + &moonlight_sk, + None, + 0, + 0, + GAS_LIMIT, + LUX, + contract_call, + ); + + let gas_spent = + execute(session, transaction).expect("Transaction should succeed"); + + println!("MOONLIGHT PING: {} gas", gas_spent); + + let acc = account(session, &moonlight_pk) + .expect("Getting the account should succeed"); + + assert_eq!( + acc.balance, + MOONLIGHT_GENESIS_VALUE - gas_spent, + "The account should decrease by the amount spent" + ); +} + +#[test] +fn phoenix_deposit_and_withdraw() { const DEPOSIT_FEE: u64 = dusk(1.0); const WITHDRAW_FEE: u64 = dusk(1.0); @@ -263,11 +409,14 @@ fn deposit_and_withdraw() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let sender_sk = SecretKey::random(rng); - let sender_vk = ViewKey::from(&sender_sk); - let sender_pk = PublicKey::from(&sender_sk); + let phoenix_sender_sk = SecretKey::random(rng); + let phoenix_sender_vk = ViewKey::from(&phoenix_sender_sk); + let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); + + let moonlight_sk = BlsSecretKey::random(rng); + let moonlight_pk = BlsPublicKey::from(&moonlight_sk); - let session = &mut instantiate(rng, vm, &sender_pk); + let session = &mut instantiate(rng, vm, &phoenix_sender_pk, &moonlight_pk); let leaves = leaves_from_height(session, 0) .expect("Getting leaves in the given range should succeed"); @@ -280,17 +429,17 @@ fn deposit_and_withdraw() { let input_note_pos = 0; let transfer_value = 0; let is_obfuscated = false; - let deposit_value = GENESIS_VALUE / 2; + let deposit_value = PHOENIX_GENESIS_VALUE / 2; let contract_call = Some(ContractCall { contract: ALICE_ID.to_bytes(), fn_name: String::from("deposit"), fn_args: deposit_value.to_bytes().into(), }); - let tx = create_transaction( + let tx = create_phoenix_transaction( session, - &sender_sk, - &sender_pk, + &phoenix_sender_sk, + &phoenix_sender_pk, gas_limit, gas_price, [input_note_pos], @@ -309,12 +458,12 @@ fn deposit_and_withdraw() { let leaves = leaves_from_height(session, 1) .expect("Getting the notes should succeed"); assert_eq!( - GENESIS_VALUE, + PHOENIX_GENESIS_VALUE, transfer_value + tx.payload().tx_skeleton.deposit + tx.payload().tx_skeleton.max_fee + tx.payload().tx_skeleton.outputs[1] - .value(Some(&ViewKey::from(&sender_sk))) + .value(Some(&ViewKey::from(&phoenix_sender_sk))) .unwrap() ); assert_eq!( @@ -339,7 +488,7 @@ fn deposit_and_withdraw() { // transfer contract let input_notes = filter_notes_owned_by( - sender_vk, + phoenix_sender_vk, leaves.into_iter().map(|leaf| leaf.note), ); @@ -348,14 +497,22 @@ fn deposit_and_withdraw() { 2, "All new notes should be owned by our view key" ); - let alice_user_account = - BlsPublicKey::from(&BlsSecretKey::from(BlsScalar::from(42))); - let mint = Mint { - value: (GENESIS_VALUE / 2), - address: sender_pk - .gen_stealth_address(&JubJubScalar::random(&mut *rng)), - sender: alice_user_account, - }; + + let address = + phoenix_sender_pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + let note_sk = phoenix_sender_sk.gen_note_sk(&address); + + let withdraw = Withdraw::new( + rng, + ¬e_sk, + ALICE_ID.to_bytes(), + PHOENIX_GENESIS_VALUE / 2, + WithdrawReceiver::Phoenix(address), + WithdrawReplayToken::Phoenix(vec![ + input_notes[0].gen_nullifier(&phoenix_sender_sk), + input_notes[1].gen_nullifier(&phoenix_sender_sk), + ]), + ); let gas_limit = WITHDRAW_FEE; let gas_price = LUX; @@ -366,15 +523,15 @@ fn deposit_and_withdraw() { let contract_call = Some(ContractCall { contract: ALICE_ID.to_bytes(), fn_name: String::from("withdraw"), - fn_args: rkyv::to_bytes::<_, 1024>(&mint) + fn_args: rkyv::to_bytes::<_, 1024>(&withdraw) .expect("should serialize Mint correctly") .to_vec(), }); - let tx = create_transaction( + let tx = create_phoenix_transaction( session, - &sender_sk, - &sender_pk, + &phoenix_sender_sk, + &phoenix_sender_pk, gas_limit, gas_price, input_notes_pos.try_into().unwrap(), From ba2c805a861591bd4c08b0a1458940638bad3fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:24:14 +0200 Subject: [PATCH 04/12] stake-contract: rework to use new withdrawal system We rework the contract to make use of the changed data structures. Most notably, `mint` and `withdraw` now make use of the new withdrawals system. --- contracts/stake/benches/get_provisioners.rs | 14 +- contracts/stake/src/lib.rs | 5 +- contracts/stake/src/state.rs | 326 ++++++++++---------- contracts/stake/tests/common/assert.rs | 13 +- contracts/stake/tests/common/utils.rs | 16 +- contracts/stake/tests/events.rs | 25 +- contracts/stake/tests/stake.rs | 139 +++++---- 7 files changed, 283 insertions(+), 255 deletions(-) diff --git a/contracts/stake/benches/get_provisioners.rs b/contracts/stake/benches/get_provisioners.rs index 4cf36788c7..aae8881f24 100644 --- a/contracts/stake/benches/get_provisioners.rs +++ b/contracts/stake/benches/get_provisioners.rs @@ -5,7 +5,7 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use criterion::{criterion_group, criterion_main, Criterion}; -use execution_core::{stake::StakeData, StakePublicKey, StakeSecretKey}; +use execution_core::{stake::StakeData, BlsPublicKey, BlsSecretKey}; use rand::rngs::StdRng; use rand::{CryptoRng, RngCore, SeedableRng}; use rusk_abi::{ @@ -66,7 +66,7 @@ fn instantiate(vm: &VM) -> Session { fn do_get_provisioners( session: &mut Session, -) -> Result, Error> { +) -> Result, Error> { let (sender, receiver) = mpsc::channel(); session.feeder_call::<_, ()>( STAKE_CONTRACT, @@ -76,7 +76,7 @@ fn do_get_provisioners( sender, )?; Ok(receiver.into_iter().map(|bytes| { - rkyv::from_bytes::<(StakePublicKey, StakeData)>(&bytes) + rkyv::from_bytes::<(BlsPublicKey, StakeData)>(&bytes) .expect("The contract should only return (pk, stake_data) tuples") })) } @@ -87,11 +87,11 @@ fn do_insert_stake( ) -> Result<(), Error> { let stake_data = StakeData { amount: Some((TEST_STAKE, 0)), - counter: 1, + nonce: 1, reward: 0, }; - let sk = StakeSecretKey::random(rng); - let pk = StakePublicKey::from(&sk); + let sk = BlsSecretKey::random(rng); + let pk = BlsPublicKey::from(&sk); session.call::<_, ()>( STAKE_CONTRACT, "insert_stake", @@ -116,7 +116,7 @@ fn get_provisioners(c: &mut Criterion) { c.bench_function("get_provisioners", |b| { b.iter(|| { - let _: Vec<(StakePublicKey, StakeData)> = + let _: Vec<(BlsPublicKey, StakeData)> = do_get_provisioners(&mut session) .expect("getting provisioners should succeed") .collect(); diff --git a/contracts/stake/src/lib.rs b/contracts/stake/src/lib.rs index a52d0e8b7f..10405844c8 100644 --- a/contracts/stake/src/lib.rs +++ b/contracts/stake/src/lib.rs @@ -18,7 +18,6 @@ use state::StakeState; /// The minimum amount of Dusk one can stake. pub const MINIMUM_STAKE: Dusk = dusk(1_000.0); -use execution_core::StakePublicKey; use rusk_abi::{ContractId, PaymentInfo}; #[no_mangle] @@ -56,9 +55,7 @@ unsafe fn withdraw(arg_len: u32) -> u32 { #[no_mangle] unsafe fn get_stake(arg_len: u32) -> u32 { - rusk_abi::wrap_call(arg_len, |pk: StakePublicKey| { - STATE.get_stake(&pk).cloned() - }) + rusk_abi::wrap_call(arg_len, |pk| STATE.get_stake(&pk).cloned()) } #[no_mangle] diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index 6e0c884e23..b106c80166 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -11,11 +11,10 @@ use dusk_bytes::Serializable; use execution_core::{ stake::{ - next_epoch, Stake, StakeData, StakingEvent, Unstake, Withdraw, EPOCH, + next_epoch, Stake, StakeAmount, StakeData, StakeEvent, Withdraw, EPOCH, STAKE_WARNINGS, }, - transfer::Mint, - StakePublicKey, + BlsPublicKey, }; use rusk_abi::{STAKE_CONTRACT, TRANSFER_CONTRACT}; @@ -31,12 +30,10 @@ use crate::*; /// valid stake. #[derive(Debug, Default, Clone)] pub struct StakeState { - stakes: BTreeMap<[u8; StakePublicKey::SIZE], (StakeData, StakePublicKey)>, + stakes: BTreeMap<[u8; BlsPublicKey::SIZE], (StakeData, BlsPublicKey)>, burnt_amount: u64, - previous_block_state: BTreeMap< - [u8; StakePublicKey::SIZE], - (Option, StakePublicKey), - >, + previous_block_state: + BTreeMap<[u8; BlsPublicKey::SIZE], (Option, BlsPublicKey)>, // This is needed just to keep track of blocks to automatically clear the // prev_block_state. Future implementations will rely on // `before_state_transition` to handle that @@ -70,166 +67,174 @@ impl StakeState { pub fn stake(&mut self, stake: Stake) { self.check_new_block(); - if stake.value < MINIMUM_STAKE { - panic!("The staked value is lower than the minimum amount!"); - } + let value = stake.value(); + let account = *stake.account(); + let nonce = stake.nonce(); + let signature = *stake.signature(); - // allot a stake to the given key and increment the signature counter - let loaded_stake = self.load_or_create_stake_mut(&stake.public_key); + let loaded_stake = self.load_or_create_stake_mut(&account); - let counter = loaded_stake.counter(); + // ensure the stake is at least the minimum and that there isn't an + // amount staked already + if value < MINIMUM_STAKE { + panic!("The staked value is lower than the minimum amount!"); + } - loaded_stake.increment_counter(); - loaded_stake.insert_amount(stake.value, rusk_abi::block_height()); + if loaded_stake.amount.is_some() { + panic!("Can't stake twice for the same key"); + } - // verify the signature is over the correct digest - let digest = Stake::signature_message(counter, stake.value).to_vec(); + // check signature and nonce used are correct + if nonce <= loaded_stake.nonce { + panic!("Replayed nonce"); + } - if !rusk_abi::verify_bls(digest, stake.public_key, stake.signature) { + let digest = stake.signature_message().to_vec(); + if !rusk_abi::verify_bls(digest, account, signature) { panic!("Invalid signature!"); } // make call to transfer contract to transfer balance from the user to // this contract - let _: () = rusk_abi::call(TRANSFER_CONTRACT, "deposit", &stake.value) - .expect("Depositing funds into contract should succeed"); + let _: () = + rusk_abi::call::<_, ()>(TRANSFER_CONTRACT, "deposit", &value) + .expect("Depositing funds into contract should succeed"); + + // update the state accordingly + loaded_stake.nonce = nonce; + loaded_stake.amount = + Some(StakeAmount::new(value, rusk_abi::block_height())); rusk_abi::emit( "stake", - StakingEvent { - public_key: stake.public_key, - value: stake.value, + StakeEvent { + account, + value, + receiver: None, }, ); - let key = stake.public_key.to_bytes(); + let key = account.to_bytes(); self.previous_block_state .entry(key) - .or_insert((None, stake.public_key)); + .or_insert((None, account)); } - pub fn unstake(&mut self, unstake: Unstake) { + pub fn unstake(&mut self, unstake: Withdraw) { self.check_new_block(); - // remove the stake from a key and increment the signature counter + let transfer_withdraw = unstake.transfer_withdraw(); + let account = *unstake.account(); + let value = transfer_withdraw.value(); + let signature = *unstake.signature(); + let loaded_stake = self - .get_stake_mut(&unstake.public_key) + .get_stake_mut(&account) .expect("A stake should exist in the map to be unstaked!"); + let prev_stake = Some(*loaded_stake); - let prev_value = Some(loaded_stake.clone()); + // ensure there is a value staked, and that the withdrawal is exactly + // the same amount + let staked_value = loaded_stake + .amount + .expect("There must be an amount to unstake") + .value; - let counter = loaded_stake.counter(); - - let (value, _) = loaded_stake.remove_amount(); - loaded_stake.increment_counter(); - - // verify signature - let digest = - Unstake::signature_message(counter, value, unstake.address) - .to_vec(); + if value != staked_value { + panic!("Value withdrawn different from staked amount"); + } - if !rusk_abi::verify_bls(digest, unstake.public_key, unstake.signature) - { + // check signature is correct + let digest = unstake.signature_message().to_vec(); + if !rusk_abi::verify_bls(digest, account, signature) { panic!("Invalid signature!"); } - // make call to transfer contract to withdraw a note from this contract - // containing the value of the stake - let withdraw_data = Mint { - value, - address: unstake.address, - sender: unstake.public_key, - }; + + // make call to the transfer contract to withdraw funds from this + // contract into the receiver specified by the withdrawal. let _: () = - rusk_abi::call(TRANSFER_CONTRACT, "withdraw", &withdraw_data) - .expect( - "Withdrawing the balance from contract should be successful", - ); + rusk_abi::call(TRANSFER_CONTRACT, "withdraw", transfer_withdraw) + .expect("Withdrawing stake should succeed"); + + // update the state accordingly + loaded_stake.amount = None; rusk_abi::emit( "unstake", - StakingEvent { - public_key: unstake.public_key, - value, + StakeEvent { + account, + value: staked_value, + receiver: Some(*transfer_withdraw.receiver()), }, ); - let key = unstake.public_key.to_bytes(); + let key = account.to_bytes(); self.previous_block_state .entry(key) - .or_insert((prev_value, unstake.public_key)); + .or_insert((prev_stake, account)); } pub fn withdraw(&mut self, withdraw: Withdraw) { - // deplete the stake from a key and increment the signature counter - let loaded_stake = self - .get_stake_mut(&withdraw.public_key) - .expect("A stake should exist in the map to be withdrawn!"); + let transfer_withdraw = withdraw.transfer_withdraw(); + let account = *withdraw.account(); + let value = transfer_withdraw.value(); + let signature = *withdraw.signature(); - let counter = loaded_stake.counter(); - let reward = loaded_stake.reward(); + let loaded_stake = self + .get_stake_mut(&account) + .expect("A stake should exist in the map to be unstaked!"); - if reward == 0 { - panic!("Nothing to withdraw!"); + // ensure there is a non-zero reward, and that the withdrawal is exactly + // the same amount + if loaded_stake.reward == 0 { + panic!("There is no reward available to withdraw"); } - loaded_stake.deplete_reward(); - loaded_stake.increment_counter(); - - // verify signature - let digest = Withdraw::signature_message( - counter, - withdraw.address, - withdraw.nonce, - ) - .to_vec(); + if value != loaded_stake.reward { + panic!("Value withdrawn different from available reward"); + } - if !rusk_abi::verify_bls( - digest, - withdraw.public_key, - withdraw.signature, - ) { + // check signature is correct + let digest = withdraw.signature_message().to_vec(); + if !rusk_abi::verify_bls(digest, account, signature) { panic!("Invalid signature!"); } - // make call to transfer contract to mint the reward to the given - // address - let transfer_contract = TRANSFER_CONTRACT; - let _: bool = rusk_abi::call( - transfer_contract, - "mint", - &Mint { - address: withdraw.address, - value: reward, - sender: withdraw.public_key, - }, - ) - .expect("Minting a reward note should succeed"); + // make call to the transfer contract to withdraw funds from this + // contract into the receiver specified by the withdrawal. + let _: () = + rusk_abi::call(TRANSFER_CONTRACT, "mint", transfer_withdraw) + .expect("Withdrawing reward should succeed"); + + // update the state accordingly + loaded_stake.reward = 0; rusk_abi::emit( "withdraw", - StakingEvent { - public_key: withdraw.public_key, - value: reward, + StakeEvent { + account, + value, + receiver: Some(*transfer_withdraw.receiver()), }, ); } /// Gets a reference to a stake. - pub fn get_stake(&self, key: &StakePublicKey) -> Option<&StakeData> { + pub fn get_stake(&self, key: &BlsPublicKey) -> Option<&StakeData> { self.stakes.get(&key.to_bytes()).map(|(s, _)| s) } /// Gets a mutable reference to a stake. pub fn get_stake_mut( &mut self, - key: &StakePublicKey, + key: &BlsPublicKey, ) -> Option<&mut StakeData> { self.stakes.get_mut(&key.to_bytes()).map(|(s, _)| s) } - /// Pushes the given `stake` onto the state for a given `stake_pk`. - pub fn insert_stake(&mut self, stake_pk: StakePublicKey, stake: StakeData) { - self.stakes.insert(stake_pk.to_bytes(), (stake, stake_pk)); + /// Pushes the given `stake` onto the state for a given `account`. + pub fn insert_stake(&mut self, account: BlsPublicKey, stake: StakeData) { + self.stakes.insert(account.to_bytes(), (stake, account)); } /// Gets a mutable reference to the stake of a given key. If said stake @@ -237,37 +242,41 @@ impl StakeState { /// returned. pub(crate) fn load_or_create_stake_mut( &mut self, - stake_pk: &StakePublicKey, + account: &BlsPublicKey, ) -> &mut StakeData { - let is_missing = self.stakes.get(&stake_pk.to_bytes()).is_none(); + let is_missing = self.stakes.get(&account.to_bytes()).is_none(); if is_missing { - let stake = StakeData::default(); - self.stakes.insert(stake_pk.to_bytes(), (stake, *stake_pk)); + let stake = StakeData::EMPTY; + self.stakes.insert(account.to_bytes(), (stake, *account)); } // SAFETY: unwrap is ok since we're sure we inserted an element self.stakes - .get_mut(&stake_pk.to_bytes()) + .get_mut(&account.to_bytes()) .map(|(s, _)| s) .unwrap() } - /// Rewards a `stake_pk` with the given `value`. If a stake does not exist + /// Rewards a `account` with the given `value`. If a stake does not exist /// in the map for the key one will be created. - pub fn reward(&mut self, stake_pk: &StakePublicKey, value: u64) { + pub fn reward(&mut self, account: &BlsPublicKey, value: u64) { self.check_new_block(); - let stake = self.load_or_create_stake_mut(stake_pk); + let stake = self.load_or_create_stake_mut(account); + // Reset faults counters stake.faults = 0; stake.hard_faults = 0; - stake.increase_reward(value); + + stake.reward += value; + rusk_abi::emit( "reward", - StakingEvent { - public_key: *stake_pk, + StakeEvent { + account: *account, value, + receiver: None, }, ); } @@ -282,31 +291,29 @@ impl StakeState { STAKE_CONTRACT_VERSION } - /// Slash the given `to_slash` amount from a `stake_pk` reward + /// Slash the given `to_slash` amount from an `account`'s reward /// /// If the reward is less than the `to_slash` amount, then the reward is /// depleted and the provisioner eligibility is shifted to the /// next epoch as well - pub fn slash(&mut self, stake_pk: &StakePublicKey, to_slash: Option) { + pub fn slash(&mut self, account: &BlsPublicKey, to_slash: Option) { self.check_new_block(); let stake = self - .get_stake_mut(stake_pk) + .get_stake_mut(account) .expect("The stake to slash should exist"); + let prev_stake = Some(*stake); // Stake can have no amount if provisioner unstake in the same block - if stake.amount().is_none() { + if stake.amount.is_none() { return; } - let prev_value = Some(stake.clone()); - stake.faults = stake.faults.saturating_add(1); let effective_faults = stake.faults.saturating_sub(STAKE_WARNINGS) as u64; - let (stake_amount, eligibility) = - stake.amount.as_mut().expect("stake_to_exists"); + let stake_amount = stake.amount.as_mut().expect("stake_to_exists"); // Shift eligibility (aka stake suspension) only if warnings are // saturated @@ -314,69 +321,74 @@ impl StakeState { // The stake is suspended for the rest of the current epoch plus // effective_faults epochs let to_shift = effective_faults * EPOCH; - *eligibility = next_epoch(rusk_abi::block_height()) + to_shift; + + stake_amount.eligibility = + next_epoch(rusk_abi::block_height()) + to_shift; + rusk_abi::emit( "suspended", - StakingEvent { - public_key: *stake_pk, - value: *eligibility, + StakeEvent { + account: *account, + value: stake_amount.eligibility, + receiver: None, }, ); } // Slash the provided amount or calculate the percentage according to // effective faults - let to_slash = - to_slash.unwrap_or(*stake_amount / 100 * effective_faults * 10); - let to_slash = min(to_slash, *stake_amount); + let to_slash = to_slash + .unwrap_or(stake_amount.value / 100 * effective_faults * 10); + let to_slash = min(to_slash, stake_amount.value); if to_slash > 0 { // Move the slash amount from stake to reward and deduct contract // balance - *stake_amount -= to_slash; - stake.increase_reward(to_slash); + stake_amount.value -= to_slash; + stake.reward += to_slash; + Self::deduct_contract_balance(to_slash); rusk_abi::emit( "slash", - StakingEvent { - public_key: *stake_pk, + StakeEvent { + account: *account, value: to_slash, + receiver: None, }, ); } - let key = stake_pk.to_bytes(); + let key = account.to_bytes(); self.previous_block_state .entry(key) - .or_insert((prev_value, *stake_pk)); + .or_insert((prev_stake, *account)); } - /// Slash the given `to_slash` amount from a `stake_pk` stake + /// Slash the given `to_slash` amount from an `account`'s stake. /// /// If the stake is less than the `to_slash` amount, then the stake is /// depleted pub fn hard_slash( &mut self, - stake_pk: &StakePublicKey, + account: &BlsPublicKey, to_slash: Option, severity: Option, ) { self.check_new_block(); let stake = self - .get_stake_mut(stake_pk) + .get_stake_mut(account) .expect("The stake to slash should exist"); // Stake can have no amount if provisioner unstake in the same block - if stake.amount().is_none() { + if stake.amount.is_none() { return; } - let prev_value = Some(stake.clone()); + let prev_stake = Some(*stake); - let (stake_amount, eligibility) = - stake.amount.as_mut().expect("stake_to_exists"); + let stake_amount = stake.amount.as_mut().expect("stake_to_exists"); let severity = severity.unwrap_or(1); stake.hard_faults = stake.hard_faults.saturating_add(severity); @@ -385,24 +397,27 @@ impl StakeState { // The stake is shifted (aka suspended) for the rest of the current // epoch plus hard_faults epochs let to_shift = hard_faults * EPOCH; - *eligibility = next_epoch(rusk_abi::block_height()) + to_shift; + stake_amount.eligibility = + next_epoch(rusk_abi::block_height()) + to_shift; + rusk_abi::emit( "suspended", - StakingEvent { - public_key: *stake_pk, - value: *eligibility, + StakeEvent { + account: *account, + value: stake_amount.eligibility, + receiver: None, }, ); // Slash the provided amount or calculate the percentage according to // hard faults let to_slash = - to_slash.unwrap_or(*stake_amount / 100 * hard_faults * 10); - let to_slash = min(to_slash, *stake_amount); + to_slash.unwrap_or(stake_amount.value / 100 * hard_faults * 10); + let to_slash = min(to_slash, stake_amount.value); if to_slash > 0 { // Update the staked amount - *stake_amount -= to_slash; + stake_amount.value -= to_slash; Self::deduct_contract_balance(to_slash); // Update the total burnt amount @@ -410,17 +425,18 @@ impl StakeState { rusk_abi::emit( "hard_slash", - StakingEvent { - public_key: *stake_pk, + StakeEvent { + account: *account, value: to_slash, + receiver: None, }, ); } - let key = stake_pk.to_bytes(); + let key = account.to_bytes(); self.previous_block_state .entry(key) - .or_insert((prev_value, *stake_pk)); + .or_insert((prev_stake, *account)); } /// Sets the burnt amount @@ -430,8 +446,8 @@ impl StakeState { /// Feeds the host with the stakes. pub fn stakes(&self) { - for (stake_data, stake_pk) in self.stakes.values() { - rusk_abi::feed((*stake_pk, stake_data.clone())); + for (stake_data, account) in self.stakes.values() { + rusk_abi::feed((*account, *stake_data)); } } @@ -448,8 +464,8 @@ impl StakeState { /// Feeds the host with previous state of the changed provisioners. pub fn prev_state_changes(&self) { - for (stake_data, stake_pk) in self.previous_block_state.values() { - rusk_abi::feed((*stake_pk, stake_data.clone())); + for (stake_data, account) in self.previous_block_state.values() { + rusk_abi::feed((*account, *stake_data)); } } } diff --git a/contracts/stake/tests/common/assert.rs b/contracts/stake/tests/common/assert.rs index aa489bea4d..52809171a7 100644 --- a/contracts/stake/tests/common/assert.rs +++ b/contracts/stake/tests/common/assert.rs @@ -7,13 +7,13 @@ use dusk_bytes::Serializable; use rkyv::{check_archived_root, Deserialize, Infallible}; -use execution_core::{stake::StakingEvent, StakePublicKey}; +use execution_core::{stake::StakeEvent, BlsPublicKey}; use rusk_abi::Event; pub fn assert_event( events: &Vec, topic: S, - should_pk: &StakePublicKey, + should_pk: &BlsPublicKey, should_amount: u64, ) where S: AsRef, @@ -27,14 +27,11 @@ pub fn assert_event( topic.as_ref() )); let staking_event_data = - check_archived_root::(event.data.as_slice()) + check_archived_root::(event.data.as_slice()) .expect("Stake event data should deserialize correctly"); - let staking_event_data: StakingEvent = staking_event_data + let staking_event_data: StakeEvent = staking_event_data .deserialize(&mut Infallible) .expect("Infallible"); assert_eq!(staking_event_data.value, should_amount); - assert_eq!( - staking_event_data.public_key.to_bytes(), - should_pk.to_bytes() - ); + assert_eq!(staking_event_data.account.to_bytes(), should_pk.to_bytes()); } diff --git a/contracts/stake/tests/common/utils.rs b/contracts/stake/tests/common/utils.rs index fa1f442c48..de8b8b7887 100644 --- a/contracts/stake/tests/common/utils.rs +++ b/contracts/stake/tests/common/utils.rs @@ -16,7 +16,7 @@ use rand::SeedableRng; use execution_core::{ transfer::{ - ContractCall, ContractExec, Fee, Payload, Transaction, TreeLeaf, + ContractCall, ContractExec, Fee, PhoenixPayload, Transaction, TreeLeaf, TRANSFER_TREE_DEPTH, }, value_commitment, JubJubScalar, Note, PublicKey, SchnorrSecretKey, @@ -122,15 +122,17 @@ pub fn filter_notes_owned_by>( /// Executes a transaction, returning the call receipt pub fn execute( session: &mut Session, - tx: Transaction, + tx: impl Into, ) -> Result, ContractError>>, Error> { + let tx = tx.into(); + // Spend the inputs and execute the call. If this errors the transaction is // unspendable. let mut receipt = session.call::<_, Result, ContractError>>( TRANSFER_CONTRACT, "spend_and_execute", &tx, - tx.payload().fee.gas_limit, + tx.gas_limit(), )?; // Ensure all gas is consumed if there's an error in the contract call @@ -145,7 +147,7 @@ pub fn execute( .call::<_, ()>( TRANSFER_CONTRACT, "refund", - &(tx.payload().fee, receipt.gas_spent), + &receipt.gas_spent, u64::MAX, ) .expect("Refunding must succeed"); @@ -270,10 +272,10 @@ pub fn create_transaction( deposit, }; - let tx_payload = Payload { + let tx_payload = PhoenixPayload { tx_skeleton, fee, - contract_exec: (contract_call.map(|c| ContractExec::Call(c))), + exec: (contract_call.map(|c| ContractExec::Call(c))), }; let payload_hash = tx_payload.hash(); @@ -361,5 +363,5 @@ pub fn create_transaction( .expect("creating a proof should succeed"); // build the transaction from the payload and proof - Transaction::new(tx_payload, proof.to_bytes()) + Transaction::phoenix(tx_payload, proof.to_bytes()) } diff --git a/contracts/stake/tests/events.rs b/contracts/stake/tests/events.rs index 4de34b218a..1fad7ab226 100644 --- a/contracts/stake/tests/events.rs +++ b/contracts/stake/tests/events.rs @@ -10,7 +10,8 @@ use rand::rngs::StdRng; use rand::SeedableRng; use execution_core::{ - stake::StakeData, PublicKey, SecretKey, StakePublicKey, StakeSecretKey, + stake::{StakeAmount, StakeData}, + BlsPublicKey, BlsSecretKey, PublicKey, SecretKey, }; use rusk_abi::dusk::dusk; use rusk_abi::{Error, STAKE_CONTRACT, TRANSFER_CONTRACT}; @@ -30,8 +31,8 @@ fn reward_slash() -> Result<(), Error> { let sk = SecretKey::random(rng); let pk = PublicKey::from(&sk); - let stake_sk = StakeSecretKey::random(rng); - let stake_pk = StakePublicKey::from(&stake_sk); + let stake_sk = BlsSecretKey::random(rng); + let stake_pk = BlsPublicKey::from(&stake_sk); let mut session = instantiate(rng, vm, &pk, GENESIS_VALUE); @@ -41,8 +42,11 @@ fn reward_slash() -> Result<(), Error> { let stake_data = StakeData { reward: 0, - amount: Some((stake_amount, 0)), - counter: 0, + amount: Some(StakeAmount { + value: stake_amount, + eligibility: 0, + }), + nonce: 0, faults: 0, hard_faults: 0, }; @@ -116,8 +120,8 @@ fn stake_hard_slash() -> Result<(), Error> { let sk = SecretKey::random(rng); let pk = PublicKey::from(&sk); - let stake_sk = StakeSecretKey::random(rng); - let stake_pk = StakePublicKey::from(&stake_sk); + let stake_sk = BlsSecretKey::random(rng); + let stake_pk = BlsPublicKey::from(&stake_sk); let mut session = instantiate(rng, vm, &pk, GENESIS_VALUE); @@ -129,8 +133,11 @@ fn stake_hard_slash() -> Result<(), Error> { let stake_data = StakeData { reward: 0, - amount: Some((stake_amount, block_height)), - counter: 0, + amount: Some(StakeAmount { + value: stake_amount, + eligibility: block_height, + }), + nonce: 0, faults: 0, hard_faults: 0, }; diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index 44b140e35f..43f2bd80d5 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -10,10 +10,10 @@ use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; -use execution_core::stake::{Stake, StakeData, Unstake, Withdraw}; use execution_core::{ - transfer::ContractCall, BlsScalar, JubJubScalar, PublicKey, SecretKey, - StakePublicKey, StakeSecretKey, ViewKey, + stake::{Stake, StakeData, Withdraw as StakeWithdraw}, + transfer::{ContractCall, Withdraw, WithdrawReceiver, WithdrawReplayToken}, + BlsPublicKey, BlsSecretKey, JubJubScalar, PublicKey, SecretKey, ViewKey, }; use rusk_abi::dusk::{dusk, LUX}; use rusk_abi::STAKE_CONTRACT; @@ -39,14 +39,14 @@ fn stake_withdraw_unstake() { let vm = &mut rusk_abi::new_ephemeral_vm() .expect("Creating ephemeral VM should work"); - let sender_sk = SecretKey::random(rng); - let sender_vk = ViewKey::from(&sender_sk); - let sender_pk = PublicKey::from(&sender_sk); + let phoenix_sender_sk = SecretKey::random(rng); + let phoenix_sender_vk = ViewKey::from(&phoenix_sender_sk); + let phoenix_sender_pk = PublicKey::from(&phoenix_sender_sk); - let stake_sk = StakeSecretKey::random(rng); - let stake_pk = StakePublicKey::from(&stake_sk); + let stake_sk = BlsSecretKey::random(rng); + let stake_pk = BlsPublicKey::from(&stake_sk); - let mut session = instantiate(rng, vm, &sender_pk, GENESIS_VALUE); + let mut session = instantiate(rng, vm, &phoenix_sender_pk, GENESIS_VALUE); let leaves = leaves_from_height(&mut session, 0) .expect("Getting leaves in the given range should succeed"); @@ -56,7 +56,7 @@ fn stake_withdraw_unstake() { // ------ // Stake - let receiver_pk = sender_pk; + let phoenix_receiver_pk = phoenix_sender_pk; let gas_limit = DEPOSIT_FEE; let gas_price = LUX; let transfer_value = 0; @@ -65,13 +65,7 @@ fn stake_withdraw_unstake() { let deposit = INITIAL_STAKE; // Fashion a Stake struct - let stake_digest = Stake::signature_message(0, deposit); - let stake_sig = stake_sk.sign(&stake_pk, &stake_digest); - let stake = Stake { - public_key: stake_pk, - signature: stake_sig, - value: deposit, - }; + let stake = Stake::new(&stake_sk, deposit, 1); let stake_bytes = rkyv::to_bytes::<_, 1024>(&stake) .expect("Should serialize Stake correctly") .to_vec(); @@ -83,8 +77,8 @@ fn stake_withdraw_unstake() { let tx = create_transaction( &mut session, - &sender_sk, - &receiver_pk, + &phoenix_sender_sk, + &phoenix_receiver_pk, gas_limit, gas_price, [input_note_pos], @@ -109,14 +103,17 @@ fn stake_withdraw_unstake() { .call(STAKE_CONTRACT, "get_stake", &stake_pk, POINT_LIMIT) .expect("Getting the stake should succeed") .data; - let stake_data = stake_data.expect("The stake should exist"); + let stake_data = + stake_data.expect("There should be a stake for the given key"); - let (amount, _) = - stake_data.amount.expect("There should be an amount staked"); + let amount = stake_data.amount.expect("There should be an amount staked"); - assert_eq!(amount, deposit, "Staked amount should match sent amount"); + assert_eq!( + amount.value, deposit, + "Staked amount should match sent amount" + ); assert_eq!(stake_data.reward, 0, "Initial reward should be zero"); - assert_eq!(stake_data.counter, 1, "Counter should increment once"); + assert_eq!(stake_data.nonce, 1, "Nonce should be set to stake value"); // ------ // Add a reward to the staked key @@ -138,17 +135,20 @@ fn stake_withdraw_unstake() { .call(STAKE_CONTRACT, "get_stake", &stake_pk, POINT_LIMIT) .expect("Getting the stake should succeed") .data; - let stake_data = stake_data.expect("The stake should exist"); + let stake_data = + stake_data.expect("There should be a stake for the given key"); - let (amount, _) = - stake_data.amount.expect("There should be an amount staked"); + let amount = stake_data.amount.expect("There should be an amount staked"); - assert_eq!(amount, deposit, "Staked amount should match sent amount"); + assert_eq!( + amount.value, deposit, + "Staked amount should match sent amount" + ); assert_eq!( stake_data.reward, REWARD_AMOUNT, "Reward should be set to specified amount" ); - assert_eq!(stake_data.counter, 1, "Counter should increment once"); + assert_eq!(stake_data.nonce, 1, "Nonce should remain the same"); // ------ // Start withdrawing the reward just given to our key @@ -157,7 +157,7 @@ fn stake_withdraw_unstake() { .expect("Getting the notes should succeed"); let input_notes = filter_notes_owned_by( - sender_vk, + phoenix_sender_vk, leaves.into_iter().map(|leaf| leaf.note), ); @@ -167,7 +167,7 @@ fn stake_withdraw_unstake() { "All new notes should be owned by our view key" ); - let receiver_pk = sender_pk; + let receiver_pk = phoenix_sender_pk; let gas_limit = WITHDRAW_FEE; let gas_price = LUX; let input_positions = [*input_notes[0].pos(), *input_notes[1].pos()]; @@ -176,21 +176,23 @@ fn stake_withdraw_unstake() { let deposit = 0; // Fashion a `Withdraw` struct instance - let withdraw_address_r = JubJubScalar::random(&mut *rng); - let withdraw_address = sender_pk.gen_stealth_address(&withdraw_address_r); - let withdraw_nonce = BlsScalar::random(&mut *rng); - let withdraw_digest = Withdraw::signature_message( - stake_data.counter, - withdraw_address, - withdraw_nonce, + let address = + phoenix_sender_pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + let note_sk = phoenix_sender_sk.gen_note_sk(&address); + + let withdraw = Withdraw::new( + rng, + ¬e_sk, + STAKE_CONTRACT.to_bytes(), + REWARD_AMOUNT, + WithdrawReceiver::Phoenix(address), + WithdrawReplayToken::Phoenix(vec![ + input_notes[0].gen_nullifier(&phoenix_sender_sk), + input_notes[1].gen_nullifier(&phoenix_sender_sk), + ]), ); - let withdraw_signature = stake_sk.sign(&stake_pk, &withdraw_digest); - let withdraw = Withdraw { - public_key: stake_pk, - signature: withdraw_signature, - address: withdraw_address, - nonce: withdraw_nonce, - }; + let withdraw = StakeWithdraw::new(&stake_sk, withdraw); + let withdraw_bytes = rkyv::to_bytes::<_, 2048>(&withdraw) .expect("Serializing Withdraw should succeed") .to_vec(); @@ -203,7 +205,7 @@ fn stake_withdraw_unstake() { let tx = create_transaction( &mut session, - &sender_sk, + &phoenix_sender_sk, &receiver_pk, gas_limit, gas_price, @@ -235,17 +237,17 @@ fn stake_withdraw_unstake() { .call(STAKE_CONTRACT, "get_stake", &stake_pk, POINT_LIMIT) .expect("Getting the stake should succeed") .data; - let stake_data = stake_data.expect("The stake should exist"); + let stake_data = + stake_data.expect("There should be a stake for the given key"); - let (amount, _) = - stake_data.amount.expect("There should be an amount staked"); + let amount = stake_data.amount.expect("There should be an amount staked"); assert_eq!( - amount, INITIAL_STAKE, + amount.value, INITIAL_STAKE, "Staked amount shouldn't have changed" ); assert_eq!(stake_data.reward, 0, "Reward should be set to zero"); - assert_eq!(stake_data.counter, 2, "Counter should increment once"); + assert_eq!(stake_data.nonce, 1, "Nonce should remain the same"); // ------ // Start unstaking the previously staked amount @@ -260,7 +262,7 @@ fn stake_withdraw_unstake() { ); let input_notes = filter_notes_owned_by( - sender_vk, + phoenix_sender_vk, leaves.into_iter().map(|leaf| leaf.note), ); @@ -270,7 +272,7 @@ fn stake_withdraw_unstake() { "All new notes should be owned by our view key" ); - let receiver_pk = sender_pk; + let receiver_pk = phoenix_sender_pk; let gas_limit = WITHDRAW_TRANSFER_FEE; let gas_price = LUX; let input_positions = [ @@ -283,18 +285,25 @@ fn stake_withdraw_unstake() { let deposit = 0; // Fashion an `Unstake` struct instance - let value = stake_data.amount.expect("There should be a stake").0; let address = - receiver_pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); - let unstake_digest = - Unstake::signature_message(stake_data.counter, value, address); - let unstake_sig = stake_sk.sign(&stake_pk, unstake_digest.as_slice()); - - let unstake = Unstake { - public_key: stake_pk, - signature: unstake_sig, - address, - }; + phoenix_sender_pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); + let note_sk = phoenix_sender_sk.gen_note_sk(&address); + + let withdraw = Withdraw::new( + rng, + ¬e_sk, + STAKE_CONTRACT.to_bytes(), + INITIAL_STAKE, + WithdrawReceiver::Phoenix(address), + WithdrawReplayToken::Phoenix(vec![ + input_notes[0].gen_nullifier(&phoenix_sender_sk), + input_notes[1].gen_nullifier(&phoenix_sender_sk), + input_notes[2].gen_nullifier(&phoenix_sender_sk), + ]), + ); + + let unstake = StakeWithdraw::new(&stake_sk, withdraw); + let unstake_bytes = rkyv::to_bytes::<_, 2048>(&unstake) .expect("Serializing Unstake should succeed") .to_vec(); @@ -307,7 +316,7 @@ fn stake_withdraw_unstake() { let tx = create_transaction( &mut session, - &sender_sk, + &phoenix_sender_sk, &receiver_pk, gas_limit, gas_price, From 84a257c798a228db7562c8482afbcb23e61dcd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:40:08 +0200 Subject: [PATCH 05/12] alice-contract: use `Withdraw` instead of `Mint` --- contracts/alice/src/state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/alice/src/state.rs b/contracts/alice/src/state.rs index 663a883c69..e4f87bd4bd 100644 --- a/contracts/alice/src/state.rs +++ b/contracts/alice/src/state.rs @@ -4,7 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use execution_core::transfer::Mint; +use execution_core::transfer::Withdraw; use rusk_abi::TRANSFER_CONTRACT; /// Alice contract. @@ -16,8 +16,8 @@ impl Alice { // no-op } - pub fn withdraw(&mut self, mint: Mint) { - let _: () = rusk_abi::call(TRANSFER_CONTRACT, "withdraw", &mint) + pub fn withdraw(&mut self, withdraw: Withdraw) { + let _: () = rusk_abi::call(TRANSFER_CONTRACT, "withdraw", &withdraw) .expect("Transparent withdrawal transaction should succeed"); } From e3c20e77b9f3312fafa2a74c31bd93d96356051d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:39:17 +0200 Subject: [PATCH 06/12] node-data: small name changes --- node-data/src/ledger/transaction.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/node-data/src/ledger/transaction.rs b/node-data/src/ledger/transaction.rs index 6c550c7f99..e291b243e1 100644 --- a/node-data/src/ledger/transaction.rs +++ b/node-data/src/ledger/transaction.rs @@ -6,17 +6,17 @@ use super::*; -use execution_core::transfer::Transaction as PhoenixTransaction; +use execution_core::transfer::Transaction as ProtocolTransaction; #[derive(Debug, Clone)] pub struct Transaction { pub version: u32, pub r#type: u32, - pub inner: PhoenixTransaction, + pub inner: ProtocolTransaction, } -impl From for Transaction { - fn from(value: PhoenixTransaction) -> Self { +impl From for Transaction { + fn from(value: ProtocolTransaction) -> Self { Self { inner: value, r#type: 1, @@ -60,13 +60,12 @@ impl Transaction { } pub fn gas_price(&self) -> u64 { - self.inner.payload().fee.gas_price + self.inner.gas_price() } + pub fn to_nullifiers(&self) -> Vec<[u8; 32]> { self.inner - .payload() - .tx_skeleton - .nullifiers + .nullifiers() .iter() .map(|n| n.to_bytes()) .collect() @@ -94,7 +93,9 @@ impl Eq for SpentTransaction {} #[cfg(any(feature = "faker", test))] pub mod faker { use super::*; - use execution_core::transfer::{ContractCall, ContractExec, Fee, Payload}; + use execution_core::transfer::{ + ContractCall, ContractExec, Fee, PhoenixPayload, + }; use execution_core::{ BlsScalar, JubJubScalar, Note, PublicKey, SecretKey, TxSkeleton, }; @@ -150,13 +151,13 @@ pub mod faker { let contract_call = ContractCall::new([21; 32], "some_method", &()).unwrap(); - let payload = Payload { + let payload = PhoenixPayload { tx_skeleton, fee, - contract_exec: Some(ContractExec::Call(contract_call)), + exec: Some(ContractExec::Call(contract_call)), }; let proof = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; - PhoenixTransaction::new(payload, proof).into() + ProtocolTransaction::phoenix(payload, proof).into() } } From 86eb9e3c8e3790dc6687400febe3df4a23283181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:42:20 +0200 Subject: [PATCH 07/12] consensus: small renames and reorders --- consensus/src/aggregator.rs | 9 ++++----- consensus/src/commons.rs | 9 +++++---- consensus/src/operations.rs | 10 +++++----- consensus/src/quorum/verifiers.rs | 10 +++++----- consensus/src/user/sortition.rs | 6 +++--- consensus/src/user/stake.rs | 6 +++--- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/consensus/src/aggregator.rs b/consensus/src/aggregator.rs index fb24d4022e..5ba23cc72a 100644 --- a/consensus/src/aggregator.rs +++ b/consensus/src/aggregator.rs @@ -214,7 +214,7 @@ mod tests { use crate::user::provisioners::{Provisioners, DUSK}; use crate::user::sortition::Config; use dusk_bytes::DeserializableSlice; - use execution_core::{StakePublicKey, StakeSecretKey}; + use execution_core::{BlsPublicKey, BlsSecretKey}; use hex::FromHex; use node_data::ledger::{Header, Seed}; use std::collections::HashMap; @@ -246,7 +246,7 @@ mod tests { .iter() .map(|hex| hex::decode(hex).expect("valid hex")) .map(|data| { - StakeSecretKey::from_slice(&data[..]).expect("valid secret key") + BlsSecretKey::from_slice(&data[..]).expect("valid secret key") }) .collect(); @@ -267,9 +267,8 @@ mod tests { tip_header.height = 0; for secret_key in sks { - let pubkey_bls = node_data::bls::PublicKey::new( - StakePublicKey::from(&secret_key), - ); + let pubkey_bls = + node_data::bls::PublicKey::new(BlsPublicKey::from(&secret_key)); p.add_member_with_value(pubkey_bls.clone(), 1000 * DUSK); diff --git a/consensus/src/commons.rs b/consensus/src/commons.rs index 9672990016..acd23a8196 100644 --- a/consensus/src/commons.rs +++ b/consensus/src/commons.rs @@ -14,13 +14,14 @@ use std::collections::HashMap; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use thiserror::Error; -use crate::operations::VoterWithCredits; -use execution_core::{BlsSigError, StakeSecretKey}; +use execution_core::{BlsSecretKey, BlsSigError}; use node_data::bls::PublicKey; use node_data::message::{AsyncQueue, Message, Payload}; use node_data::StepName; use tracing::error; +use crate::operations::VoterWithCredits; + pub type TimeoutSet = HashMap; #[derive(Clone, Default, Debug)] @@ -30,7 +31,7 @@ pub struct RoundUpdate { // This provisioner consensus keys pub pubkey_bls: PublicKey, - pub secret_key: StakeSecretKey, + pub secret_key: BlsSecretKey, seed: Seed, hash: [u8; 32], @@ -44,7 +45,7 @@ pub struct RoundUpdate { impl RoundUpdate { pub fn new( pubkey_bls: PublicKey, - secret_key: StakeSecretKey, + secret_key: BlsSecretKey, tip_header: &Header, base_timeouts: TimeoutSet, att_voters: Vec, diff --git a/consensus/src/operations.rs b/consensus/src/operations.rs index ec1642fe12..41416a4c3c 100644 --- a/consensus/src/operations.rs +++ b/consensus/src/operations.rs @@ -8,16 +8,16 @@ use std::fmt; use std::io; use std::time::Duration; -use execution_core::StakePublicKey; -use node_data::ledger::Fault; -use node_data::ledger::InvalidFault; -use node_data::ledger::{Block, Header, Slash, SpentTransaction, Transaction}; +use execution_core::BlsPublicKey; +use node_data::ledger::{ + Block, Fault, Header, InvalidFault, Slash, SpentTransaction, Transaction, +}; use node_data::StepName; use thiserror::Error; pub type StateRoot = [u8; 32]; pub type EventHash = [u8; 32]; -pub type VoterWithCredits = (StakePublicKey, usize); +pub type VoterWithCredits = (BlsPublicKey, usize); #[derive(Debug, Error)] pub enum Error { diff --git a/consensus/src/quorum/verifiers.rs b/consensus/src/quorum/verifiers.rs index df5ddf29bb..e3db197598 100644 --- a/consensus/src/quorum/verifiers.rs +++ b/consensus/src/quorum/verifiers.rs @@ -17,7 +17,7 @@ use crate::user::sortition; use crate::config::CONSENSUS_MAX_ITER; use dusk_bytes::Serializable as BytesSerializable; -use execution_core::{StakeAggPublicKey, StakeSignature}; +use execution_core::{BlsAggPublicKey, BlsSignature}; use tokio::sync::RwLock; use tracing::error; @@ -177,13 +177,13 @@ pub fn verify_votes( } impl Cluster { - fn aggregate_pks(&self) -> Result { + fn aggregate_pks(&self) -> Result { let pks: Vec<_> = self.iter().map(|(pubkey, _)| *pubkey.inner()).collect(); match pks.split_first() { Some((first, rest)) => { - let mut apk = StakeAggPublicKey::from(first); + let mut apk = BlsAggPublicKey::from(first); apk.aggregate(rest)?; Ok(apk) } @@ -196,7 +196,7 @@ fn verify_step_signature( header: &ConsensusHeader, step: StepName, vote: &Vote, - apk: StakeAggPublicKey, + apk: BlsAggPublicKey, signature: &[u8; 48], ) -> Result<(), StepSigError> { // Compile message to verify @@ -206,7 +206,7 @@ fn verify_step_signature( StepName::Proposal => Err(StepSigError::InvalidType)?, }; - let sig = StakeSignature::from_bytes(signature)?; + let sig = BlsSignature::from_bytes(signature)?; let mut msg = header.signable(); msg.extend_from_slice(sign_seed); vote.write(&mut msg).expect("Writing to vec should succeed"); diff --git a/consensus/src/user/sortition.rs b/consensus/src/user/sortition.rs index d60ef98e7f..63b5fbac81 100644 --- a/consensus/src/user/sortition.rs +++ b/consensus/src/user/sortition.rs @@ -100,7 +100,7 @@ mod tests { use crate::user::provisioners::{Provisioners, DUSK}; use crate::user::sortition::Config; use dusk_bytes::DeserializableSlice; - use execution_core::{StakePublicKey, StakeSecretKey}; + use execution_core::{BlsPublicKey, BlsSecretKey}; use node_data::ledger::Seed; @@ -298,7 +298,7 @@ mod tests { .take(n) .map(|hex| hex::decode(hex).expect("valid hex")) .map(|data| { - StakeSecretKey::from_slice(&data[..]).expect("valid secret key") + BlsSecretKey::from_slice(&data[..]).expect("valid secret key") }) .collect(); @@ -306,7 +306,7 @@ mod tests { for (i, sk) in sks.iter().enumerate().skip(1) { let stake_value = 1000 * (i) as u64 * DUSK; let stake_pk = - node_data::bls::PublicKey::new(StakePublicKey::from(sk)); + node_data::bls::PublicKey::new(BlsPublicKey::from(sk)); p.add_member_with_value(stake_pk, stake_value); } p diff --git a/consensus/src/user/stake.rs b/consensus/src/user/stake.rs index e78e1c93ff..6e6716f903 100644 --- a/consensus/src/user/stake.rs +++ b/consensus/src/user/stake.rs @@ -9,7 +9,7 @@ pub struct Stake { value: u64, pub reward: u64, - pub counter: u64, + pub nonce: u64, pub eligible_since: u64, } @@ -18,13 +18,13 @@ impl Stake { value: u64, reward: u64, eligible_since: u64, - counter: u64, + nonce: u64, ) -> Self { Self { value, reward, eligible_since, - counter, + nonce, } } From 6c90c312b74a54034c2e279d852df7280391d0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:43:54 +0200 Subject: [PATCH 08/12] node: small renames and reorders --- node/benches/accept.rs | 10 +++++----- node/src/chain/acceptor.rs | 10 +++++----- node/src/chain/consensus.rs | 2 +- node/src/chain/header_validation.rs | 6 +++--- node/src/database/rocksdb.rs | 4 ++-- node/src/mempool.rs | 8 ++------ node/src/vm.rs | 4 ++-- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/node/benches/accept.rs b/node/benches/accept.rs index fa0f77df8c..10ba3a3c14 100644 --- a/node/benches/accept.rs +++ b/node/benches/accept.rs @@ -21,7 +21,7 @@ use dusk_consensus::user::{ cluster::Cluster, committee::Committee, provisioners::Provisioners, sortition::Config as SortitionConfig, }; -use execution_core::{StakePublicKey, StakeSecretKey, StakeSignature}; +use execution_core::{BlsPublicKey, BlsSecretKey, BlsSignature}; use node_data::ledger::{Attestation, StepVotes}; use node_data::message::payload::{ QuorumType, RatificationResult, ValidationResult, Vote, @@ -36,7 +36,7 @@ fn create_step_votes( step: StepName, iteration: u8, provisioners: &Provisioners, - keys: &[(node_data::bls::PublicKey, StakeSecretKey)], + keys: &[(node_data::bls::PublicKey, BlsSecretKey)], ) -> StepVotes { let round = tip_header.height + 1; let seed = tip_header.seed; @@ -83,7 +83,7 @@ fn create_step_votes( } _ => unreachable!(), }; - signatures.push(StakeSignature::from_bytes(sig.inner()).unwrap()); + signatures.push(BlsSignature::from_bytes(sig.inner()).unwrap()); cluster.add(pk, weight); } } @@ -113,8 +113,8 @@ pub fn verify_block_att(c: &mut Criterion) { let mut provisioners = Provisioners::empty(); let rng = &mut StdRng::seed_from_u64(0xbeef); for _ in 0..input.provisioners { - let sk = StakeSecretKey::random(rng); - let pk = StakePublicKey::from(&sk); + let sk = BlsSecretKey::random(rng); + let pk = BlsPublicKey::from(&sk); let pk = node_data::bls::PublicKey::new(pk); keys.push((pk.clone(), sk)); provisioners.add_member_with_value(pk, 1000000000000) diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 602e545c3b..33f31052ff 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -18,7 +18,7 @@ use node_data::message::AsyncQueue; use node_data::message::Payload; use dusk_consensus::operations::VoterWithCredits; -use execution_core::stake::Unstake; +use execution_core::stake::Withdraw; use metrics::{counter, gauge, histogram}; use node_data::message::payload::Vote; use node_data::{Serializable, StepName}; @@ -309,7 +309,7 @@ impl Acceptor { // hit the chain. let stake_calls = txs.iter().filter(|t| t.err.is_none()).filter_map(|t| { - match &t.inner.inner.payload().contract_call() { + match &t.inner.inner.call() { Some(call) if (call.contract == STAKE_CONTRACT && (call.fn_name == STAKE @@ -334,18 +334,18 @@ impl Acceptor { ) -> Result { let change = match fn_name { UNSTAKE => { - let unstake: Unstake = + let unstake: Withdraw = rkyv::from_bytes(calldata).map_err(|e| { anyhow::anyhow!("Cannot deserialize unstake rkyv {e:?}") })?; - ProvisionerChange::Unstake(PublicKey::new(unstake.public_key)) + 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.public_key)) + ProvisionerChange::Stake(PublicKey::new(*stake.account())) } e => unreachable!("Parsing unexpected method: {e}"), }; diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index 64c792827f..a628526740 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -52,7 +52,7 @@ pub(crate) struct Task { task_id: u64, /// Loaded Consensus keys - pub keys: (execution_core::StakeSecretKey, node_data::bls::PublicKey), + pub keys: (execution_core::BlsSecretKey, node_data::bls::PublicKey), } impl Task { diff --git a/node/src/chain/header_validation.rs b/node/src/chain/header_validation.rs index 6b7b4a9be1..2ab02d3834 100644 --- a/node/src/chain/header_validation.rs +++ b/node/src/chain/header_validation.rs @@ -146,13 +146,13 @@ impl<'a, DB: database::DB> Validator<'a, DB> { seed: &[u8; 48], pk_bytes: &[u8; 96], ) -> anyhow::Result<()> { - let pk = execution_core::StakePublicKey::from_bytes(pk_bytes) + let pk = execution_core::BlsPublicKey::from_bytes(pk_bytes) .map_err(|err| anyhow!("invalid pk bytes: {:?}", err))?; - let signature = execution_core::StakeSignature::from_bytes(seed) + let signature = execution_core::BlsSignature::from_bytes(seed) .map_err(|err| anyhow!("invalid signature bytes: {}", err))?; - execution_core::StakeAggPublicKey::from(&pk) + execution_core::BlsAggPublicKey::from(&pk) .verify(&signature, &self.prev_header.seed.inner()[..]) .map_err(|err| anyhow!("invalid seed: {:?}", err))?; diff --git a/node/src/database/rocksdb.rs b/node/src/database/rocksdb.rs index cf7df63af3..d1cd6937ea 100644 --- a/node/src/database/rocksdb.rs +++ b/node/src/database/rocksdb.rs @@ -689,7 +689,7 @@ impl<'db, DB: DBAccess> Mempool for DBTransaction<'db, DB> { // Add Secondary indexes // // Nullifiers - for n in tx.inner.payload().tx_skeleton.nullifiers.iter() { + for n in tx.inner.nullifiers() { let key = n.to_bytes(); self.put_cf(self.nullifiers_cf, key, hash)?; } @@ -729,7 +729,7 @@ impl<'db, DB: DBAccess> Mempool for DBTransaction<'db, DB> { // Delete Secondary indexes // Delete Nullifiers - for n in tx.inner.payload().tx_skeleton.nullifiers.iter() { + for n in tx.inner.nullifiers() { let key = n.to_bytes(); self.inner.delete_cf(self.nullifiers_cf, key)?; } diff --git a/node/src/mempool.rs b/node/src/mempool.rs index 4739c28d1e..942922b59a 100644 --- a/node/src/mempool.rs +++ b/node/src/mempool.rs @@ -146,9 +146,7 @@ impl MempoolSrv { let nullifiers: Vec<_> = tx .inner - .payload() - .tx_skeleton - .nullifiers + .nullifiers() .iter() .map(|nullifier| nullifier.to_bytes()) .collect(); @@ -156,9 +154,7 @@ impl MempoolSrv { // ensure nullifiers do not exist in the mempool for m_tx_id in view.get_txs_by_nullifiers(&nullifiers) { if let Some(m_tx) = view.get_tx(m_tx_id)? { - if m_tx.inner.payload().fee.gas_price - < tx.inner.payload().fee.gas_price - { + if m_tx.inner.gas_price() < tx.inner.gas_price() { view.delete_tx(m_tx_id)?; } else { return Err( diff --git a/node/src/vm.rs b/node/src/vm.rs index cd801b7f4b..91dd4f3010 100644 --- a/node/src/vm.rs +++ b/node/src/vm.rs @@ -9,7 +9,7 @@ use dusk_consensus::{ operations::{CallParams, VerificationOutput}, user::{provisioners::Provisioners, stake::Stake}, }; -use execution_core::StakePublicKey; +use execution_core::BlsPublicKey; use node_data::ledger::{Block, SpentTransaction, Transaction}; #[derive(Default)] @@ -58,7 +58,7 @@ pub trait VMExecution: Send + Sync + 'static { fn get_provisioner( &self, - pk: &StakePublicKey, + pk: &BlsPublicKey, ) -> anyhow::Result>; fn get_state_root(&self) -> anyhow::Result<[u8; 32]>; From 93c6e627985123a9471f242cb25844f9d4a0851a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:35:00 +0200 Subject: [PATCH 09/12] rusk-recovery: change to handle moonlight on genesis To handle placing moonlight balances during state recovery, we add a new `moonlight_account` item than a `.toml` configuration file can specify a balance for given accounts at genesis. To ensure clarity we also rename the `balance` item to `phoenix_balance`. --- examples/genesis.toml | 2 +- rusk-recovery/config/example.toml | 13 +++- rusk-recovery/config/example_gov.toml | 29 --------- rusk-recovery/config/localnet.toml | 2 +- rusk-recovery/config/singlenet.toml | 2 +- rusk-recovery/config/testnet.toml | 24 ++++---- rusk-recovery/src/state.rs | 74 +++++++++++++++-------- rusk-recovery/src/state/snapshot.rs | 37 +++++++++--- rusk-recovery/src/state/snapshot/stake.rs | 6 +- 9 files changed, 107 insertions(+), 82 deletions(-) delete mode 100644 rusk-recovery/config/example_gov.toml diff --git a/examples/genesis.toml b/examples/genesis.toml index 56e00f13f8..b52a3e8f88 100644 --- a/examples/genesis.toml +++ b/examples/genesis.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = '4ZH3oyfTuMHyWD1Rp4e7QKp5yK6wLrWvxHneufAiYBAjvereFvfjtDvTbBcZN5ZCsaoMo49s1LKPTwGpowik6QJG' seed = 0xdead_beef notes = [100_000_000_000_000] diff --git a/rusk-recovery/config/example.toml b/rusk-recovery/config/example.toml index 49babdeb17..99c23ac312 100644 --- a/rusk-recovery/config/example.toml +++ b/rusk-recovery/config/example.toml @@ -14,7 +14,7 @@ base_state = "https://dusk-infra.ams3.digitaloceanspaces.com/keys/genesis.zip" # and an optional seed (used during notes creation) # # If no `seed` is configured, a random one is used. -[[balance]] +[[phoenix_balance]] address = 'dQgpVd2XwtXfVhDBdKX5rnXqyRkYsJgZ5wq826wdn6atfKnaNyptTogEko2u2ZWhW5sGADhR4YMKztqrFJphuMz' seed = 0xcafe notes = [ @@ -23,10 +23,19 @@ notes = [ 245_123_000_423 ] -[[balance]] +[[phoenix_balance]] address = '5i9RZjT87TLa1BtWXRRoFy3FoMzFHiXT3GWasHdUaxuo3YapUKYeXXiY1yuAeKng2hmxxaYsGNhKhjyrE9KYTSE7' notes = [1_000_000_000_000] + +# Accounts to be included in the genesis contract +# +# Each balance consists of an address (a BLS PublicKey in base58 format) +# and a balance. +[[moonlight_account]] +address = 'qe1FbZxf6YaCAeFNSvL1G82cBhG4Q4gBf4vKYo527Vws3b23jdbBuzKSFsdUHnZeBgsTnyNJLkApEpRyJw87sdzR9g9iESJrG5ZgpCs9jq88m6d4qMY5txGpaXskRQmkzE3' +balance = 1_000_000_000 + # Stakes to be included in the stake contract # # Each stake is identified by the address (a BLS PublicKey in base58 format) diff --git a/rusk-recovery/config/example_gov.toml b/rusk-recovery/config/example_gov.toml deleted file mode 100644 index 96a1088997..0000000000 --- a/rusk-recovery/config/example_gov.toml +++ /dev/null @@ -1,29 +0,0 @@ -[[balance]] -address = '5i9RZjT87TLa1BtWXRRoFy3FoMzFHiXT3GWasHdUaxuo3YapUKYeXXiY1yuAeKng2hmxxaYsGNhKhjyrE9KYTSE7' -seed = 0xdead_beef -notes = [1_000_000_000_000] - -[[balance]] -address = 'dQgpVd2XwtXfVhDBdKX5rnXqyRkYsJgZ5wq826wdn6atfKnaNyptTogEko2u2ZWhW5sGADhR4YMKztqrFJphuMz' -seed = 0xdead_beef -notes = [500_000_000_000_000_000] - -[[stake]] -address = 'rm6jwcM9xQChnVeHYw6W8HP7kW3E8nejgs4XhmKoYspQnrZL5dDcQjVR5e2UmbyNNF4DJDRdZGdYLgJfZ3qQXuhSCL6UTDkcQ2QEf6RW3aCtfqQMKCFMNaBsfNrZuzrP23Z' -amount = 200_000_000_000_000 - -[[stake]] -address = 'uazLgN2HtLqaDZCxAtJtkkurQTXyZWXjQYJ3HdSVbpBjGRzMvKaqvpx3MRw8HTv7bTwyviLosEJAa56G66cdNjL4CJdqTUyFHdufzWWfLz9E7eF97tdc6jZsVTiGXGNGf2E' -amount = 200_000_000_000_000 - -[[stake]] -address = '23RRcwBV2BsBVvmNPUh16xCYfLZj7qkxjCFg8dS5hkJcRrnVbFr5sHHGvTYbcvyDaZ19D6jqn21miC52y9rjycYrG1EBtc1wYwZLhZLHk63L52aaYYoyJtgM6hbuzmPXVvaQ' -amount = 200_000_000_000_000 - -[[stake]] -address = 'xxRf7HtmFrcNqF8QpEpXmNqEePC7ftcrAGyavJQUv5mQrZKmS1GBazS1fbrftFbj5t6A19F4FComuyLcS9dVkgu8bE7vzA4QTfEUWWjLU9As9FaazE8ewVEBkAe4f8SeGny' -amount = 200_000_000_000_000 - -[[stake]] -address = 'uZvKUYkVz7SJkicS4gQXGAjj8rziVkcV7ZXqGJAKNSW7iFFfTYCdo3k9Hw4A5dPfUxvBDtBPiSDjmehZGEQwDSYkdSf39WDEbavq9XHyzLY1S72EXCbEoB38avxaY7eXp6i' -amount = 200_000_000_000_000 diff --git a/rusk-recovery/config/localnet.toml b/rusk-recovery/config/localnet.toml index 68e74588a4..7d98ed1687 100644 --- a/rusk-recovery/config/localnet.toml +++ b/rusk-recovery/config/localnet.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = '5i9RZjT87TLa1BtWXRRoFy3FoMzFHiXT3GWasHdUaxuo3YapUKYeXXiY1yuAeKng2hmxxaYsGNhKhjyrE9KYTSE7' seed = 0xdead_beef notes = [1_000_000_000_000] diff --git a/rusk-recovery/config/singlenet.toml b/rusk-recovery/config/singlenet.toml index 9ad4b7be56..91f19672b1 100644 --- a/rusk-recovery/config/singlenet.toml +++ b/rusk-recovery/config/singlenet.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = '5i9RZjT87TLa1BtWXRRoFy3FoMzFHiXT3GWasHdUaxuo3YapUKYeXXiY1yuAeKng2hmxxaYsGNhKhjyrE9KYTSE7' seed = 0xdead_beef notes = [1_000_000_000_000] diff --git a/rusk-recovery/config/testnet.toml b/rusk-recovery/config/testnet.toml index 1e9a6eee12..b8e52c120a 100644 --- a/rusk-recovery/config/testnet.toml +++ b/rusk-recovery/config/testnet.toml @@ -1,59 +1,59 @@ -[[balance]] +[[phoenix_balance]] address = '68UiBbsJ4PumJyVpS91VbffhKgq9p6QaLC8QCqzgLiKBX71mvCswmeViTHDsBJ3RMwsVcxBSkZ3HbjHAL9qCNxh' seed = 0xdead_beef notes = [1_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '4LULsDGr3CkeZcVDSfM9UtNr1Xa9DF7HqCEnfnbsDH2HXkkoCfdQWKpEwKwPF6enrSVoTTfb2bGCB8v2VyHHFoUJ' seed = 0xdead_beef notes = [500_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '9Qary8ZQHYRZos3jmk4FBqo35mjJVqzsLwFthRB2vDY3wNnFTNaKcVrw66aeF8X2nYf5YqSq6BDs4TfsbBEnGjd' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = 'qDfYNMkC3m2GdBLsPy4PFCiVfniuSTzWPwgZ9TvpcgRE8ztEJvsCcfTJGtokrxGF8cFyCc9k1gZQpjrdjSooeDE' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '5KtKooBDhJFU3caSPC4AjeHcigRFrZ5YswMFXu74mavmu7sWPExDn9DLsYqTiLdstz8r7JJsDvAnCBhRPyEHQWMF' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '2N9o1ghLKx1e93weUJtymqfRoemjHu43xXLZWKEmbn6WtMrd3WzXDvtEw4LkBd4DskempKKq9UthdKGVoeXvhZVs' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '2neU6jSkQ2QnPxYfc4DaGQvkRtGeaBtU3vdfZHYh3iEWbZ3fosstgx2cKY3JVZTBiQHX3t5NetCK5v7fvoMPZaGe' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '3VHxhiWkA11vT8c5sTC4oDAk7QZ7hRqpMbrPP4z2A94mwWox1WhBWJjBMjVjQuvtJ7Mxqkm53JmH6oWBnXZqPSmi' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '3DXXr1k8NEerGwKCKsCbAaS6Da4TjkX1fYjoJeh61fPjJkyVnfiRG4A7eETTDoqWNUnmMVmvBniSMgcdWXHc7Q5J' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '2YiHv7ageZBUQif2ZRo4NUFGcZBNyWWAm3tyhchczBmPk336MX6ATkaPSLayHrhz1J6A5yfbU1zfoB8aRZBLHUK' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '3Bzh3YrwU1eiNoabHtSrwrU9AtTbAFDUt4w1rcEtCe9jztCrLzEZjJ14HBSECACf1g1jJjn5Ud1jM7oKWWjYSUTJ' seed = 0xdead_beef notes = [10_000_000_000_000_000] -[[balance]] +[[phoenix_balance]] address = '33GGzrthCdzCT98ZvHSoXGbQVKkYPXkRfTuxxcTVDW44fJpzTdgoaZYj8tDADmfQX44Cdz9je9dvLrfa6a76Qkq9' seed = 0xdead_beef notes = [10_000_000_000_000_000] diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index 3037affbd5..a6efe83049 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -17,14 +17,14 @@ use tracing::info; use url::Url; use execution_core::{ - stake::StakeData, transfer::SenderAccount, BlsPublicKey, BlsSecretKey, - JubJubScalar, Note, PublicKey, + stake::{StakeAmount, StakeData}, + BlsPublicKey, JubJubScalar, Note, PublicKey, Sender, }; use rusk_abi::{ContractData, ContractId, Session, VM}; use rusk_abi::{LICENSE_CONTRACT, STAKE_CONTRACT, TRANSFER_CONTRACT}; use crate::Theme; -pub use snapshot::{Balance, GenesisStake, Snapshot}; +pub use snapshot::{GenesisStake, PhoenixBalance, Snapshot}; mod http; mod snapshot; @@ -55,38 +55,58 @@ fn generate_transfer_state( let theme = Theme::default(); let mut update_root = false; - snapshot.transfers().enumerate().for_each(|(idx, balance)| { - update_root = true; - info!("{} balance #{}", theme.action("Generating"), idx,); - let mut rng = match balance.seed { - Some(seed) => StdRng::seed_from_u64(seed), - None => StdRng::from_entropy(), - }; + snapshot + .phoenix_balances() + .enumerate() + .for_each(|(idx, balance)| { + update_root = true; + info!("{} phoenix balance #{idx}", theme.action("Generating")); - balance.notes.iter().for_each(|&amount| { - let r = JubJubScalar::random(&mut rng); - let address = balance.address().gen_stealth_address(&r); - let sender = SenderAccount { - contract: STAKE_CONTRACT.to_bytes(), - account: BlsPublicKey::from(&BlsSecretKey::random(&mut rng)), + let mut rng = match balance.seed { + Some(seed) => StdRng::seed_from_u64(seed), + None => StdRng::from_entropy(), }; - let note = Note::transparent_stealth(address, amount, sender); + + balance.notes.iter().for_each(|&amount| { + let r = JubJubScalar::random(&mut rng); + let address = balance.address().gen_stealth_address(&r); + // the sender is "genesis" + let sender = Sender::ContractInfo([0u8; 128]); + let note = Note::transparent_stealth(address, amount, sender); + session + .call::<(u64, Note), ()>( + TRANSFER_CONTRACT, + "push_note", + &(GENESIS_BLOCK_HEIGHT, note), + u64::MAX, + ) + .expect("Minting should succeed"); + }); + }); + + snapshot + .moonlight_accounts() + .enumerate() + .for_each(|(idx, account)| { + info!("{} moonlight account #{idx}", theme.action("Generating")); + session - .call::<(u64, Note), ()>( + .call::<(BlsPublicKey, u64), ()>( TRANSFER_CONTRACT, - "push_note", - &(GENESIS_BLOCK_HEIGHT, note), + "add_account_balance", + &(*account.address(), account.balance), u64::MAX, ) - .expect("Minting should succeed"); + .expect("Making account should succeed"); }); - }); + if update_root { session .call::<_, ()>(TRANSFER_CONTRACT, "update_root", &(), u64::MAX) .expect("Root to be updated after pushing genesis note"); } + Ok(()) } @@ -97,16 +117,20 @@ fn generate_stake_state( let theme = Theme::default(); snapshot.stakes().enumerate().for_each(|(idx, staker)| { info!("{} provisioner #{}", theme.action("Generating"), idx); - let amount = (staker.amount > 0) - .then(|| (staker.amount, staker.eligibility.unwrap_or_default())); + + let amount = (staker.amount > 0).then(|| StakeAmount { + value: staker.amount, + eligibility: staker.eligibility.unwrap_or_default(), + }); let stake = StakeData { amount, reward: staker.reward.unwrap_or_default(), - counter: 0, + nonce: 0, faults: 0, hard_faults: 0, }; + session .call::<_, ()>( STAKE_CONTRACT, diff --git a/rusk-recovery/src/state/snapshot.rs b/rusk-recovery/src/state/snapshot.rs index 14fb4050f1..a160681fdf 100644 --- a/rusk-recovery/src/state/snapshot.rs +++ b/rusk-recovery/src/state/snapshot.rs @@ -7,7 +7,7 @@ use std::fmt::Debug; use dusk_bytes::Serializable; -use execution_core::PublicKey; +use execution_core::{BlsPublicKey, PublicKey}; use rusk_abi::dusk::Dusk; use serde_derive::{Deserialize, Serialize}; @@ -19,19 +19,31 @@ pub use stake::GenesisStake; use wrapper::Wrapper; #[derive(Serialize, Deserialize, PartialEq, Eq)] -pub struct Balance { +pub struct PhoenixBalance { address: Wrapper, pub seed: Option, #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] pub notes: Vec, } -impl Balance { +impl PhoenixBalance { pub fn address(&self) -> &PublicKey { &self.address } } +#[derive(Serialize, Deserialize, PartialEq, Eq)] +pub struct MoonlightAccount { + address: Wrapper, + pub balance: Dusk, +} + +impl MoonlightAccount { + pub fn address(&self) -> &BlsPublicKey { + &self.address + } +} + #[derive(Serialize, Deserialize, Default, PartialEq, Eq)] pub struct Snapshot { base_state: Option, @@ -39,7 +51,9 @@ pub struct Snapshot { // This "serde skip" workaround seems needed as per https://github.com/toml-rs/toml-rs/issues/384 #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] - balance: Vec, + phoenix_balance: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] + moonlight_account: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default = "Vec::new")] stake: Vec, } @@ -55,9 +69,16 @@ impl Debug for Snapshot { } impl Snapshot { - /// Returns an iterator of the transfers included in this snapshot - pub fn transfers(&self) -> impl Iterator { - self.balance.iter() + /// Returns an iterator over the phoenix balances included in this snapshot + pub fn phoenix_balances(&self) -> impl Iterator { + self.phoenix_balance.iter() + } + + /// Returns an iterator of the moonlight accounts included in this snapshot + pub fn moonlight_accounts( + &self, + ) -> impl Iterator { + self.moonlight_account.iter() } /// Returns an iterator of the stakes included in this snapshot. @@ -96,7 +117,7 @@ mod tests { let testnet = testnet_from_file()?; testnet - .balance + .phoenix_balance .iter() .find(|b| b.address().eq(&*state::FAUCET_KEY)) .expect("Testnet must have faucet configured"); diff --git a/rusk-recovery/src/state/snapshot/stake.rs b/rusk-recovery/src/state/snapshot/stake.rs index 3ffb4f67d8..4b5fb8e11b 100644 --- a/rusk-recovery/src/state/snapshot/stake.rs +++ b/rusk-recovery/src/state/snapshot/stake.rs @@ -7,21 +7,21 @@ use dusk_bytes::Serializable; use serde_derive::{Deserialize, Serialize}; -use execution_core::StakePublicKey; +use execution_core::BlsPublicKey; use rusk_abi::dusk::Dusk; use super::wrapper::Wrapper; #[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct GenesisStake { - pub(crate) address: Wrapper, + pub(crate) address: Wrapper, pub amount: Dusk, pub eligibility: Option, pub reward: Option, } impl GenesisStake { - pub fn address(&self) -> &StakePublicKey { + pub fn address(&self) -> &BlsPublicKey { &self.address } } From 44873d2171a88876ad56abf5fe8900d696eca31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:47:10 +0200 Subject: [PATCH 10/12] rusk-prover: small changes to use modified `execution-core` --- rusk-prover/src/tx.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rusk-prover/src/tx.rs b/rusk-prover/src/tx.rs index 580f012a89..1e5f5756a0 100644 --- a/rusk-prover/src/tx.rs +++ b/rusk-prover/src/tx.rs @@ -12,7 +12,7 @@ use dusk_bytes::{ }; use dusk_plonk::prelude::Proof; use execution_core::{ - transfer::{Payload, Transaction, TRANSFER_TREE_DEPTH}, + transfer::{PhoenixPayload, PhoenixTransaction, TRANSFER_TREE_DEPTH}, BlsScalar, JubJubAffine, JubJubExtended, JubJubScalar, Note, PublicKey, SchnorrSignature, SchnorrSignatureDouble, SecretKey, GENERATOR_NUMS_EXTENDED, OUTPUT_NOTES, @@ -160,15 +160,15 @@ impl UnprovenTransactionInput { pub struct UnprovenTransaction { pub inputs: Vec, pub outputs: [(Note, u64, JubJubScalar, [JubJubScalar; 2]); OUTPUT_NOTES], - pub payload: Payload, + pub payload: PhoenixPayload, pub sender_pk: PublicKey, pub signatures: (SchnorrSignature, SchnorrSignature), } impl UnprovenTransaction { /// Consumes self and a proof to generate a transaction. - pub fn gen_transaction(self, proof: Proof) -> Transaction { - Transaction::new(self.payload, proof.to_bytes()) + pub fn gen_transaction(self, proof: Proof) -> PhoenixTransaction { + PhoenixTransaction::new(self.payload, proof.to_bytes()) } /// Serialize the transaction to a variable length byte buffer. @@ -296,7 +296,7 @@ impl UnprovenTransaction { outputs.try_into().map_err(|_| BytesError::InvalidData)?; let payload_len = u64::from_reader(&mut buffer)?; - let payload = Payload::from_slice(buffer)?; + let payload = PhoenixPayload::from_slice(buffer)?; let mut buffer = &buffer[payload_len as usize..]; let sender_pk = PublicKey::from_reader(&mut buffer)?; @@ -323,7 +323,7 @@ impl UnprovenTransaction { } /// Returns the payload of the transaction. - pub fn payload(&self) -> &Payload { + pub fn payload(&self) -> &PhoenixPayload { &self.payload } @@ -393,10 +393,10 @@ mod tests { ) .unwrap(); - let payload = Payload { + let payload = PhoenixPayload { tx_skeleton, fee, - contract_exec: Some(ContractExec::Call(call)), + exec: Some(ContractExec::Call(call)), }; let sender_blinder_1 = From 5a4beeb36c5b86c8f2bb3b72bbf9f42d19e5ae23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:34:06 +0200 Subject: [PATCH 11/12] test-wallet: handle moonlight and withdrawal system --- test-wallet/src/imp.rs | 573 +++++++++++++++++++++++++---------------- test-wallet/src/lib.rs | 98 ++----- 2 files changed, 366 insertions(+), 305 deletions(-) diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs index ed30591447..a1ea4f32bf 100644 --- a/test-wallet/src/imp.rs +++ b/test-wallet/src/imp.rs @@ -4,9 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::{ - BalanceInfo, ProverClient, StakeInfo, StateClient, Store, MAX_CALL_SIZE, -}; +use crate::{BalanceInfo, ProverClient, StateClient, Store}; use core::convert::Infallible; @@ -15,11 +13,14 @@ use alloc::vec::Vec; use dusk_bytes::Error as BytesError; use execution_core::{ - stake::{Stake, Unstake, Withdraw}, - transfer::{ContractCall, ContractExec, Fee, Payload, Transaction}, - BlsPublicKey as StakePublicKey, BlsScalar, JubJubScalar, Note, - PhoenixError, PublicKey, SchnorrSecretKey, SecretKey, TxSkeleton, ViewKey, - OUTPUT_NOTES, + stake::{Stake, StakeData, Withdraw as StakeWithdraw}, + transfer::{ + AccountData, ContractCall, ContractDeploy, ContractExec, Fee, + MoonlightPayload, PhoenixPayload, Transaction, Withdraw, + WithdrawReceiver, WithdrawReplayToken, + }, + BlsPublicKey, BlsScalar, JubJubScalar, Note, PhoenixError, PublicKey, + SchnorrSecretKey, SecretKey, TxSkeleton, ViewKey, OUTPUT_NOTES, }; use ff::Field; use rand_core::{CryptoRng, Error as RngError, RngCore}; @@ -30,6 +31,7 @@ use rkyv::validation::validators::CheckDeserializeError; use rusk_prover::{UnprovenTransaction, UnprovenTransactionInput}; const MAX_INPUT_NOTES: usize = 4; +const SCRATCH_SIZE: usize = 2048; const TX_STAKE: &str = "stake"; const TX_UNSTAKE: &str = "unstake"; @@ -70,25 +72,25 @@ pub enum Error { /// staked for a key and the user tries to make a stake transaction. AlreadyStaked { /// The key that already has a stake. - key: StakePublicKey, + key: BlsPublicKey, /// Information about the key's stake. - stake: StakeInfo, + stake: StakeData, }, /// The key is not staked. This happens when a key doesn't have an amount /// staked and the user tries to make an unstake transaction. NotStaked { /// The key that is not staked. - key: StakePublicKey, + key: BlsPublicKey, /// Information about the key's stake. - stake: StakeInfo, + stake: StakeData, }, /// The key has no reward. This happens when a key has no reward in the /// stake contract and the user tries to make a withdraw transaction. NoReward { /// The key that has no reward. - key: StakePublicKey, + key: BlsPublicKey, /// Information about the key's stake. - stake: StakeInfo, + stake: StakeData, }, } @@ -197,24 +199,24 @@ where SC: StateClient, PC: ProverClient, { - /// Retrieve the public spend key with the given index. + /// Retrieve the public key with the given index. pub fn public_key( &self, index: u64, ) -> Result> { self.store - .retrieve_sk(index) + .fetch_secret_key(index) .map(|sk| PublicKey::from(&sk)) .map_err(Error::from_store_err) } - /// Retrieve the public key with the given index. - pub fn stake_public_key( + /// Retrieve the account public key with the given index. + pub fn account_public_key( &self, index: u64, - ) -> Result> { + ) -> Result> { self.store - .retrieve_stake_sk(index) + .fetch_account_secret_key(index) .map(|stake_sk| From::from(&stake_sk)) .map_err(Error::from_store_err) } @@ -338,37 +340,38 @@ where Ok((inputs, outputs)) } - /// Execute a generic contract call - #[allow(clippy::too_many_arguments)] - pub fn execute( + fn phoenix_transaction( &self, rng: &mut Rng, - contract_exec: ContractExec, - sender_index: u64, + sender_sk: &SecretKey, + receiver_pk: &PublicKey, + value: u64, gas_limit: u64, gas_price: u64, deposit: u64, + exec: MaybeExec, ) -> Result> where Rng: RngCore + CryptoRng, + MaybeExec: MaybePhoenixExec, { - let sender_sk = self - .store - .retrieve_sk(sender_index) - .map_err(Error::from_store_err)?; - let sender_pk = PublicKey::from(&sender_sk); + let sender_pk = PublicKey::from(sender_sk); let (inputs, outputs) = self.inputs_and_change_output( rng, &sender_sk, &sender_pk, - &sender_pk, - 0, + &receiver_pk, + value, gas_limit * gas_price, deposit, )?; let fee = Fee::new(rng, &sender_pk, gas_limit, gas_price); + let contract_call = exec.maybe_phoenix_exec( + rng, + inputs.iter().map(|(n, _, _)| n.clone()).collect(), + ); let utx = new_unproven_tx( rng, @@ -377,8 +380,8 @@ where inputs, outputs, fee, - 0, - Some(contract_exec), + deposit, + contract_call, ) .map_err(Error::from_state_err)?; @@ -387,56 +390,70 @@ where .map_err(Error::from_prover_err) } - /// Transfer Dusk from one key to another. + /// Execute a generic contract call or deployment, using Phoenix notes to + /// pay for gas. #[allow(clippy::too_many_arguments)] - pub fn transfer( + pub fn phoenix_execute( &self, rng: &mut Rng, + exec: impl Into, sender_index: u64, - receiver_pk: &PublicKey, - value: u64, gas_limit: u64, gas_price: u64, - ) -> Result> { + deposit: u64, + ) -> Result> + where + Rng: RngCore + CryptoRng, + { let sender_sk = self .store - .retrieve_sk(sender_index) + .fetch_secret_key(sender_index) .map_err(Error::from_store_err)?; - let sender_pk = PublicKey::from(&sender_sk); + let receiver_pk = PublicKey::from(&sender_sk); - let deposit = 0; - let (inputs, outputs) = self.inputs_and_change_output( + self.phoenix_transaction( rng, &sender_sk, - &sender_pk, - receiver_pk, - value, - gas_limit * gas_price, + &receiver_pk, + 0, + gas_limit, + gas_price, deposit, - )?; + exec.into(), + ) + } - let fee = Fee::new(rng, &sender_pk, gas_limit, gas_price); + /// Transfer Dusk in the form of Phoenix notes from one key to another. + #[allow(clippy::too_many_arguments)] + pub fn phoenix_transfer( + &self, + rng: &mut Rng, + sender_index: u64, + receiver_pk: &PublicKey, + value: u64, + gas_limit: u64, + gas_price: u64, + ) -> Result> { + let sender_sk = self + .store + .fetch_secret_key(sender_index) + .map_err(Error::from_store_err)?; - let utx = new_unproven_tx( + self.phoenix_transaction( rng, - &self.state, &sender_sk, - inputs, - outputs, - fee, - deposit, + receiver_pk, + value, + gas_limit, + gas_price, + 0, None, ) - .map_err(Error::from_state_err)?; - - self.prover - .compute_proof_and_propagate(&utx) - .map_err(Error::from_prover_err) } - /// Stakes an amount of Dusk. + /// Stakes an amount of Dusk using Phoenix notes. #[allow(clippy::too_many_arguments)] - pub fn stake( + pub fn phoenix_stake( &self, rng: &mut Rng, sender_index: u64, @@ -447,75 +464,47 @@ where ) -> Result> { let sender_sk = self .store - .retrieve_sk(sender_index) + .fetch_secret_key(sender_index) .map_err(Error::from_store_err)?; - let sender_pk = PublicKey::from(&sender_sk); + let receiver_pk = PublicKey::from(&sender_sk); let stake_sk = self .store - .retrieve_stake_sk(staker_index) + .fetch_account_secret_key(staker_index) .map_err(Error::from_store_err)?; - let stake_pk = StakePublicKey::from(&stake_sk); - let deposit = value; - - let (inputs, outputs) = self.inputs_and_change_output( - rng, - &sender_sk, - &sender_pk, - &sender_pk, - 0, - gas_limit * gas_price, - deposit, - )?; + let stake_pk = BlsPublicKey::from(&stake_sk); let stake = self .state .fetch_stake(&stake_pk) .map_err(Error::from_state_err)?; - if stake.amount.is_some() { - return Err(Error::AlreadyStaked { - key: stake_pk, - stake, - }); - } - let fee = Fee::new(rng, &sender_pk, gas_limit, gas_price); + let stake = Stake::new(&stake_sk, value, stake.nonce + 1); - let msg = Stake::signature_message(stake.counter, value); - let stake_sig = stake_sk.sign(&stake_pk, &msg); + let stake_bytes = rkyv::to_bytes::<_, SCRATCH_SIZE>(&stake) + .expect("Should serialize Stake correctly") + .to_vec(); - let stake = Stake { - public_key: stake_pk, - signature: stake_sig, - value, + let contract_call = ContractCall { + contract: rusk_abi::STAKE_CONTRACT.to_bytes(), + fn_name: String::from(TX_STAKE), + fn_args: stake_bytes, }; - let contract_call = ContractCall::new( - rusk_abi::STAKE_CONTRACT.to_bytes(), - TX_STAKE, - &stake, - ) - .expect("call data should serialize"); - - let utx = new_unproven_tx( + self.phoenix_transaction( rng, - &self.state, &sender_sk, - inputs, - outputs, - fee, + &receiver_pk, + 0, + gas_limit, + gas_price, value, Some(ContractExec::Call(contract_call)), ) - .map_err(Error::from_state_err)?; - - self.prover - .compute_proof_and_propagate(&utx) - .map_err(Error::from_prover_err) } - /// Unstake a key from the stake contract. - pub fn unstake( + /// Unstakes a key from the stake contract, using Phoenix notes. + pub fn phoenix_unstake( &self, rng: &mut Rng, sender_index: u64, @@ -525,82 +514,72 @@ where ) -> Result> { let sender_sk = self .store - .retrieve_sk(sender_index) + .fetch_secret_key(sender_index) .map_err(Error::from_store_err)?; - let sender_pk = PublicKey::from(&sender_sk); + let receiver_pk = PublicKey::from(&sender_sk); let stake_sk = self .store - .retrieve_stake_sk(staker_index) + .fetch_account_secret_key(staker_index) .map_err(Error::from_store_err)?; - let stake_pk = StakePublicKey::from(&stake_sk); - - let (inputs, outputs) = self.inputs_and_change_output( - rng, - &sender_sk, - &sender_pk, - &sender_pk, - 0, - gas_limit * gas_price, - 0, - )?; + let stake_pk = BlsPublicKey::from(&stake_sk); let stake = self .state .fetch_stake(&stake_pk) .map_err(Error::from_state_err)?; - let (value, _) = stake.amount.ok_or(Error::NotStaked { + + let amount = stake.amount.ok_or(Error::NotStaked { key: stake_pk, stake, })?; - let fee = Fee::new(rng, &sender_pk, gas_limit, gas_price); - let deposit = 0; - - let unstake_stealth_address = PublicKey::from(&sender_sk) - .gen_stealth_address(&JubJubScalar::random(&mut *rng)); - - let signature_message = Unstake::signature_message( - stake.counter, - value, - unstake_stealth_address, - ); - - let stake_sig = stake_sk.sign(&stake_pk, &signature_message); - - let unstake = Unstake { - public_key: stake_pk, - signature: stake_sig, - address: unstake_stealth_address, - }; - - let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&unstake)?.to_vec(); - let call = ContractCall { - contract: rusk_abi::STAKE_CONTRACT.to_bytes(), - fn_name: String::from(TX_UNSTAKE), - fn_args: call_data, - }; - - let utx = new_unproven_tx( + self.phoenix_transaction( rng, - &self.state, &sender_sk, - inputs, - outputs, - fee, - deposit, - Some(ContractExec::Call(call)), + &receiver_pk, + 0, + gas_limit, + gas_price, + 0, + |rng: &mut Rng, input_notes: Vec| { + let address = receiver_pk + .gen_stealth_address(&JubJubScalar::random(&mut *rng)); + let note_sk = sender_sk.gen_note_sk(&address); + + let nullifiers = input_notes + .into_iter() + .map(|note| note.gen_nullifier(&sender_sk)) + .collect(); + + let withdraw = Withdraw::new( + rng, + ¬e_sk, + rusk_abi::STAKE_CONTRACT.to_bytes(), + amount.value, + WithdrawReceiver::Phoenix(address), + WithdrawReplayToken::Phoenix(nullifiers), + ); + + let withdraw = StakeWithdraw::new(&stake_sk, withdraw); + + let withdraw_bytes = + rkyv::to_bytes::<_, SCRATCH_SIZE>(&withdraw) + .expect("Serializing Withdraw should succeed") + .to_vec(); + + ContractCall { + contract: rusk_abi::STAKE_CONTRACT.to_bytes(), + fn_name: String::from(TX_UNSTAKE), + fn_args: withdraw_bytes, + } + }, ) - .map_err(Error::from_state_err)?; - - self.prover - .compute_proof_and_propagate(&utx) - .map_err(Error::from_prover_err) } - /// Withdraw the reward a key has reward if accumulated by staking and - /// taking part in operating the network. - pub fn withdraw( + /// Withdraw the accumulated staking reward for a key, into Phoenix notes. + /// Rewards are accumulated by participating in the consensus. + pub fn phoenix_withdraw( &self, rng: &mut Rng, sender_index: u64, @@ -610,30 +589,21 @@ where ) -> Result> { let sender_sk = self .store - .retrieve_sk(sender_index) + .fetch_secret_key(sender_index) .map_err(Error::from_store_err)?; - let sender_pk = PublicKey::from(&sender_sk); + let receiver_pk = PublicKey::from(&sender_sk); let stake_sk = self .store - .retrieve_stake_sk(staker_index) + .fetch_account_secret_key(staker_index) .map_err(Error::from_store_err)?; - let stake_pk = StakePublicKey::from(&stake_sk); - - let (inputs, outputs) = self.inputs_and_change_output( - rng, - &sender_sk, - &sender_pk, - &sender_pk, - 0, - gas_limit * gas_price, - 0, - )?; + let stake_pk = BlsPublicKey::from(&stake_sk); let stake = self .state .fetch_stake(&stake_pk) .map_err(Error::from_state_err)?; + if stake.reward == 0 { return Err(Error::NoReward { key: stake_pk, @@ -641,46 +611,115 @@ where }); } - let address = - sender_pk.gen_stealth_address(&JubJubScalar::random(&mut *rng)); - let nonce = BlsScalar::random(&mut *rng); + self.phoenix_transaction( + rng, + &sender_sk, + &receiver_pk, + 0, + gas_limit, + gas_price, + 0, + |rng: &mut Rng, input_notes: Vec| { + let address = receiver_pk + .gen_stealth_address(&JubJubScalar::random(&mut *rng)); + let note_sk = sender_sk.gen_note_sk(&address); + + let nullifiers = input_notes + .into_iter() + .map(|note| note.gen_nullifier(&sender_sk)) + .collect(); + + let withdraw = Withdraw::new( + rng, + ¬e_sk, + rusk_abi::STAKE_CONTRACT.to_bytes(), + stake.reward, + WithdrawReceiver::Phoenix(address), + WithdrawReplayToken::Phoenix(nullifiers), + ); + + let unstake = StakeWithdraw::new(&stake_sk, withdraw); + + let unstake_bytes = rkyv::to_bytes::<_, SCRATCH_SIZE>(&unstake) + .expect("Serializing Withdraw should succeed") + .to_vec(); + + ContractCall { + contract: rusk_abi::STAKE_CONTRACT.to_bytes(), + fn_name: String::from(TX_WITHDRAW), + fn_args: unstake_bytes, + } + }, + ) + } - let msg = Withdraw::signature_message(stake.counter, address, nonce); - let stake_sig = stake_sk.sign(&stake_pk, &msg); + /// Transfer Dusk from one key to another using moonlight. + pub fn moonlight_transfer( + &self, + from: u64, + to: BlsPublicKey, + value: u64, + gas_limit: u64, + gas_price: u64, + ) -> Result> { + self.moonlight_transaction( + from, + Some(to), + value, + 0, + gas_limit, + gas_price, + None::, + ) + } - let withdraw = Withdraw { - public_key: stake_pk, - signature: stake_sig, - address, - nonce, - }; + /// Creates a generic moonlight transaction. + #[allow(clippy::too_many_arguments)] + pub fn moonlight_transaction( + &self, + from: u64, + to: Option, + value: u64, + deposit: u64, + gas_limit: u64, + gas_price: u64, + exec: Option>, + ) -> Result> { + let from_sk = self + .store + .fetch_account_secret_key(from) + .map_err(Error::from_store_err)?; - let fee = Fee::new(rng, &sender_pk, gas_limit, gas_price); - let deposit = 0; + let from = BlsPublicKey::from(&from_sk); - let call_data = rkyv::to_bytes::<_, MAX_CALL_SIZE>(&withdraw)?.to_vec(); + let account = self + .state + .fetch_account(&from) + .map_err(Error::from_state_err)?; - let call = ContractCall { - contract: rusk_abi::STAKE_CONTRACT.to_bytes(), - fn_name: String::from(TX_WITHDRAW), - fn_args: call_data, - }; + // technically this check is not necessary, but it's nice to not spam + // the network with transactions that are unspendable. + let max_value = value + deposit + gas_limit * gas_price; + if max_value > account.balance { + return Err(Error::NotEnoughBalance); + } + let nonce = account.nonce + 1; - let utx = new_unproven_tx( - rng, - &self.state, - &sender_sk, - inputs, - outputs, - fee, + let payload = MoonlightPayload { + from, + to, + value, deposit, - Some(ContractExec::Call(call)), - ) - .map_err(Error::from_state_err)?; + gas_limit, + gas_price, + nonce, + exec: exec.map(Into::into), + }; - self.prover - .compute_proof_and_propagate(&utx) - .map_err(Error::from_prover_err) + let digest = payload.to_hash_input_bytes(); + let signature = from_sk.sign(&from, &digest); + + Ok(Transaction::moonlight(payload, signature)) } /// Gets the balance of a key. @@ -690,7 +729,7 @@ where ) -> Result> { let sender_sk = self .store - .retrieve_sk(sk_index) + .fetch_secret_key(sk_index) .map_err(Error::from_store_err)?; let vk = ViewKey::from(&sender_sk); @@ -713,20 +752,40 @@ where pub fn get_stake( &self, sk_index: u64, - ) -> Result> { - let stake_sk = self + ) -> Result> { + let account_sk = self .store - .retrieve_stake_sk(sk_index) + .fetch_account_secret_key(sk_index) .map_err(Error::from_store_err)?; - let stake_pk = StakePublicKey::from(&stake_sk); + let account_pk = BlsPublicKey::from(&account_sk); - let s = self + let stake = self .state - .fetch_stake(&stake_pk) + .fetch_stake(&account_pk) .map_err(Error::from_state_err)?; - Ok(s) + Ok(stake) + } + + /// Gets the account data for a key. + pub fn get_account( + &self, + sk_index: u64, + ) -> Result> { + let account_sk = self + .store + .fetch_account_secret_key(sk_index) + .map_err(Error::from_store_err)?; + + let account_pk = BlsPublicKey::from(&account_sk); + + let account = self + .state + .fetch_account(&account_pk) + .map_err(Error::from_state_err)?; + + Ok(account) } } @@ -740,7 +799,7 @@ fn new_unproven_tx( outputs: [(Note, u64, JubJubScalar, [JubJubScalar; 2]); OUTPUT_NOTES], fee: Fee, deposit: u64, - contract_exec: Option, + exec: Option>, ) -> Result { let nullifiers: Vec = inputs .iter() @@ -763,10 +822,10 @@ fn new_unproven_tx( deposit, }; - let payload = Payload { + let payload = PhoenixPayload { tx_skeleton, fee, - contract_exec, + exec: exec.map(Into::into), }; let payload_hash = payload.hash(); @@ -800,6 +859,74 @@ fn new_unproven_tx( }) } +/// Optionally produces contract calls/executions for Phoenix transactions. +trait MaybePhoenixExec { + fn maybe_phoenix_exec( + self, + rng: &mut R, + inputs: Vec, + ) -> Option; +} + +impl MaybePhoenixExec for Option { + fn maybe_phoenix_exec( + self, + _rng: &mut R, + _inputs: Vec, + ) -> Option { + self + } +} + +impl MaybePhoenixExec for ContractExec { + fn maybe_phoenix_exec( + self, + _rng: &mut R, + _inputs: Vec, + ) -> Option { + Some(self) + } +} + +impl MaybePhoenixExec for ContractCall { + fn maybe_phoenix_exec( + self, + _rng: &mut R, + _inputs: Vec, + ) -> Option { + Some(ContractExec::Call(self)) + } +} + +impl MaybePhoenixExec for ContractDeploy { + fn maybe_phoenix_exec( + self, + _rng: &mut R, + _inputs: Vec, + ) -> Option { + Some(ContractExec::Deploy(self)) + } +} + +impl MaybePhoenixExec for F +where + F: FnOnce(&mut R, Vec) -> M, + M: MaybePhoenixExec, +{ + fn maybe_phoenix_exec( + self, + rng: &mut R, + inputs: Vec, + ) -> Option { + // NOTE: it may be (pun intended) possible to get rid of this clone if + // we use a lifetime into a slice of `Note`s. However, it comes at the + // cost of clarity. This is more important here, since this is testing + // infrastructure, and not production code. + let maybe = self(rng, inputs.clone()); + maybe.maybe_phoenix_exec(rng, inputs) + } +} + /// Pick the notes to be used in a transaction from a vector of notes. /// /// The notes are picked in a way to maximize the number of notes used, while diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs index c34f83bb86..a652e98772 100644 --- a/test-wallet/src/lib.rs +++ b/test-wallet/src/lib.rs @@ -17,8 +17,9 @@ mod imp; use alloc::vec::Vec; use dusk_bytes::{DeserializableSlice, Serializable, Write}; use execution_core::{ - stake::StakeData, transfer::Transaction, BlsPublicKey as StakePublicKey, - BlsScalar, BlsSecretKey as StakeSecretKey, Note, SecretKey, ViewKey, + stake::StakeData, + transfer::{AccountData, Transaction}, + BlsPublicKey, BlsScalar, BlsSecretKey, Note, SecretKey, ViewKey, }; use poseidon_merkle::Opening as PoseidonOpening; use rand_chacha::ChaCha12Rng; @@ -41,27 +42,27 @@ pub trait Store { /// Retrieves the seed used to derive keys. fn get_seed(&self) -> Result<[u8; 64], Self::Error>; - /// Retrieves a derived secret spend key from the store. + /// Retrieves a derived secret key from the store. /// /// The provided implementation simply gets the seed and regenerates the key /// every time with [`generate_sk`]. It may be reimplemented to /// provide a cache for keys, or implement a different key generation /// algorithm. - fn retrieve_sk(&self, index: u64) -> Result { + fn fetch_secret_key(&self, index: u64) -> Result { let seed = self.get_seed()?; Ok(derive_sk(&seed, index)) } - /// Retrieves a derived secret key from the store. + /// Retrieves a derived account secret key from the store. /// /// The provided implementation simply gets the seed and regenerates the key /// every time with [`generate_sk`]. It may be reimplemented to /// provide a cache for keys, or implement a different key generation /// algorithm. - fn retrieve_stake_sk( + fn fetch_account_secret_key( &self, index: u64, - ) -> Result { + ) -> Result { let seed = self.get_seed()?; Ok(derive_stake_sk(&seed, index)) } @@ -92,7 +93,7 @@ pub fn derive_sk(seed: &[u8; 64], index: u64) -> SecretKey { /// `index` are passed through SHA-256. A constant is then mixed in and the /// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is /// subsequently used to generate the key. -pub fn derive_stake_sk(seed: &[u8; 64], index: u64) -> StakeSecretKey { +pub fn derive_stake_sk(seed: &[u8; 64], index: u64) -> BlsSecretKey { let mut hash = Sha256::new(); hash.update(seed); @@ -102,7 +103,7 @@ pub fn derive_stake_sk(seed: &[u8; 64], index: u64) -> StakeSecretKey { let hash = hash.finalize().into(); let mut rng = ChaCha12Rng::from_seed(hash); - StakeSecretKey::random(&mut rng) + BlsSecretKey::random(&mut rng) } /// Types that are client of the prover. @@ -152,10 +153,13 @@ pub trait StateClient { /// Queries the node for the stake of a key. If the key has no stake, a /// `Default` stake info should be returned. - fn fetch_stake( + fn fetch_stake(&self, pk: &BlsPublicKey) -> Result; + + /// Queries the account data for a given key. + fn fetch_account( &self, - pk: &StakePublicKey, - ) -> Result; + pk: &BlsPublicKey, + ) -> Result; } /// Information about the balance of a particular key. @@ -195,73 +199,3 @@ impl Serializable<16> for BalanceInfo { buf } } - -/// The stake of a particular key. -#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)] -pub struct StakeInfo { - /// The value and eligibility of the stake, in that order. - pub amount: Option<(u64, u64)>, - /// The reward available for withdrawal. - pub reward: u64, - /// Signature counter. - pub counter: u64, -} - -impl From for StakeInfo { - fn from(data: StakeData) -> Self { - StakeInfo { - amount: data.amount, - reward: data.reward, - counter: data.counter, - } - } -} - -impl Serializable<32> for StakeInfo { - type Error = dusk_bytes::Error; - - /// Deserializes in the same order as defined in [`to_bytes`]. If the - /// deserialized value is 0, then `amount` will be `None`. This means that - /// the eligibility value is left loose, and could be any number when value - /// is 0. - fn from_bytes(buf: &[u8; Self::SIZE]) -> Result - where - Self: Sized, - { - let mut reader = &buf[..]; - - let value = u64::from_reader(&mut reader)?; - let eligibility = u64::from_reader(&mut reader)?; - let reward = u64::from_reader(&mut reader)?; - let counter = u64::from_reader(&mut reader)?; - - let amount = match value > 0 { - true => Some((value, eligibility)), - false => None, - }; - - Ok(Self { - amount, - reward, - counter, - }) - } - - /// Serializes the amount and the eligibility first, and then the reward and - /// the counter. If `amount` is `None`, and since a stake of no value should - /// not be possible, the first 16 bytes are filled with zeros. - #[allow(unused_must_use)] - fn to_bytes(&self) -> [u8; Self::SIZE] { - let mut buf = [0u8; Self::SIZE]; - let mut writer = &mut buf[..]; - - let (value, eligibility) = self.amount.unwrap_or_default(); - - writer.write(&value.to_bytes()); - writer.write(&eligibility.to_bytes()); - writer.write(&self.reward.to_bytes()); - writer.write(&self.counter.to_bytes()); - - buf - } -} From 32cc884b934dc2f36cc27534c73c17d3b30a7b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Thu, 11 Jul 2024 01:48:08 +0200 Subject: [PATCH 12/12] rusk: support Moonlight transactions 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. --- rusk/benches/block_ingestion.rs | 27 ++- rusk/src/lib/chain/rusk.rs | 61 ++++--- rusk/src/lib/chain/vm.rs | 92 +++++++--- rusk/src/lib/error.rs | 10 +- rusk/src/lib/http/chain/graphql.rs | 4 +- rusk/src/lib/http/chain/graphql/data.rs | 77 ++++---- rusk/src/lib/http/rusk.rs | 7 +- rusk/src/lib/verifier.rs | 20 ++- rusk/tests/common/keys.rs | 8 +- rusk/tests/common/state.rs | 10 +- rusk/tests/common/wallet.rs | 33 ++-- rusk/tests/config/bench.toml | 200 ++++++++++----------- rusk/tests/config/contract_deployment.toml | 2 +- rusk/tests/config/gas-behavior.toml | 4 +- rusk/tests/config/multi_transfer.toml | 6 +- rusk/tests/config/rusk-state.toml | 2 +- rusk/tests/config/slash.toml | 2 +- rusk/tests/config/stake.toml | 2 +- rusk/tests/config/transfer.toml | 6 +- rusk/tests/config/unspendable.toml | 6 +- rusk/tests/rusk-state.rs | 2 +- rusk/tests/services/contract_deployment.rs | 2 +- rusk/tests/services/gas_behavior.rs | 6 +- rusk/tests/services/multi_transfer.rs | 12 +- rusk/tests/services/stake.rs | 108 +++++++---- rusk/tests/services/transfer.rs | 5 +- rusk/tests/services/unspendable.rs | 8 +- 27 files changed, 429 insertions(+), 293 deletions(-) diff --git a/rusk/benches/block_ingestion.rs b/rusk/benches/block_ingestion.rs index f872d08e97..8d16bac883 100644 --- a/rusk/benches/block_ingestion.rs +++ b/rusk/benches/block_ingestion.rs @@ -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; @@ -37,7 +37,7 @@ fn load_txs() -> Vec { 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, @@ -46,12 +46,21 @@ fn load_txs() -> Vec { } 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 @@ -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; diff --git a/rusk/src/lib/chain/rusk.rs b/rusk/src/lib/chain/rusk.rs index 80b680f66f..0ae63b1e9a 100644 --- a/rusk/src/lib/chain/rusk.rs +++ b/rusk/src/lib/chain/rusk.rs @@ -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; @@ -44,9 +45,9 @@ use crate::http::RuesEvent; use crate::Error::InvalidCreditsCount; use crate::{Error, Result}; -pub static DUSK_KEY: LazyLock = LazyLock::new(|| { +pub static DUSK_KEY: LazyLock = 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") }); @@ -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; } @@ -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, @@ -215,7 +216,7 @@ impl Rusk { &self, block_height: u64, block_gas_limit: u64, - generator: &StakePublicKey, + generator: &BlsPublicKey, txs: &[Transaction], slashing: Vec, voters: Option<&[VoterWithCredits]>, @@ -245,7 +246,7 @@ impl Rusk { &self, block_height: u64, block_gas_limit: u64, - generator: StakePublicKey, + generator: BlsPublicKey, txs: Vec, consistency_check: Option, slashing: Vec, @@ -332,16 +333,21 @@ impl Rusk { pub fn provisioners( &self, base_commit: Option<[u8; 32]>, - ) -> Result> { + ) -> Result> { 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 { + 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 @@ -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)>> { + ) -> Result)>> { let (sender, receiver) = mpsc::channel(); self.feeder_query( STAKE_CONTRACT, @@ -371,16 +377,13 @@ impl Rusk { base_commit, )?; Ok(receiver.into_iter().map(|bytes| { - rkyv::from_bytes::<(StakePublicKey, Option)>(&bytes).expect( + rkyv::from_bytes::<(BlsPublicKey, Option)>(&bytes).expect( "The contract should only return (pk, Option) tuples", ) }).collect()) } - pub fn provisioner( - &self, - pk: &StakePublicKey, - ) -> Result> { + pub fn provisioner(&self, pk: &BlsPublicKey) -> Result> { self.query(STAKE_CONTRACT, "get_stake", pk) } @@ -433,7 +436,7 @@ fn accept( session: Session, block_height: u64, block_gas_limit: u64, - generator: &StakePublicKey, + generator: &BlsPublicKey, txs: &[Transaction], slashing: Vec, voters: Option<&[VoterWithCredits]>, @@ -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)?; @@ -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, ) -> Result, 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(), )); @@ -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, ); @@ -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"); @@ -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, - voters: Option<&[(StakePublicKey, usize)]>, + voters: Option<&[(BlsPublicKey, usize)]>, ) -> Result> { let ( dusk_value, @@ -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 diff --git a/rusk/src/lib/chain/vm.rs b/rusk/src/lib/chain/vm.rs index c0d699e4ee..aa15ca1f7f 100644 --- a/rusk/src/lib/chain/vm.rs +++ b/rusk/src/lib/chain/vm.rs @@ -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}; @@ -47,7 +50,7 @@ impl VMExecution for Rusk { ) -> anyhow::Result { 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)?; @@ -73,7 +76,7 @@ impl VMExecution for Rusk { ) -> anyhow::Result<(Vec, 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)?; @@ -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}")) + } + } + } } } @@ -140,15 +189,12 @@ impl VMExecution for Rusk { fn get_provisioner( &self, - pk: &StakePublicKey, + pk: &BlsPublicKey, ) -> anyhow::Result> { 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) } @@ -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) } } diff --git a/rusk/src/lib/error.rs b/rusk/src/lib/error.rs index 1f2c5dadd3..9556e268af 100644 --- a/rusk/src/lib/error.rs +++ b/rusk/src/lib/error.rs @@ -6,8 +6,9 @@ use std::{fmt, io}; +use dusk_bytes::Serializable; use execution_core::BlsScalar; -use execution_core::PhoenixError; +use execution_core::{BlsPublicKey, PhoenixError}; use rusk_abi::dusk::Dusk; #[derive(Debug)] @@ -22,6 +23,8 @@ pub enum Error { OutOfGas, /// Repeated nullifier in transaction verification RepeatingNullifiers(Vec), + /// Repeating a nonce that has already been used + RepeatingNonce(Box, u64), /// Wrong inputs and/or outputs in the transaction verification InvalidCircuitArguments(usize, usize), /// Failed to build a Rusk instance @@ -132,6 +135,11 @@ impl fmt::Display for Error { Error::RepeatingNullifiers(n) => { write!(f, "Nullifiers repeat: {n:?}") } + Error::RepeatingNonce(account, nonce) => { + let encoded_account = + bs58::encode(&account.to_bytes()).into_string(); + write!(f, "Nonce repeat: {encoded_account} {nonce}") + } Error::InvalidCircuitArguments(inputs_len, outputs_len) => { write!(f,"Expected: 0 < (inputs: {inputs_len}) < 5, 0 ≤ (outputs: {outputs_len}) < 3") } diff --git a/rusk/src/lib/http/chain/graphql.rs b/rusk/src/lib/http/chain/graphql.rs index c7e2612b0c..600ff0a0f2 100644 --- a/rusk/src/lib/http/chain/graphql.rs +++ b/rusk/src/lib/http/chain/graphql.rs @@ -82,9 +82,7 @@ impl Query { let tx_contract = t.0.inner .inner - .payload() - .contract_call() - .as_ref() + .call() .map(|c| c.contract) .unwrap_or( rusk_abi::TRANSFER_CONTRACT.to_bytes(), diff --git a/rusk/src/lib/http/chain/graphql/data.rs b/rusk/src/lib/http/chain/graphql/data.rs index 6e9f5df13e..64d8d5ee2e 100644 --- a/rusk/src/lib/http/chain/graphql/data.rs +++ b/rusk/src/lib/http/chain/graphql/data.rs @@ -249,26 +249,33 @@ impl Transaction<'_> { let tx = &self.0.inner; let mut map = Map::new(); - map.insert( - "root".into(), - json!(hex::encode(tx.payload().tx_skeleton.root.to_bytes())), - ); + + if let Some(root) = tx.root() { + map.insert("root".into(), json!(hex::encode(root.to_bytes()))); + } + + if let Some(from) = tx.from() { + map.insert("from".into(), json!(hex::encode(from.to_bytes()))); + } + if let Some(to) = tx.to() { + map.insert("to".into(), json!(hex::encode(to.to_bytes()))); + } + if let Some(value) = tx.value() { + map.insert("value".into(), json!(hex::encode(value.to_bytes()))); + } + let nullifiers: Vec<_> = tx - .payload() - .tx_skeleton - .nullifiers + .nullifiers() .iter() .map(|n| hex::encode(n.to_bytes())) .collect(); - map.insert("nullifier".into(), json!(nullifiers)); + map.insert("nullifiers".into(), json!(nullifiers)); map.insert( "deposit".into(), - json!(hex::encode(tx.payload().tx_skeleton.deposit.to_bytes())), + json!(hex::encode(tx.deposit().to_bytes())), ); let notes: Vec<_> = tx - .payload() - .tx_skeleton - .outputs + .outputs() .iter() .map(|n| { let mut map = Map::new(); @@ -303,20 +310,23 @@ impl Transaction<'_> { map.insert("notes".into(), json!(notes)); let mut fee = Map::new(); - fee.insert("gas_limit".into(), json!(tx.payload().fee.gas_limit)); - fee.insert("gas_price".into(), json!(tx.payload().fee.gas_price)); - fee.insert( - "stealth_address".into(), - json!(bs58::encode(tx.payload().fee.stealth_address.to_bytes()) - .into_string()), - ); - fee.insert( - "sender".into(), - json!(hex::encode(tx.payload().fee.sender.to_bytes())), - ); + + fee.insert("gas_limit".into(), json!(tx.gas_limit())); + fee.insert("gas_price".into(), json!(tx.gas_price())); + + if let Some(stealth_address) = tx.stealth_address() { + fee.insert( + "stealth_address".into(), + json!(bs58::encode(stealth_address.to_bytes()).into_string()), + ); + } + if let Some(sender) = tx.sender() { + fee.insert("sender".into(), json!(hex::encode(sender.to_bytes()))); + } + map.insert("fee".into(), json!(fee)); - if let Some(c) = &tx.payload().contract_call() { + if let Some(c) = tx.call() { let mut call = Map::new(); call.insert("contract".into(), json!(hex::encode(c.contract))); call.insert("fn_name".into(), json!(&c.fn_name)); @@ -332,24 +342,19 @@ impl Transaction<'_> { } pub async fn gas_limit(&self) -> u64 { - self.0.inner.payload().fee.gas_limit + self.0.inner.gas_limit() } pub async fn gas_price(&self) -> u64 { - self.0.inner.payload().fee.gas_price + self.0.inner.gas_price() } pub async fn call_data(&self) -> Option { - self.0 - .inner - .payload() - .contract_call() - .as_ref() - .map(|call| CallData { - contract: hex::encode(call.contract), - fn_name: call.fn_name.clone(), - fn_args: hex::encode(&call.fn_args), - }) + self.0.inner.call().map(|call| CallData { + contract: hex::encode(call.contract), + fn_name: call.fn_name.clone(), + fn_args: hex::encode(&call.fn_args), + }) } } diff --git a/rusk/src/lib/http/rusk.rs b/rusk/src/lib/http/rusk.rs index 6911431e5b..591b27b91f 100644 --- a/rusk/src/lib/http/rusk.rs +++ b/rusk/src/lib/http/rusk.rs @@ -98,10 +98,11 @@ impl Rusk { .expect("Cannot query state for provisioners") .map(|(key, stake)| { let key = bs58::encode(key.to_bytes()).into_string(); - let (amount, eligibility) = stake.amount.unwrap_or_default(); + let amount = stake.amount.unwrap_or_default(); + Provisioner { - amount, - eligibility, + amount: amount.value, + eligibility: amount.eligibility, key, reward: stake.reward, } diff --git a/rusk/src/lib/verifier.rs b/rusk/src/lib/verifier.rs index 743f44ffda..3e884c52e9 100644 --- a/rusk/src/lib/verifier.rs +++ b/rusk/src/lib/verifier.rs @@ -9,7 +9,7 @@ use crate::error::Error; use crate::Result; -use execution_core::transfer::Transaction; +use execution_core::transfer::{MoonlightTransaction, PhoenixTransaction}; use rusk_profile::Circuit as CircuitProfile; use std::sync::LazyLock; @@ -26,7 +26,8 @@ pub static VD_EXEC_3_2: LazyLock> = pub static VD_EXEC_4_2: LazyLock> = LazyLock::new(|| fetch_verifier("ExecuteCircuitFourTwo")); -pub fn verify_proof(tx: &Transaction) -> Result { +/// Verifies the proof of the incoming transaction. +pub fn verify_proof(tx: &PhoenixTransaction) -> Result { let pi: Vec = tx.public_inputs().iter().map(|pi| pi.into()).collect(); @@ -43,11 +44,22 @@ pub fn verify_proof(tx: &Transaction) -> Result { } }; - // Maybe we want to handle internal serialization error too, currently - // they map to `false`. + // Maybe we want to handle internal serialization error too, + // currently they map to `false`. Ok(rusk_abi::verify_proof(vd.to_vec(), tx.proof().to_vec(), pi)) } +/// Verifies the signature of the incoming transaction. +pub fn verify_signature(tx: &MoonlightTransaction) -> Result { + let payload = tx.payload(); + let signature = tx.signature(); + + let digest = payload.to_hash_input_bytes(); + let is_valid = rusk_abi::verify_bls(digest, payload.from, *signature); + + Ok(is_valid) +} + fn fetch_verifier(circuit_name: &str) -> Vec { let circuit_profile = CircuitProfile::from_name(circuit_name) .unwrap_or_else(|_| { diff --git a/rusk/tests/common/keys.rs b/rusk/tests/common/keys.rs index f96871a8e1..73c92e9756 100644 --- a/rusk/tests/common/keys.rs +++ b/rusk/tests/common/keys.rs @@ -10,12 +10,12 @@ use rand::prelude::*; use rand::rngs::StdRng; use tracing::info; -use execution_core::StakeSecretKey; +use execution_core::BlsSecretKey; #[allow(dead_code)] -pub static STAKE_SK: LazyLock = LazyLock::new(|| { - info!("Generating StakeSecretKey"); +pub static STAKE_SK: LazyLock = LazyLock::new(|| { + info!("Generating BlsSecretKey"); let mut rng = StdRng::seed_from_u64(0xdead); - StakeSecretKey::random(&mut rng) + BlsSecretKey::random(&mut rng) }); diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index 6349b16744..ed4b4ef1b7 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -13,9 +13,7 @@ use rusk::{Result, Rusk}; use rusk_recovery_tools::state::{self, Snapshot}; use dusk_consensus::operations::CallParams; -use execution_core::{ - transfer::Transaction as PhoenixTransaction, StakePublicKey, -}; +use execution_core::{transfer::Transaction, BlsPublicKey}; use node_data::{ bls::PublicKeyBytes, ledger::{ @@ -68,10 +66,10 @@ pub struct ExecuteResult { #[allow(dead_code)] pub fn generator_procedure( rusk: &Rusk, - txs: &[PhoenixTransaction], + txs: &[Transaction], block_height: u64, block_gas_limit: u64, - missed_generators: Vec, + missed_generators: Vec, expected: Option, ) -> anyhow::Result> { let expected = expected.unwrap_or(ExecuteResult { @@ -84,7 +82,7 @@ pub fn generator_procedure( rusk.preverify(tx)?; } - let generator = StakePublicKey::from(LazyLock::force(&STAKE_SK)); + let generator = BlsPublicKey::from(LazyLock::force(&STAKE_SK)); let generator_pubkey = node_data::bls::PublicKey::new(generator); let generator_pubkey_bytes = *generator_pubkey.bytes(); let round = block_height; diff --git a/rusk/tests/common/wallet.rs b/rusk/tests/common/wallet.rs index 0daed7d5ad..f5b74c80f5 100644 --- a/rusk/tests/common/wallet.rs +++ b/rusk/tests/common/wallet.rs @@ -12,15 +12,16 @@ use crate::common::block::Block as BlockAwait; use dusk_bytes::{DeserializableSlice, Serializable}; use dusk_plonk::prelude::Proof; -use execution_core::transfer::{ - Transaction as PhoenixTransaction, TRANSFER_TREE_DEPTH, +use execution_core::{ + stake::StakeData, + transfer::{AccountData, Transaction, TRANSFER_TREE_DEPTH}, + BlsPublicKey, BlsScalar, Note, ViewKey, }; -use execution_core::{BlsPublicKey, BlsScalar, Note, ViewKey}; use futures::StreamExt; use poseidon_merkle::Opening as PoseidonOpening; use rusk::{Error, Result, Rusk}; use rusk_prover::{LocalProver, Prover}; -use test_wallet::{self as wallet, StakeInfo, Store, UnprovenTransaction}; +use test_wallet::{self as wallet, Store, UnprovenTransaction}; use tracing::info; #[derive(Debug, Clone)] @@ -106,18 +107,18 @@ impl wallet::StateClient for TestStateClient { .ok_or(Error::OpeningPositionNotFound(*note.pos())) } - fn fetch_stake(&self, pk: &BlsPublicKey) -> Result { - let stake = self - .rusk - .provisioner(pk)? - .map(|stake| StakeInfo { - amount: stake.amount, - counter: stake.counter, - reward: stake.reward, - }) - .unwrap_or_default(); + fn fetch_stake(&self, pk: &BlsPublicKey) -> Result { + let stake = self.rusk.provisioner(pk)?.unwrap_or(StakeData::EMPTY); Ok(stake) } + + fn fetch_account( + &self, + pk: &BlsPublicKey, + ) -> Result { + let account = self.rusk.account(pk)?; + Ok(account) + } } #[derive(Default)] @@ -137,7 +138,7 @@ impl wallet::ProverClient for TestProverClient { fn compute_proof_and_propagate( &self, utx: &UnprovenTransaction, - ) -> Result { + ) -> Result { let utx_bytes = &utx.to_var_bytes()[..]; let proof = self.prover.prove_execute(utx_bytes)?; info!("UTX: {}", hex::encode(utx_bytes)); @@ -146,7 +147,7 @@ impl wallet::ProverClient for TestProverClient { //Propagate is not required yet - Ok(tx) + Ok(tx.into()) } } diff --git a/rusk/tests/config/bench.toml b/rusk/tests/config/bench.toml index d79800a3e4..7d219f1617 100644 --- a/rusk/tests/config/bench.toml +++ b/rusk/tests/config/bench.toml @@ -2,502 +2,502 @@ owners = [] allowlist = [] -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3MoVQ6VfGNu8fJ5GeHPRDVUfxcsDEmGXpWhvKhXY7F2dKCp7QWRw8RqPcbuJGdRqeTtxpuiwETnGAJLnhT4Kq4e8" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4RyaodGmN8MyUDmpRrtRxJJhrVW2HsY2ycRUnRUXR97JCN1GHraQT9Ygb8yYo7oKzyZg2EXXCGkHBwoeNb96BKtQ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4csmXrPE5bbw6jhrrZ6gtfEbGRJQkYWmaDLxRiAprvFH8ZULZXr5vwGoRohPEzwUQZUZJtgtQzJtiaD1tbX6E2Ef" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5ocii5SbQFu8L3oZUz8nxXN97aS2WbiofzsJZGFWnRiW2HHc58m55SJXtu5KXNgJEwawA9axmDpseaen5HmoFZVA" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "62U4U6DqbbL86qtvzsp1Nv7fwF8VBKY8TgQCMjgbvZgppB35DB7gHrDoSv3xfpoxGMeksTwyftXZYeyBHRquEYxo" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2bu73CmZbbavWojBMtmP811JkNTv4uqpU55Xw5ieVCvXsB28WWUq9h2VFvU2jTJJA4eZaWb5vE7HUWgjBhpaCQuo" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3FCKVvuAFsMQKk4y1sbfyRp4Mhct6YcJXF5TUijZK825txyFpTv5cw8KgcpPjRHLs5wUy7QBUdLZ3kjCC9EMyNbR" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5ZNtdcXXEMPMV4gR8WpeqoZyd15xMfsAGMd7LCwnpLW6rHkq1JAehYHsCn4Qkhrv2qYdZfn9iaP4LucZBJwS9mEt" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3Yz6yWWbnF2tXeCnMyhFvX6c8f6HEr49hy4WNus3pM3KmkBppbUggwMduhUpnmAzrqUHpZpFKMjwYrj4SPokp92T" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "gyC9TpyFDivjKMaQ1mWGvK1R46ajuKVqh99Ck9SfrujFNSV2RU6PiqNvGf8mYhxFyvkHbrRTzU4vQpMbdaMCpZF" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "rXQqPhrsCFwbtUGNFsvM9wR14183nGL8BCTU2mN2Z3xUr8UZMeNGfCC1RkxxvaVs8ubfa3Dhxo2WdUxhFE2JsZo" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3Caf5EmCGrfpB4vsjT7Musw9PCwyBbjNTZZqyczA6g4gUmTJUnbnCrzETjmKaC3cXzEywHfFyXb64fKrWK5DNBwu" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "21qrgR6XXoE5Jv97MEa6w11nQuzo3trkKcLwrtGMdkdPRyVbyTPNzRet32rX3taZeLjXvidprw2iW1b183381hFJ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2vW324Kn9QxQdYngn6Wu5QJZ3AuMCHBSVEw1nUaRtxvmbS4efRVJho5ChoVLjYJwxA6hxJbvkhgwAFdEqyxdgjik" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3jaU8Atz1tgcFHA6SPuforum8dm5jbADJxoqmekw77mRv96aj2gh36mP4jESbLZRVfh9Zpce921SbDKrHCrgr96X" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2L4A9ufMmhCwc4VcC1guL5vW5B3CzjyhBuBAneU2GpedNMPXTDALY4vFATgxwZuxSW7zCUgjcuRvQaiYPfUUqiK" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "33McXDGhwLbvF9fxSfEzR7dQZeapRD3hUdnscL4t7XD5TKS5SGgJBKExJ7LFAxrVpsHoxnxBLoTqJwj4JfLMKkkn" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5KQhdTnMAwDDFs23t2NWvSKfBZuf3Q4zm4R687CurZKWVXPuwkmX7ipt63wahDRxcqRreeaJ2YYtJ6updHjj2ttq" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5oK9gF3gxUmpUyQNLMfCg71a7E8X8Rx3Xr7J7k6LupsBK44XraFe48osC3sJG2XCYgTqaxCUZA4MGUN97XEmszPp" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4XwE8LmHJaKN1W1DycyFKQHHn8qapekjHaK5vReqdPDKzGpimns2xM9CsxutKY4E9ewZQvc9vRJTkeWp4LQWcG1F" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "rgUY7fCjwaGHD9f8UyAXJ4yJgNHgDnmyC7EWU7SDDuGo3KJPasZtMJheLbNXNGVygAmZL2fEpzJkAMhL4cJLwP3" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5LPjsubaXqAhrRoLF3s7XjZUmtTe38xFffVvar8YYATsMy6SVBfYssnTjBs8pQd5xh6krGzSmrppSmzC17GX4UZ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "crGaYYkbCWAXEoVmtjzJXhYWifE68TkAxvZjNEUqA2AkWJiENcFbaSR5HetE3mHrKEkgJAxEgjAPT34UDKBhJni" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2hZ6E58B3xgVK6AoVLbyqoYLGKYzDNUJMLBao9nhhypyxt7axM9UwRWVZ7QdV4W6mkXWCeAfMGTG9aXyvyTqXTH2" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "g3uJ37m5SfGXazaTG1XHBZRSShCoEJtbyGsCR519JithwsfEqbcvNDK5YeyVK4nVL17PVZvoHD7xevk7X7JvRU7" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4FSc4RRBBcbsu6wmLLJocciKADFW7NfbRi29aJ4fnJeocRzqiSbGzBztb1gjB5JMRuZtqvWWViM4oreYdTzW4zue" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5RaiJk4Mi1SNbEQFDQqibKZVpKxLt9CE9F5GaAAyLyDjLS8kQtK9nNTGdaHcJCVZFkBrAnRec1YKaC2kmUCzQcc6" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4JvQ3RV68fskV9ZiXRjVqDb3vyjyiwjwjywYYuKbY66v2dekU7NdbwR8ZwjecHVHRaKNNzkAvT4UzVNAwPPBVHMS" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3SxDqNZw8dMZzB8tCRSFYjxFcAEmbJ5VFws8JnN1E4PNbiETKjTBbe3iKPN3EneJDpSYvemJvz9Ehp419YkJSDJx" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5nfU5c9Te7wVsukb1aWyq5ZNTqqjfnAaa4QYPwcvEcsf8LKri9DgiChvcJjNSvttjr5LcENb2TBvHVBjPcqjcQMh" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4b6mMQwDJCw433Cw9JQ3dPBu8gPXHCr1zoVe4GGpUHMFQgyQUmrCGjE4vKr9GX3ScTr62v7bSqV9FoSRhAi19cKn" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "63T1U8hdY474uvrMV7AyCjH8iu73ecaupDuXipmuoXgAs2BaR5Wc5JznuMGCYJSSzhXfCwCZGctSfqLqRBBUuTBf" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5PKa8ivDGwxUQNt3tzGqYNfBH5nv6nVKCWpM8Xz5ux8N3jfrSfiCjqxam1wNWXHqEKhW5wDPh3yp2tqGh9PeiC5S" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3BFT4dya32fQFN1QsPagUPUVDKXFCXkhHbuzMEvMyz6J9m5CpQV7mDoFurUD47NbxqfNQxU7J5GaMZe2PzfREfCm" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "Zh61npxEFUczgipGC9sQViGhqj2hah1rozGyGUWNxsg5ADTMJKaKyKcmQbnPJFfiBf5VJcPsgerUhNfwJKEDNRZ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5HH3tZLopGgABacuzkTv8bf5qCaZdX73zqbPwHmaAk4uarnoU2c3NXpjCn9MKBNYt3GWyGVnTSVSFBQW5K7JJJnx" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "n6LvprxWRjsdaiJ15gCz9nejyouqK9CXcuEZc5YPQnChWpnR3jvaeeo23WBVj13hNH3nzv665KJR52B7wA37HrY" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "BWHLdy5XVZ3ZbAFFj41BPHGeErWdLX9Ajw91AEo9GcuhkePgtCBPg48nxLoBTaCEjhgnFePeX8qLtiL1jHKecHD" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2tctTRyYsJcoxNaVCRn22JJxPZEPKMPY9nT5XuBkMr3MQ3rKgGXrtfno9eiascgF6SjE2k1dCzmNq28gazhRrse7" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "23n2mMNLaRQ4Y8YX9m9nViLPXG1pWsFSAkVwf4WKnAG37TuaJEAaLWwWUcwo8LY77RRhY5scGKL8cGizxpCq2kqB" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "qeGvAQN6MPn41uCKpLNzaw7Mqw1VEYUJfW5Mf5BaEwS6fk5WEtYYkswYoR9EKapruRVLApW7ko3EnjC6XEWSTcZ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "234krJcAkmM6V33CJiBMVwZdWNWMNT3PpsfnZT8L99yPDtL9Gk3n3SaN71rDsQs9wGCHfKAJZMVxrJvHtojsJzWt" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3dGxB78sVYj8YUUvGD8s4KXVEu5Cc94sepDbqCio9MjGaqfPMhz6muR71WEWuL9xEcV3VrcmKXUHU883BGbDfqDg" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5MzRRvf7CSrBpYJoKnoe2U3qpj3iceiJZwtp3cipxWtqdkakCyA9YffygufiT6tcD5oHrJNrmqU4DJKjNa7p5G3Z" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5yBkU7qNUDUMXqZbUbXPCYYKTtMeHMekuznx9XXWuioAs4QfzMXzwnXP8t7BVd7iMJmDazo86GFqMpj33um1KNG6" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2nhKKA3rrQEu6FTpQ3SJwJuwBUYQY9Whbg1QqUwhBAURcs3UsfbjUuFVTZdNcCvvLFz8Z783F3jnjSY2hJJTvaFy" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "31RZmSH8y7WaKG8CZBoatKm2CuWtKkYaspnejRV2rrL6F3JfiaH3oLsnknrD3jzgtaSfDVtgpVRTaa8sHr7X3PSm" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4yiKrYVff43bPismeqR1K5UaWNnyi5a973VxUCoYCe8xogs74eocgqxHYfYDD2dmRjyjGzpzQ2q9aXcSDraYNvAP" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3uEGPvqhMwh9u4Dg3nbqtGAHGALrh5GX5ctvLS1Tj5srNNY92e9n5qpwkgCpqJ3kgXgugzRLx1JrPxxaKRXWDhXy" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2xvry7WtUQbsaPnFVmvdTVSE9o8ZFtD2WquT2m8zM6MUWx3MgioRxrG7VPJ5BWeWJy6zMdyjRXB47xUPSALrnEvk" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2oNqbjtjhVX183xemr1WcWMUJQ5Xr2gL5xzB5oYZ99mEwvfBDLgHLL2LEEFEj5XkYMh3h9aPcf2cubqv5bzciNj3" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "bXjqRN8wvBotSfLQc2Y3TVhMnAT1qSxjKtCVyTDSYzyHbtmZYMxvSrWn3Lik7oo1wZseArNaTpuucRfKtUC9DF8" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5VBquJ24Yu8UjcVWVHhNeHE7NKTYZdvKjSkWDs8xcbm2yKVsXATyjoYd9AEWggatB8Pv6bB5Zoq51UyzLFFhCQjn" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "22Pa9YQuFF21yEb3NvXzvbxAtu6c6MAAgUNnMS9VFQKBJpsDSwGhvDXrX8y26yMQosctZk1c6BZh6N1tAfTez3Ch" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4HNpF1jhNPQqZaFtSh97b479LaaaHBcikLhJj574Q2ip5mCppTmogY3UnqPcqFcRQjgzxuWob1kXq7qKknoqteXJ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "627uNFuZ1QaUFF1J7wQLMbhbztnkTQXjsbTSbornSCN3w9XMbBU39sRYDm3zncYCYNeC2NHGSgC831S2S2Rqk5Hf" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "63awhjyALPuzV8UyzMehs27ZMQrr6emjzQ3zFQkdby5TSB9ZkBrhGnkCTyHm4dcxJRPCt32ct7bK4PfLq7wWLJVB" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3u6prTnXA7jPGcX5XPWTbqQ44RcrV5iUMf478Qv5A4MuA7xmj8V2j8PCKfuw3GLz5K12DGQVYV9qi9bxwoJVDLwC" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3oUrVJnfS4TTsBdRH2QF5AcrV6ZMPT34nMHaBiHBcF7RQTrKSqFS2cXtFAPktGpg9CGT6FNr8kuucd8kHWhGsQx6" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2ZzNYciQ2Gfpg54nE2NMxvBAKLboLSq34AWKnbPhDCVkM1nE8jt77uBZ927srFWWhNcDjVw3W2AGYvRuWwV4jm51" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2gsz4G3toLvip7C8iYycMKdoTrV4cqF4Cd2xTmaLUhCQeSVT58tyL1fdvoXVvxW9YmeAKnUdJiUp7azVGchQ8DuH" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5MNy3k5uTV97PUTPE4gjKUaNxBXaU4c6AQFszqDTWZ4Zf6ahU63sfiy7D65fBsuu4BzKTd318Ne9bz67Z1hkVBsR" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4a71cEiJikTfv8nEXNxN9QdUjFT63fgRVELfcXED5tJGKJsuEQwjwPS9NQQqRzzTKL4jhs3ioyADKa18LyTxMRqE" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5a1VCq32gUcR79SN92qahj6yinnSwoyxhmDngSLdmUkZQmcKBjHsAh2eAiGoRm4cXx61tDqg5GSATD7UofnCgRi8" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2vUYbcLkvJkMT7SZrm49VoUVSYbGXDdLLZLPmrCjnMa8gcEtrqD7LG6EbBsWZ2iBm84EU5xi5boDjX8rnr2TTrZn" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3EJAuaLwokvMHgmZuMsH6tcbvPpfySJ7pM1PsprkyY9GfemyyYojRa9zmBFQXwPGsB1K3EnQFAR11b1c7ZsubLyS" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5nh6gTYEfDA4ZzHzoqxKWrcv7VBJ8mjdrkmFnGNaAVyEJ8FYqRUx5WhX9V39Rp4sS8CmrWhkiLh11yQzy51p7HUg" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "zaBifCyywS6qV3dGznJ95c5g4D2hoxbWUpRbDUXGYkdkBXuikFSSC1ECZCBVeq7YhFdgmtgnvCgWxnynZkRMWwB" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5j1wxMC6aGgCFSz1QV9tisiT82utubk4h9M8oEYkXawM6vEqRXpLkFcfuhKN1kZKDvW8wBRhoo7YisPmEvVGKu2m" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "24Eq4p9WhSgvDUwxwjCuqevZsUQzqbjCkijBiecpWVbdUUBqb6cUKCt2JXnEAfSPJzQjJBme7Fi3pMEaUL49PHM9" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3zuKL9AwoHTiUW4nr5J48P1zVhBMMbeX8isMjqgtha9gTF1famJ3DvfhbegaWWNDwhwKtpEZvew3JDnxqDCP5zAi" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5y2aGb8swMupCTZH5TUH2hqkNc7eWrkbFKyfgpEJgNwQqty8QBDjpv7gLmPWHHc3v6vWYQSQ4TPFfpPyFuDmPhCt" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5qHigydKMau4QJFVLjPeYewuKy4EittbDC4LcDrXN2dMhkhmGDJrnkCKwJGRbeC777KetFGWURdrSCrrTDWveWUc" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "33xo4QiwpX1W4VyPQ5yCv77KCpnn5W4aSLbTztAUoVf9rWe5sknUwp8bCfLLt56r934QwZCrcBv7TEwexTpo2EVH" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5CCB1n5RXirddtD2iqiKv5jMCPD4w6ZhDapga4iA6pvHNDN3u3ShL1R2xGmfBUpfitMdZnRV5MVt2a8M4cH2uFnU" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4vrhLD7TjZM9FYg9rtrX6cKJAR5s6iDwVVAvsnmNrJCstudDAPf8vPzuAZvJxPmqQ4iB5kVC6bZ2kP9A5CgsvLU4" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "rcCfNFYoAPGLsn4dsPPAW6i7hzFbKzT3bUij4mjrFLTWBQyWVeoH9Mxp9whsF9g3dv4LQieeSjx31jWnEa4vJZt" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2Mh1M1HQpe1b9RgWthrTHVDN9vda8qW46DSyeykeyG8iabDfi5MWbaRCWF6diD14qj9qad7NmNQnSb2GKroKoGEp" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "f8f9kWc6jxpq7xTtQwUoBX6mn3nqymFyBaCGfa2p3bNmEs6jPdWnNRMDcSamgo4KoS2Q9GsRPGTvbn2CHajMdgf" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "54oXBmmS6egddTmSCJrVnAKfiVEUGDJSHdQDApoNYzrvzR1U92ESC6HxDo7c8xBCBpu5DCdmetNaZtFCRVKXXZJV" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "cjkA1SAGWDTeQH43LqQaQMpe3chjvGuQjChbCCupm4iJGbFxgyPz3D4BjK3bGEJ2TwNiudQYZv9eGYYX5pEBn8k" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2TsAGP21XZ53KLHkwzcwK2xyhEcoTzPpkBpTCfiU6RKdAGDhrBnFXKA8xmFFVzAzjj5LWvxPC2MP3BU2znCLC5dC" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2NZkWZ8hT4iHcC8LFCh4NU29AP1PuFUYMWG7oX8QV1WQYFcSzV1KjpxfotMPDj14o3dQPTLCV1vqLQLKcNVHTyiJ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "21cDt3yMP1vVNRRT89fnYW6j2zZ2c17FVxakiWFe4UFjUoJsayembAFVjbYHhAHB29pywq7A2o4EH88GDafwo7h9" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4uR2TNHpyJ4qrF7C9GRAh5CfsgfjntpW5XzcnVWDNkbU84NLmHWAZhQcdrUcpKSvziKvNWfK4nsmq4ras7v4kJ4c" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "cjkSvAJyWarWfi3y4Fv5XkugjGcnYkTryW8MoQVBKwRmYiG2xgQqwt3QTvoPFpDEz1ceWjBoSaHWADvj7w28UTK" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "cyp6EUdy3mrC7rBvhUZKYDwzh9TNgg1hADhSe2v7a7Ha5BZ2JxiHHwCTFf5LmwLUW39fNA3BcFU8Xe5zYnMpkex" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "NcXCQ4XkizooMW6mwiRNVndTQbdHL6vPMNYjVjR1B2qfxQD39eFpqrmscKBWqij1wntTi9HUJdvhaNzK3wx7pwq" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "hxkYm7ZdX8xxaCBnB2jenmTA7MKm2apRHkgU5AX4BhCj3q8g3zwzZhf2ct5h6DtQmWmfA4ZESxbTy9rs58BjL3F" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3V4qRoMUTv8v8LDgsLuikn1vBDrJ6kh8gSDf4eJG5FMkHMSbR1tNkAbDkGYiDRuqa6BqK6ppnyEkYXW7Q34zjo51" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "nWPkucpSv8bEQbQCnJ5qy3fRHJ42LmaoqAF1kA4HguGFcnKzG5X4pruFMAGKPndFDmWySuX1zmu1ZoEs9C8NWFC" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2mqZDnRXLfd4oPQfGdpd2AssDYX1vUoCGzxQPp4q5WLW6rmaQCfRn39KzFYZkYDfDSF6hCrFZqCraZpUUL8sCtYy" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "fNK4RqzLb7s4xPcxJCixFcNbjPFU7iSwCJM4wcaFnB4UpBMEeKLx3SEQZDd4Lyag2QfwvZMootWfhau5VPpPfGF" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3ZnVAe3swEHF5VGJSJHWzdD3jMqGceepMiQ8B694S1FBRnyQteMfoh2xsnQWNWxsfvyQE5exxXSCxSZscZHgkcEP" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "5WKMm5hvk6fSQinVq1z61coN1G2Y2fbuzKsmj9tJvfjcjLYPdnXJyZ6hCKxUQf3pYfMRB4SaWwtB7xi68JaM3Fge" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3mYXQujZrvbkcPErxHaFZQHYKEw7zX4kMtDkcTkhqKF5seZBnSNeSDvPYaBQrUucW9smq2VkBE7D7QsfTmgz1eYZ" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "uwUqqWhfRoWdsc4qbRtbSQuYEmcTigHKXfnM136u2whZMq9w9yeiJyxD9Kx5QiF5yEcKf7o5d6jwj5G52y5ihey" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3VeQA7eTZHakSBLrAAL6E7CSfNCjdMbm84CccV765VTrcNaurh1GWC2zSKhnqtQ6fMy9eTLcWBMdV6iD4kbwzEd4" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "2yXq2JgJ1FWZADRTkNubns5skENYEjEX2XA27yqviDWFU2kSSEA8P4ZTkLXtYvGiyUsjkL7rDrMcN6EQXWn9ZZyq" seed = 57005 notes = [10_000_000_000] diff --git a/rusk/tests/config/contract_deployment.toml b/rusk/tests/config/contract_deployment.toml index a6502ad7ac..1d61cec0e9 100644 --- a/rusk/tests/config/contract_deployment.toml +++ b/rusk/tests/config/contract_deployment.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [10_000_000_000] diff --git a/rusk/tests/config/gas-behavior.toml b/rusk/tests/config/gas-behavior.toml index 984acdf5b4..2674bfa893 100644 --- a/rusk/tests/config/gas-behavior.toml +++ b/rusk/tests/config/gas-behavior.toml @@ -1,9 +1,9 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3MoVQ6VfGNu8fJ5GeHPRDVUfxcsDEmGXpWhvKhXY7F2dKCp7QWRw8RqPcbuJGdRqeTtxpuiwETnGAJLnhT4Kq4e8" seed = 57005 notes = [10_000_000_000] diff --git a/rusk/tests/config/multi_transfer.toml b/rusk/tests/config/multi_transfer.toml index 5ab88e2533..f3905c128f 100644 --- a/rusk/tests/config/multi_transfer.toml +++ b/rusk/tests/config/multi_transfer.toml @@ -1,14 +1,14 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3MoVQ6VfGNu8fJ5GeHPRDVUfxcsDEmGXpWhvKhXY7F2dKCp7QWRw8RqPcbuJGdRqeTtxpuiwETnGAJLnhT4Kq4e8" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4RyaodGmN8MyUDmpRrtRxJJhrVW2HsY2ycRUnRUXR97JCN1GHraQT9Ygb8yYo7oKzyZg2EXXCGkHBwoeNb96BKtQ" seed = 57005 notes = [10_000_000_000] diff --git a/rusk/tests/config/rusk-state.toml b/rusk/tests/config/rusk-state.toml index 46ea313173..dedee2cebc 100644 --- a/rusk/tests/config/rusk-state.toml +++ b/rusk/tests/config/rusk-state.toml @@ -1,3 +1,3 @@ -[[balance]] +[[phoenix_balance]] address = "29ENJqLtHJRSZdghZxiGuzQTe3F4t1bv35zM7mEMS142e5QdxkknokMALrBEFUnyav9NfeXLNvfjx4sfTtJN9WCB" notes = [10_000_000_000] diff --git a/rusk/tests/config/slash.toml b/rusk/tests/config/slash.toml index b55927269c..a25023954f 100644 --- a/rusk/tests/config/slash.toml +++ b/rusk/tests/config/slash.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [ diff --git a/rusk/tests/config/stake.toml b/rusk/tests/config/stake.toml index 91771b1607..9aca8ee612 100644 --- a/rusk/tests/config/stake.toml +++ b/rusk/tests/config/stake.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [ diff --git a/rusk/tests/config/transfer.toml b/rusk/tests/config/transfer.toml index 42d2e378c8..8bbbc89344 100644 --- a/rusk/tests/config/transfer.toml +++ b/rusk/tests/config/transfer.toml @@ -1,4 +1,4 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [ @@ -13,3 +13,7 @@ notes = [ 10_000_000_000, 10_000_000_000, ] + +[[moonlight_account]] +address = "qe1FbZxf6YaCAeFNSvL1G82cBhG4Q4gBf4vKYo527Vws3b23jdbBuzKSFsdUHnZeBgsTnyNJLkApEpRyJw87sdzR9g9iESJrG5ZgpCs9jq88m6d4qMY5txGpaXskRQmkzE3" +balance = 10_000_000_000 diff --git a/rusk/tests/config/unspendable.toml b/rusk/tests/config/unspendable.toml index 5ab88e2533..f3905c128f 100644 --- a/rusk/tests/config/unspendable.toml +++ b/rusk/tests/config/unspendable.toml @@ -1,14 +1,14 @@ -[[balance]] +[[phoenix_balance]] address = "ivmscertKgRyX8wNMJJsQcSVEyPsfSMUQXSAgeAPQXsndqFq9Pmknzhm61QvcEEdxPaGgxDS4RHpb6KKccrnSKN" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "3MoVQ6VfGNu8fJ5GeHPRDVUfxcsDEmGXpWhvKhXY7F2dKCp7QWRw8RqPcbuJGdRqeTtxpuiwETnGAJLnhT4Kq4e8" seed = 57005 notes = [10_000_000_000] -[[balance]] +[[phoenix_balance]] address = "4RyaodGmN8MyUDmpRrtRxJJhrVW2HsY2ycRUnRUXR97JCN1GHraQT9Ygb8yYo7oKzyZg2EXXCGkHBwoeNb96BKtQ" seed = 57005 notes = [10_000_000_000] diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index c5745cc5ce..9c2ca83884 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -204,7 +204,7 @@ async fn generate_phoenix_txs() -> Result<(), Box> { let task = tokio::task::spawn_blocking(move || { wallet - .transfer( + .phoenix_transfer( &mut rng, sender_index, &receiver, diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 6f2bd20ae0..e0d3c8db3f 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -113,7 +113,7 @@ fn make_and_execute_transaction_deploy( let hash = bytecode_hash(bytecode.as_ref()).to_bytes(); let tx = wallet - .execute( + .phoenix_execute( &mut rng, ContractExec::Deploy(ContractDeploy { bytecode: Bytecode { diff --git a/rusk/tests/services/gas_behavior.rs b/rusk/tests/services/gas_behavior.rs index 6cecb1a971..0d01ddc8a8 100644 --- a/rusk/tests/services/gas_behavior.rs +++ b/rusk/tests/services/gas_behavior.rs @@ -73,7 +73,7 @@ fn make_transactions( fn_args: Vec::new(), }; let tx_0 = wallet - .execute( + .phoenix_execute( &mut rng, ContractExec::Call(contract_call.clone()), SENDER_INDEX_0, @@ -87,9 +87,9 @@ fn make_transactions( // contract, querying for the root of the tree. This will be tested for // gas cost. let tx_1 = wallet - .execute( + .phoenix_execute( &mut rng, - ContractExec::Call(contract_call), + contract_call, SENDER_INDEX_1, GAS_LIMIT_1, 1, diff --git a/rusk/tests/services/multi_transfer.rs b/rusk/tests/services/multi_transfer.rs index cc0c722bd1..4619bcf650 100644 --- a/rusk/tests/services/multi_transfer.rs +++ b/rusk/tests/services/multi_transfer.rs @@ -88,7 +88,7 @@ fn wallet_transfer( for i in 0..3 { let tx = wallet - .transfer(&mut rng, i, &receiver, amount, GAS_LIMIT, 1) + .phoenix_transfer(&mut rng, i, &receiver, amount, GAS_LIMIT, 1) .expect("Failed to transfer"); txs.push(tx); } @@ -122,15 +122,17 @@ fn wallet_transfer( .get_balance(0) .expect("Failed to get the balance") .value; - let fee_0 = txs[0].payload().fee; - let fee_0 = fee_0.gas_limit * fee_0.gas_price; + let gas_limit_0 = txs[0].gas_limit(); + let gas_price_0 = txs[0].gas_price(); + let fee_0 = gas_limit_0 * gas_price_0; let final_balance_1 = wallet .get_balance(1) .expect("Failed to get the balance") .value; - let fee_1 = txs[1].payload().fee; - let fee_1 = fee_1.gas_limit * fee_1.gas_price; + let gas_limit_1 = txs[1].gas_limit(); + let gas_price_1 = txs[1].gas_price(); + let fee_1 = gas_limit_1 * gas_price_1; assert!( initial_balance_0 - amount - fee_0 <= final_balance_0, diff --git a/rusk/tests/services/stake.rs b/rusk/tests/services/stake.rs index 09a4e5fb0f..03acf61c50 100644 --- a/rusk/tests/services/stake.rs +++ b/rusk/tests/services/stake.rs @@ -8,8 +8,7 @@ use std::path::Path; use std::sync::{Arc, RwLock}; use execution_core::{ - transfer::{ContractCall, ContractExec}, - StakePublicKey, + stake::StakeAmount, transfer::ContractCall, BlsPublicKey, }; use rand::prelude::*; use rand::rngs::StdRng; @@ -72,7 +71,7 @@ fn wallet_stake( ); let tx = wallet - .stake(&mut rng, 0, 2, value, GAS_LIMIT, 1) + .phoenix_stake(&mut rng, 0, 2, value, GAS_LIMIT, 1) .expect("Failed to create a stake transaction"); let executed_txs = generator_procedure( rusk, @@ -92,7 +91,7 @@ fn wallet_stake( } let stake = wallet.get_stake(2).expect("stake to be found"); - let stake_value = stake.amount.expect("stake should have an amount").0; + let stake_value = stake.amount.expect("stake should have an amount").value; assert_eq!(stake_value, value); @@ -103,7 +102,7 @@ fn wallet_stake( .expect("stake amount to be found"); let tx = wallet - .unstake(&mut rng, 0, 0, GAS_LIMIT, 1) + .phoenix_unstake(&mut rng, 0, 0, GAS_LIMIT, 1) .expect("Failed to unstake"); let spent_txs = generator_procedure( rusk, @@ -121,7 +120,7 @@ fn wallet_stake( assert_eq!(stake.amount, None); let tx = wallet - .withdraw(&mut rng, 0, 1, GAS_LIMIT, 1) + .phoenix_withdraw(&mut rng, 0, 1, GAS_LIMIT, 1) .expect("failed to withdraw reward"); generator_procedure( rusk, @@ -189,8 +188,8 @@ fn wallet_reward( ) { let mut rng = StdRng::seed_from_u64(0xdead); - let stake_sk = wallet.store().retrieve_stake_sk(2).unwrap(); - let stake_pk = StakePublicKey::from(&stake_sk); + let stake_sk = wallet.store().fetch_account_secret_key(2).unwrap(); + let stake_pk = BlsPublicKey::from(&stake_sk); let reward_calldata = (stake_pk, 6u32); let stake = wallet.get_stake(2).expect("stake to be found"); @@ -203,14 +202,7 @@ fn wallet_reward( ) .expect("calldata should serialize"); let tx = wallet - .execute( - &mut rng, - ContractExec::Call(contract_call), - 0, - GAS_LIMIT, - 1, - 0, - ) + .phoenix_execute(&mut rng, contract_call, 0, GAS_LIMIT, 1, 0) .expect("Failed to create a reward transaction"); let executed_txs = generator_procedure( rusk, @@ -296,10 +288,16 @@ pub async fn slash() -> Result<()> { let contract_balance = rusk .contract_balance(STAKE_CONTRACT) .expect("balance to exists"); - let to_slash = wallet.stake_public_key(0).unwrap(); + let to_slash = wallet.account_public_key(0).unwrap(); let stake = wallet.get_stake(0).unwrap(); assert_eq!(stake.reward, dusk(3.0)); - assert_eq!(stake.amount, Some((dusk(20.0), 0))); + assert_eq!( + stake.amount, + Some(StakeAmount { + value: dusk(20.0), + eligibility: 0 + }) + ); generator_procedure( &rusk, @@ -324,9 +322,15 @@ pub async fn slash() -> Result<()> { let (_, prev) = last_changes.first().expect("Something changed").clone(); let prev = prev.expect("to have something"); assert_eq!(prev.reward, dusk(3.0)); - assert_eq!(prev.amount, Some((dusk(20.0), 0))); + assert_eq!( + prev.amount, + Some(StakeAmount { + value: dusk(20.0), + eligibility: 0 + }) + ); - let (prev_stake, _) = prev.amount.unwrap(); + let prev_stake = prev.amount.unwrap().value; let slashed_amount = prev_stake / 10; let after_slash = wallet.get_stake(0).unwrap(); @@ -334,9 +338,18 @@ pub async fn slash() -> Result<()> { assert_eq!(after_slash.reward, prev.reward + slashed_amount); assert_eq!( after_slash.amount, - Some((prev_stake - slashed_amount, 4320)) + Some(StakeAmount { + value: prev_stake - slashed_amount, + eligibility: 4320 + }) + ); + assert_eq!( + after_slash.amount, + Some(StakeAmount { + value: dusk(18.0), + eligibility: 4320 + }) ); - assert_eq!(after_slash.amount, Some((dusk(18.0), 4320))); let new_balance = rusk.contract_balance(STAKE_CONTRACT).unwrap(); assert_eq!(new_balance, contract_balance - slashed_amount); let contract_balance = new_balance; @@ -355,9 +368,15 @@ pub async fn slash() -> Result<()> { let (_, prev) = last_changes.first().expect("Something changed").clone(); let prev = prev.expect("to have something"); assert_eq!(prev.reward, dusk(5.0)); - assert_eq!(prev.amount, Some((dusk(18.0), 4320))); + assert_eq!( + prev.amount, + Some(StakeAmount { + value: dusk(18.0), + eligibility: 4320 + }) + ); - let (prev_stake, _) = prev.amount.unwrap(); + let prev_stake = prev.amount.unwrap().value; // 20% slash let slashed_amount = prev_stake / 10 * 2; @@ -366,9 +385,18 @@ pub async fn slash() -> Result<()> { assert_eq!(after_slash.reward, prev.reward + slashed_amount); assert_eq!( after_slash.amount, - Some((prev_stake - slashed_amount, 6480)) + Some(StakeAmount { + value: prev_stake - slashed_amount, + eligibility: 6480 + }) + ); + assert_eq!( + after_slash.amount, + Some(StakeAmount { + value: dusk(14.4), + eligibility: 6480 + }) ); - assert_eq!(after_slash.amount, Some((dusk(14.4), 6480))); let new_balance = rusk.contract_balance(STAKE_CONTRACT).unwrap(); assert_eq!(new_balance, contract_balance - slashed_amount); @@ -388,14 +416,26 @@ pub async fn slash() -> Result<()> { let (_, prev) = last_changes.first().expect("Something changed").clone(); let prev = prev.expect("to have something"); assert_eq!(prev.reward, dusk(8.6)); - assert_eq!(prev.amount, Some((dusk(14.4), 6480))); + assert_eq!( + prev.amount, + Some(StakeAmount { + value: dusk(14.4), + eligibility: 6480 + }) + ); - let (prev_stake, _) = prev.amount.unwrap(); + let prev_stake = prev.amount.unwrap().value; // 30% slash let slashed_amount = prev_stake / 10 * 3; let after_slash = wallet.get_stake(0).unwrap(); assert_eq!(after_slash.reward, dusk(12.92)); - assert_eq!(after_slash.amount, Some((dusk(10.08), 17280))); + assert_eq!( + after_slash.amount, + Some(StakeAmount { + value: dusk(10.08), + eligibility: 17280 + }) + ); let new_balance = rusk.contract_balance(STAKE_CONTRACT).unwrap(); assert_eq!(new_balance, contract_balance - slashed_amount); @@ -404,7 +444,7 @@ pub async fn slash() -> Result<()> { &[], 9001, BLOCK_GAS_LIMIT, - vec![wallet.stake_public_key(1).unwrap()], + vec![wallet.account_public_key(1).unwrap()], None, ) .expect_err("Slashing a public key that never staked must fail"); @@ -414,7 +454,13 @@ pub async fn slash() -> Result<()> { let (_, prev) = last_changes.first().expect("Something changed").clone(); let prev = prev.expect("to have something"); assert_eq!(prev.reward, dusk(8.6)); - assert_eq!(prev.amount, Some((dusk(14.4), 6480))); + assert_eq!( + prev.amount, + Some(StakeAmount { + value: dusk(14.4), + eligibility: 6480 + }) + ); generator_procedure(&rusk, &[], 9001, BLOCK_GAS_LIMIT, vec![], None) .expect("To work properly"); diff --git a/rusk/tests/services/transfer.rs b/rusk/tests/services/transfer.rs index fd03041cf8..51e1095ff0 100644 --- a/rusk/tests/services/transfer.rs +++ b/rusk/tests/services/transfer.rs @@ -71,7 +71,7 @@ fn wallet_transfer( // Execute a transfer let tx = wallet - .transfer(&mut rng, 0, &receiver_pk, amount, 1_000_000_000, 2) + .phoenix_transfer(&mut rng, 0, &receiver_pk, amount, 1_000_000_000, 2) .expect("Failed to transfer"); info!("Tx: {}", hex::encode(tx.to_var_bytes())); @@ -117,8 +117,7 @@ fn wallet_transfer( .get_balance(0) .expect("Failed to get the balance") .value; - let fee = tx.inner.inner.payload().fee; - let fee = gas_spent * fee.gas_price; + let fee = gas_spent * tx.inner.inner.gas_price(); assert_eq!( sender_initial_balance - amount - fee, diff --git a/rusk/tests/services/unspendable.rs b/rusk/tests/services/unspendable.rs index 2b4ddddd9e..af2c2d0c5b 100644 --- a/rusk/tests/services/unspendable.rs +++ b/rusk/tests/services/unspendable.rs @@ -85,7 +85,7 @@ fn make_transactions( fn_args: Vec::new(), }; let tx_0 = wallet - .execute( + .phoenix_execute( &mut rng, ContractExec::Call(contract_call.clone()), SENDER_INDEX_0, @@ -99,7 +99,7 @@ fn make_transactions( // contract, but with no enough gas to spend. Transaction should be // discarded let tx_1 = wallet - .execute( + .phoenix_execute( &mut rng, ContractExec::Call(contract_call.clone()), SENDER_INDEX_1, @@ -113,9 +113,9 @@ fn make_transactions( // transfer contract, querying for the root of the tree. This will be // tested for gas cost. let tx_2 = wallet - .execute( + .phoenix_execute( &mut rng, - ContractExec::Call(contract_call), + contract_call, SENDER_INDEX_2, GAS_LIMIT_2, 1,