From 182966a18502ff56d175994fc79de8d0d32002fc Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Thu, 22 Aug 2024 15:08:38 +0200 Subject: [PATCH 001/102] node: upgrade kadcast from `0.6` to `0.7` --- node/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/Cargo.toml b/node/Cargo.toml index b01cbf2d7a..c83175e6ea 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -12,7 +12,7 @@ license = "MPL-2.0" tracing = "0.1" hex = "0.4" dusk-consensus = { version = "0.1.1-rc.3", path = "../consensus" } -kadcast = "0.6.0-rc" +kadcast = "0.7.0-rc" sha3 = { version = "0.10" } anyhow = "1.0" async-trait = "0.1" From 6251f2d9af19fff5a3ae6d271ed454f4e1dc9fa1 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Thu, 22 Aug 2024 15:08:46 +0200 Subject: [PATCH 002/102] rusk: upgrade kadcast from `0.6` to `0.7` --- rusk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rusk/Cargo.toml b/rusk/Cargo.toml index 3eba219178..4b45adf0d8 100644 --- a/rusk/Cargo.toml +++ b/rusk/Cargo.toml @@ -44,7 +44,7 @@ blake2b_simd = { version = "1", default-features = false } poseidon-merkle = { version = "0.7", features = ["rkyv-impl", "size_32"] } sha3 = "0.10" dusk-bytes = "0.1" -kadcast = "0.6.0-rc" +kadcast = "0.7.0-rc" pin-project = "1" tungstenite = "0.21" hyper-tungstenite = "0.13" From 6a8ac246da29e64ebb5e329839f49f7a1a6f8d35 Mon Sep 17 00:00:00 2001 From: Milosz Muszynski Date: Mon, 26 Aug 2024 16:48:28 +0200 Subject: [PATCH 003/102] rusk-recovery: testnet moonlight accounts --- rusk-recovery/config/testnet.toml | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/rusk-recovery/config/testnet.toml b/rusk-recovery/config/testnet.toml index b8e52c120a..2b130d9a1d 100644 --- a/rusk-recovery/config/testnet.toml +++ b/rusk-recovery/config/testnet.toml @@ -1843,3 +1843,67 @@ reward = 52315800000000 address = '271zWxBm6QnM2qjfypWjE2h2RP7yYmc7nUqsozWCoZewDCUaBs86y7rmqCN6ejwoc6BPzrdu3erPBAev1yNRGdnVtemubxAQPPjzKK6bsHZrwKLPTHCJ8bZKXByaD4F9MFrk' amount = 0 reward = 8331482328410616 + +[[moonlight_account]] +address = '21QVDgB74QDYPy8n7SFnfhKaJfQi72eHh9XWJeXV33zKN7rjSGP1dgrVyn7FDGyz5QSH3HeRm73JZxAykoF51Cxn92hpvcad6XAS22R9H9CypYs5w3XuQdzw66MRJNmncSXv' +balance = 6_000_000_000_000_000 + +[[moonlight_account]] +address = '23uR8GKVMs54YHzo4JLmBwEHs3uMZpaHnogAjViiWJWgRCjpyzFC3QdwixAMVGDG7DxURKxQgnBmD6QdeWyuKs9AKNhG6J4ZV16uQK2gpLLBC5bY93Jn5porChsAbWmwUEx6' +balance = 6_100_000_000_000_000 + +[[moonlight_account]] +address = '26qQBmhVHSSny3bDnUgLKaiiRon92K7hVqih9r5mh2vD85gJ5Xm9uV7mfZTeR2hgmfVtPFd7Ndwejb5AKZE2aoYAJwsmSrTeK2MNLna29xrwcSBFe8hdL9r6AwsoVoKYrUYU' +balance = 6_200_000_000_000_000 + +[[moonlight_account]] +address = 'miYKKT714d8EfWtCXaxHwXLE3ecU4MpzNiefRr6eCypaSgfWrFMHHwmLdfiMJiEV5yoMuBmMk9zwfMpCMxaWzUgjXqvzvQpZVkQKSgAJH48z9UsnWqtvKYNHvEzQgLE6k4d' +balance = 6_300_000_000_000_000 + +[[moonlight_account]] +address = 'ybqmEJESvmA7iENSncBg8WBjeqgWiCDtGJBMpwujnHYW2yQQCu8MBXwbGAe1W9MnNnzA5beiWniHjJi5LYqRy65UrpRR23ks4ce4QEzEoewpsJZVDRmdJsAen6HfxMK8vfT' +balance = 6_400_000_000_000_000 + +[[moonlight_account]] +address = '24VYQ1kMbaUQQRzDBACk464BDg49cqUa1NqGJMmobkt2yNJmyDXV7g9oGHQWwSWCuJcyhisko8PotZgmEwPH8PtD7NuYcmuCkGfExgdR34nJyasXexGC3T7giQdR1DByU9Fp' +balance = 6_500_000_000_000_000 + +[[moonlight_account]] +address = 'mpZpUmJbZbHVRCpkxa4xX1cPH4u9GAe5P3HrFnTjXnPH2qb9M5vjJVjYmiZpevvfqYP4CSkN1mH9n6vpzp16iXeqHboMWiEZ5DwiD2qLG6tiviKAMuexcbEUSrczz3r4Q12' +balance = 6_600_000_000_000_000 + +[[moonlight_account]] +address = 'xPW1XCytA26P2WghNe3gcZ9831J9x8dakJCfGzGUYggkHH7zbp2kcJ6Qo3j72n3i7rjn11VpAHYyxRsV1zEJFukRGgpWBMfUuBu8t6aahi3By1vkP1RVV6Z8yzNLwhJcZSW' +balance = 6_700_000_000_000_000 + +[[moonlight_account]] +address = 'uHFCgFffLmQEiERwJDkSBo1Yzz2jiAnYhVukoiNji22zpLVJmZosBBnSkEtRofzUhVVYVeXkzVUNiuFhyF3jZUgxNdW4eJLS6zPX51DB1ULUpNQ8ttgnTBMvbZBKehCz1i6' +balance = 6_800_000_000_000_000 + +[[moonlight_account]] +address = '23kPyggJ6jrmf1kfkRFr9bRJYGEKqAg7a5zhW5zYq4VswVT7pBuFF7f3hd6Us2teRaCHkURqUJpRALGHwa9dKNvovKAuS6oUtyXdyrkh3rPVKoAS8qAyWrMYbsXUBrDB3Bb3' +balance = 6_900_000_000_000_000 + +[[moonlight_account]] +address = '22qWN8wCZLKXoLgdVCF1noGm65jQkRSs5XvKzARSNPFfcTXExXXCnwro1RuVRiVK84hUWXqWegieBo72qdWr8i9ALMuaxzmrKGVLFMoP59crzePUnYopEmyJDbWTHyhwX8Ps' +balance = 7_000_000_000_000_000 + +[[moonlight_account]] +address = 'qpLZtWwmNBMTGzcgC7sZ7Ctq2dDjNbc5iSN4aVmVtVK8B5RrbJfS9uLqmvcCn9CBFkq6GS8N6YXqJWPivWWY8LRdRnMkp9xxMFngGCc5SV3ZnUMpnnh7vzLF2Q7mP8cCS3a' +balance = 7_100_000_000_000_000 + +[[moonlight_account]] +address = '214twXJifCt8TeGRFaxcAcb1HUScSuGa9K5vLHZ26Xyb9iTUCesPjH4YCiMN2tzHQeYuB6e2HEtNvXitaWqP68NiV71wrfNPft4ExcoKzR29LduJb3iM3kQNnMrFS8aw197F' +balance = 7_200_000_000_000_000 + +[[moonlight_account]] +address = '212Pk7N9EgebdFtU8tBc998LEp7Xa6i3fh14P4LeXwXWcM4wkqXUmzBgaUVxWHohUNbRmhv5wAxEwidVR6YqHb8bXEtVDR6B6CTD8V2StqEaqjfBFA6aFgXxEkDRG8AzoyqH' +balance = 7_300_000_000_000_000 + +[[moonlight_account]] +address = 'xtnvj8ZT6WNGNmKVU4rUKABzKtxgY5NYwxb9vXZdEMwkXg4XNQzuiF9uXqYWDyzd4hMnyNqDZMjb7xteQXg5S5jLD3MFoeyrKL3MWMRMCjpTUTnXnvLbA92uKRQci194KKY' +balance = 7_400_000_000_000_000 + +[[moonlight_account]] +address = 'mx2GUePBkCsJENhowYK1ugn3XzT7iuMSqRUmK3ri39m8oF6wjsCtSUWN4CwPzzvLd5sijTnnecCortKhHgKSFZRjZHxUZf8V14AV9Bhv7u7LC45X2FkfNm7MRKsmaNPnseN' +balance = 7_500_000_000_000_000 From b486c178a6c46a1fa6130171f063214e86b44acf Mon Sep 17 00:00:00 2001 From: Federico Franzoni <8609060+fed-franz@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:29:32 +0200 Subject: [PATCH 004/102] consensus: remove remove_msgs_greater_than --- consensus/src/queue.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/consensus/src/queue.rs b/consensus/src/queue.rs index ddbc208cf9..873c3b33c1 100644 --- a/consensus/src/queue.rs +++ b/consensus/src/queue.rs @@ -58,11 +58,6 @@ impl MsgRegistry { self.0.remove(&round); } - /// Removes all messages that belong to a round greater than the specified. - pub fn remove_msgs_greater_than(&mut self, round: u64) { - self.0.split_off(&round); - } - /// Removes all messages that do not belong to the range (closed interval) /// of keys pub fn remove_msgs_out_of_range(&mut self, start_round: u64, offset: u64) { @@ -121,30 +116,6 @@ mod tests { assert!(reg.drain_msg_by_round_step(round, 2).is_none()); } - #[test] - fn test_remove() { - #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] - struct Item(i32); - - let round = 100; - - let mut reg = MsgRegistry::::default(); - reg.put_msg(round + 1, 1, Item(1)); - reg.put_msg(round + 2, 1, Item(1)); - reg.put_msg(round + 3, 1, Item(1)); - reg.put_msg(round, 1, Item(1)); - - reg.remove_msgs_greater_than(round + 2); - - assert!(reg.drain_msg_by_round_step(round, 1).is_some()); - assert!(reg.drain_msg_by_round_step(round + 1, 1).is_some()); - - assert!(reg.drain_msg_by_round_step(round + 2, 1).is_none()); - assert!(reg.drain_msg_by_round_step(round + 3, 1).is_none()); - - assert_eq!(reg.msg_count(), 0); - } - #[test] fn test_remove_msgs_out_of_range() { #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] From 36bdfefa762a5775d8454611cfdcc175190fb9de Mon Sep 17 00:00:00 2001 From: Federico Franzoni <8609060+fed-franz@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:07:09 +0200 Subject: [PATCH 005/102] consensus: do not call collect_from_past for Candidate --- consensus/src/consensus.rs | 3 +- consensus/src/execution_ctx.rs | 75 +++++++++++++++--------------- consensus/src/iteration_ctx.rs | 18 ++----- consensus/src/phase.rs | 4 +- consensus/src/proposal/step.rs | 2 +- consensus/src/ratification/step.rs | 17 +++---- consensus/src/validation/step.rs | 6 +-- 7 files changed, 54 insertions(+), 71 deletions(-) diff --git a/consensus/src/consensus.rs b/consensus/src/consensus.rs index 95c0915410..8f84add44c 100644 --- a/consensus/src/consensus.rs +++ b/consensus/src/consensus.rs @@ -161,7 +161,7 @@ impl Consensus { Phase::Proposal(proposal::step::ProposalStep::new( executor.clone(), db.clone(), - proposal_handler.clone(), + proposal_handler, )), Phase::Validation(validation::step::ValidationStep::new( executor.clone(), @@ -179,7 +179,6 @@ impl Consensus { let mut iter_ctx = IterationCtx::new( ru.round, iter, - proposal_handler, validation_handler, ratification_handler, ru.base_timeouts.clone(), diff --git a/consensus/src/execution_ctx.rs b/consensus/src/execution_ctx.rs index 26d443a318..016fcf05af 100644 --- a/consensus/src/execution_ctx.rs +++ b/consensus/src/execution_ctx.rs @@ -4,7 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::commons::{ConsensusError, Database, QuorumMsgSender, RoundUpdate}; +use crate::commons::{ConsensusError, QuorumMsgSender, RoundUpdate}; use crate::iteration_ctx::IterationCtx; use crate::msg_handler::{HandleMsgOutput, MsgHandler}; @@ -34,8 +34,8 @@ use tracing::{debug, error, info, trace, warn}; /// ExecutionCtx encapsulates all data needed in the execution of consensus /// messages handlers. -pub struct ExecutionCtx<'a, DB: Database, T> { - pub iter_ctx: &'a mut IterationCtx, +pub struct ExecutionCtx<'a, T> { + pub iter_ctx: &'a mut IterationCtx, /// Messaging-related fields pub inbound: AsyncQueue, @@ -57,11 +57,11 @@ pub struct ExecutionCtx<'a, DB: Database, T> { quorum_sender: QuorumMsgSender, } -impl<'a, DB: Database, T: Operations + 'static> ExecutionCtx<'a, DB, T> { +impl<'a, T: Operations + 'static> ExecutionCtx<'a, T> { /// Creates step execution context. #[allow(clippy::too_many_arguments)] pub(crate) fn new( - iter_ctx: &'a mut IterationCtx, + iter_ctx: &'a mut IterationCtx, inbound: AsyncQueue, outbound: AsyncQueue, future_msgs: Arc>>, @@ -220,7 +220,7 @@ impl<'a, DB: Database, T: Operations + 'static> ExecutionCtx<'a, DB, T> { if let Some(committee) = self.iter_ctx.committees.get_committee(step) { if self.am_member(committee) { - RatificationStep::::try_vote( + RatificationStep::try_vote( &self.round_update, msg_iteration, validation, @@ -248,41 +248,40 @@ impl<'a, DB: Database, T: Operations + 'static> ExecutionCtx<'a, DB, T> { async fn on_emergency_mode(&mut self, msg: Message) { self.outbound.try_send(msg.clone()); - // Try to cast validation vote for a candidate block from former - // iteration + // Try to vote for a candidate block from former iteration if let Payload::Candidate(p) = &msg.payload { self.try_cast_validation_vote(&p.candidate).await; - } - - let msg_iteration = msg.header.iteration; - - // Collect message from a previous iteration/step. - if let Some(m) = self - .iter_ctx - .collect_past_event(&self.round_update, msg) - .await - { - match &m.payload { - Payload::Quorum(q) => { - debug!( - event = "quorum", - src = "prev_step", - msg_step = m.get_step(), - vote = ?q.vote(), - ); - - self.quorum_sender.send_quorum(m).await; - } + } else { + let msg_iteration = msg.header.iteration; + + // Collect message from a previous iteration/step. + if let Some(m) = self + .iter_ctx + .collect_past_event(&self.round_update, msg) + .await + { + match &m.payload { + Payload::Quorum(q) => { + debug!( + event = "quorum", + src = "prev_step", + msg_step = m.get_step(), + vote = ?q.vote(), + ); + + self.quorum_sender.send_quorum(m).await; + } - Payload::ValidationResult(validation_result) => { - self.try_cast_ratification_vote( - msg_iteration, - validation_result, - ) - .await - } - _ => { - // Not supported. + Payload::ValidationResult(validation_result) => { + self.try_cast_ratification_vote( + msg_iteration, + validation_result, + ) + .await + } + _ => { + // Not supported. + } } } } diff --git a/consensus/src/iteration_ctx.rs b/consensus/src/iteration_ctx.rs index af8a680bf8..071fcb4685 100644 --- a/consensus/src/iteration_ctx.rs +++ b/consensus/src/iteration_ctx.rs @@ -4,7 +4,6 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::commons::Database; use crate::commons::{RoundUpdate, TimeoutSet}; use std::cmp; @@ -14,7 +13,7 @@ use crate::msg_handler::MsgHandler; use crate::user::committee::Committee; -use crate::{proposal, ratification, validation}; +use crate::{ratification, validation}; use node_data::bls::PublicKeyBytes; use node_data::message::Message; @@ -60,11 +59,10 @@ impl RoundCommittees { /// Represents a shared state within a context of the execution of a single /// iteration. -pub struct IterationCtx { +pub struct IterationCtx { validation_handler: Arc>, ratification_handler: Arc>, - proposal_handler: Arc>>, pub join_set: JoinSet<()>, @@ -79,11 +77,10 @@ pub struct IterationCtx { timeouts: TimeoutSet, } -impl IterationCtx { +impl IterationCtx { pub fn new( round: u64, iter: u8, - proposal_handler: Arc>>, validation_handler: Arc>, ratification_handler: Arc< Mutex, @@ -94,7 +91,6 @@ impl IterationCtx { round, join_set: JoinSet::new(), iter, - proposal_handler, validation_handler, ratification_handler, committees: Default::default(), @@ -152,12 +148,6 @@ impl IterationCtx { let generator = self.get_generator(msg.header.iteration); match msg.topic() { - node_data::message::Topics::Candidate => { - let mut handler = self.proposal_handler.lock().await; - _ = handler - .collect_from_past(msg, ru, committee, generator) - .await; - } node_data::message::Topics::Validation => { let mut handler = self.validation_handler.lock().await; if let Ok(HandleMsgOutput::Ready(m)) = handler @@ -183,7 +173,7 @@ impl IterationCtx { } } -impl Drop for IterationCtx { +impl Drop for IterationCtx { fn drop(&mut self) { self.on_close(); } diff --git a/consensus/src/phase.rs b/consensus/src/phase.rs index 5ce672debf..bb0a159311 100644 --- a/consensus/src/phase.rs +++ b/consensus/src/phase.rs @@ -29,7 +29,7 @@ macro_rules! await_phase { pub enum Phase { Proposal(proposal::step::ProposalStep), Validation(validation::step::ValidationStep), - Ratification(ratification::step::RatificationStep), + Ratification(ratification::step::RatificationStep), } impl Phase { @@ -54,7 +54,7 @@ impl Phase { pub async fn run( &mut self, - mut ctx: ExecutionCtx<'_, D, T>, + mut ctx: ExecutionCtx<'_, T>, ) -> Result { ctx.set_start_time(); diff --git a/consensus/src/proposal/step.rs b/consensus/src/proposal/step.rs index ec89979aa3..a0c4984c97 100644 --- a/consensus/src/proposal/step.rs +++ b/consensus/src/proposal/step.rs @@ -53,7 +53,7 @@ impl ProposalStep { pub async fn run( &mut self, - mut ctx: ExecutionCtx<'_, D, T>, + mut ctx: ExecutionCtx<'_, T>, ) -> Result { let committee = ctx .get_current_committee() diff --git a/consensus/src/ratification/step.rs b/consensus/src/ratification/step.rs index 24335a6a3d..8154089b42 100644 --- a/consensus/src/ratification/step.rs +++ b/consensus/src/ratification/step.rs @@ -4,10 +4,9 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::commons::{ConsensusError, Database, RoundUpdate}; +use crate::commons::{ConsensusError, RoundUpdate}; use crate::execution_ctx::ExecutionCtx; use crate::operations::Operations; -use std::marker::PhantomData; use crate::msg_handler::{HandleMsgOutput, MsgHandler}; use crate::ratification::handler; @@ -19,12 +18,11 @@ use tokio::sync::Mutex; use tracing::{info, Instrument}; -pub struct RatificationStep { +pub struct RatificationStep { handler: Arc>, - marker: PhantomData, } -impl RatificationStep { +impl RatificationStep { pub async fn try_vote( ru: &RoundUpdate, iteration: u8, @@ -70,14 +68,11 @@ pub fn build_ratification_payload( ratification } -impl RatificationStep { +impl RatificationStep { pub(crate) fn new( handler: Arc>, ) -> Self { - Self { - handler, - marker: PhantomData, - } + Self { handler } } pub async fn reinitialize( @@ -112,7 +107,7 @@ impl RatificationStep { pub async fn run( &mut self, - mut ctx: ExecutionCtx<'_, DB, T>, + mut ctx: ExecutionCtx<'_, T>, ) -> Result { let committee = ctx .get_current_committee() diff --git a/consensus/src/validation/step.rs b/consensus/src/validation/step.rs index 9484ca01b2..d87c9b7000 100644 --- a/consensus/src/validation/step.rs +++ b/consensus/src/validation/step.rs @@ -4,7 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::commons::{ConsensusError, Database, RoundUpdate}; +use crate::commons::{ConsensusError, RoundUpdate}; use crate::config; use crate::execution_ctx::ExecutionCtx; use crate::operations::{Operations, Voter}; @@ -227,9 +227,9 @@ impl ValidationStep { ) } - pub async fn run( + pub async fn run( &mut self, - mut ctx: ExecutionCtx<'_, DB, T>, + mut ctx: ExecutionCtx<'_, T>, ) -> Result { let committee = ctx .get_current_committee() From b77cf033a968b0c153b1aa4e8fb4ff558497a42e Mon Sep 17 00:00:00 2001 From: Federico Franzoni <8609060+fed-franz@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:16:36 +0200 Subject: [PATCH 006/102] consensus: delete delete_candidate_blocks --- consensus/src/commons.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/consensus/src/commons.rs b/consensus/src/commons.rs index efeb48e831..558d5aa63e 100644 --- a/consensus/src/commons.rs +++ b/consensus/src/commons.rs @@ -139,7 +139,6 @@ impl From for ConsensusError { #[async_trait::async_trait] pub trait Database: Send + Sync { fn store_candidate_block(&mut self, b: Block); - fn delete_candidate_blocks(&mut self); } #[derive(Clone)] From 057d1bd60674108af08447d56f2158f7357eba3f Mon Sep 17 00:00:00 2001 From: Federico Franzoni <8609060+fed-franz@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:16:53 +0200 Subject: [PATCH 007/102] node: delete delete_candidate_blocks --- node/src/chain/consensus.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/node/src/chain/consensus.rs b/node/src/chain/consensus.rs index d8150151bd..dfd58cbf32 100644 --- a/node/src/chain/consensus.rs +++ b/node/src/chain/consensus.rs @@ -211,19 +211,6 @@ impl dusk_consensus::commons::Database for CandidateDB { } } } - - fn delete_candidate_blocks(&mut self) { - match self.db.try_read() { - Ok(db) => { - if let Err(e) = db.update(|t| t.clear_candidates()) { - warn!("Unable to cleare candidates: {e}"); - }; - } - Err(e) => { - warn!("Cannot acquire lock to clear_candidate: {e}"); - } - } - } } /// Implements Executor trait to mock Contract Storage calls. From f68752614034cf4a6d58817cdd2787d7913138d4 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Tue, 27 Aug 2024 14:14:42 +0200 Subject: [PATCH 008/102] node-data: fix transaction entity --- node-data/src/events/transactions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-data/src/events/transactions.rs b/node-data/src/events/transactions.rs index c94b56bbab..1577352341 100644 --- a/node-data/src/events/transactions.rs +++ b/node-data/src/events/transactions.rs @@ -50,8 +50,8 @@ impl EventSource for TransactionEvent<'_> { fn entity(&self) -> String { let hash = match self { Self::Removed(hash) => *hash, - Self::Executed(tx) => tx.inner.hash(), - Self::Included(tx) => tx.hash(), + Self::Executed(tx) => tx.inner.id(), + Self::Included(tx) => tx.id(), }; hex::encode(hash) } From 3090223d1f2fe514dbd526531994aca85b454cb0 Mon Sep 17 00:00:00 2001 From: Andrea Scartabelli Date: Fri, 30 Aug 2024 10:31:23 +0200 Subject: [PATCH 009/102] explorer: Show the error message for failed transactions Resolves #2220 --- explorer/CHANGELOG.md | 3 + .../__tests__/TransactionStatus.spec.js | 67 ++++++++++++++++ .../TransactionDetails.spec.js.snap | 4 +- .../TransactionStatus.spec.js.snap | 44 +++++++++++ .../TransactionsCard.spec.js.snap | 22 +++--- .../TransactionsList.spec.js.snap | 6 +- .../TransactionsTable.spec.js.snap | 66 ++++++++-------- explorer/src/lib/components/index.js | 1 + .../TransactionDetails.svelte | 7 +- .../transaction-status/TransactionStatus.css | 3 + .../TransactionStatus.svelte | 40 ++++++++++ .../transactions-list/TransactionsList.svelte | 20 +++-- .../TransactionsTable.svelte | 8 +- .../dusk/components/__tests__/Badge.spec.js | 11 +++ .../__snapshots__/Badge.spec.js.snap | 9 +++ .../lib/dusk/components/badge/Badge.svelte | 2 +- .../src/lib/mock-data/gql-transactions.json | 2 +- .../__tests__/__snapshots__/page.spec.js.snap | 10 +-- .../__tests__/__snapshots__/page.spec.js.snap | 79 ++++++++----------- .../__tests__/__snapshots__/page.spec.js.snap | 2 +- 20 files changed, 285 insertions(+), 121 deletions(-) create mode 100644 explorer/src/lib/components/__tests__/TransactionStatus.spec.js create mode 100644 explorer/src/lib/components/__tests__/__snapshots__/TransactionStatus.spec.js.snap create mode 100644 explorer/src/lib/components/transaction-status/TransactionStatus.css create mode 100644 explorer/src/lib/components/transaction-status/TransactionStatus.svelte diff --git a/explorer/CHANGELOG.md b/explorer/CHANGELOG.md index 749a1a3019..4ed76b332c 100644 --- a/explorer/CHANGELOG.md +++ b/explorer/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Show the error message for failed transactions [#2220] + ### Changed ### Fixed @@ -55,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#2059]: https://github.com/dusk-network/rusk/issues/2059 [#2061]: https://github.com/dusk-network/rusk/issues/2061 [#2159]: https://github.com/dusk-network/rusk/issues/2159 +[#2220]: https://github.com/dusk-network/rusk/issues/2220 diff --git a/explorer/src/lib/components/__tests__/TransactionStatus.spec.js b/explorer/src/lib/components/__tests__/TransactionStatus.spec.js new file mode 100644 index 0000000000..96d8ae028c --- /dev/null +++ b/explorer/src/lib/components/__tests__/TransactionStatus.spec.js @@ -0,0 +1,67 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { cleanup, render } from "@testing-library/svelte"; + +import { TransactionStatus } from ".."; + +describe("TransactionStatus", () => { + const baseProps = { + errorMessage: "", + }; + const baseOptions = { + props: baseProps, + target: document.body, + }; + + afterEach(cleanup); + + it("should render the transaction status", () => { + const { container } = render(TransactionStatus, baseOptions); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should pass additional class names to the rendered `Badge` component", () => { + const props = { + ...baseProps, + className: "foo bar", + }; + + const { container } = render(TransactionStatus, { ...baseOptions, props }); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should render the status as error if it receives an error message", () => { + const props = { + ...baseProps, + errorMessage: "Transaction failed", + }; + + const { container } = render(TransactionStatus, { ...baseOptions, props }); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should add tooltip info if it receives an error message and the related property is set", () => { + const props = { + ...baseProps, + errorMessage: "Transaction failed", + showErrorTooltip: true, + }; + + const { container } = render(TransactionStatus, { ...baseOptions, props }); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("shouldn't add tooltip info if there's no error message, even if the related property is set to `true`", () => { + const props = { + ...baseProps, + showErrorTooltip: true, + }; + + const { container } = render(TransactionStatus, { ...baseOptions, props }); + + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/explorer/src/lib/components/__tests__/__snapshots__/TransactionDetails.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/TransactionDetails.spec.js.snap index 555e7c29db..2fe39e4604 100644 --- a/explorer/src/lib/components/__tests__/__snapshots__/TransactionDetails.spec.js.snap +++ b/explorer/src/lib/components/__tests__/__snapshots__/TransactionDetails.spec.js.snap @@ -104,7 +104,7 @@ exports[`Transaction Details > renders the Transaction Details component 1`] = ` class="details-list__definition" > success @@ -430,7 +430,7 @@ exports[`Transaction Details > renders the Transaction Details component with th class="details-list__definition" > success diff --git a/explorer/src/lib/components/__tests__/__snapshots__/TransactionStatus.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/TransactionStatus.spec.js.snap new file mode 100644 index 0000000000..c81eb3c61a --- /dev/null +++ b/explorer/src/lib/components/__tests__/__snapshots__/TransactionStatus.spec.js.snap @@ -0,0 +1,44 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`TransactionStatus > should add tooltip info if it receives an error message and the related property is set 1`] = ` + + failed + +`; + +exports[`TransactionStatus > should pass additional class names to the rendered \`Badge\` component 1`] = ` + + success + +`; + +exports[`TransactionStatus > should render the status as error if it receives an error message 1`] = ` + + failed + +`; + +exports[`TransactionStatus > should render the transaction status 1`] = ` + + success + +`; + +exports[`TransactionStatus > shouldn't add tooltip info if there's no error message, even if the related property is set to \`true\` 1`] = ` + + success + +`; diff --git a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap index 0a090903f5..0a9def3f55 100644 --- a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap +++ b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsCard.spec.js.snap @@ -179,7 +179,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -254,9 +254,9 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > - success + failed @@ -329,7 +329,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -404,7 +404,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -479,7 +479,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -554,7 +554,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -629,7 +629,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -704,7 +704,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -779,7 +779,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success @@ -854,7 +854,7 @@ exports[`Transactions Card > should disable the \`Show More\` button if there is class="table__data-cell" > success diff --git a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsList.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsList.spec.js.snap index 22ca90c48f..bd76e87e83 100644 --- a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsList.spec.js.snap +++ b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsList.spec.js.snap @@ -67,11 +67,10 @@ exports[`Transactions List > should render the \`TransactionsList\` component in class="details-list__definition details-list__definition--table" > success -
should render the \`TransactionsList\` component in class="details-list__definition details-list__definition--table" > success -
should pass additional class names to the rendered class="table__data-cell" > success @@ -184,9 +184,9 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > - success + failed @@ -259,7 +259,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -334,7 +334,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -409,7 +409,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -484,7 +484,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -559,7 +559,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -634,7 +634,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -709,7 +709,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -784,7 +784,7 @@ exports[`Transactions Table > should pass additional class names to the rendered class="table__data-cell" > success @@ -891,7 +891,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -945,9 +945,9 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > - success + failed @@ -999,7 +999,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1053,7 +1053,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1107,7 +1107,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1161,7 +1161,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1215,7 +1215,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1269,7 +1269,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1323,7 +1323,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1377,7 +1377,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1511,7 +1511,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1586,9 +1586,9 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > - success + failed @@ -1661,7 +1661,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1736,7 +1736,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1811,7 +1811,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1886,7 +1886,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -1961,7 +1961,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -2036,7 +2036,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -2111,7 +2111,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success @@ -2186,7 +2186,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component class="table__data-cell" > success diff --git a/explorer/src/lib/components/index.js b/explorer/src/lib/components/index.js index 86e43cbe63..95e2609e45 100644 --- a/explorer/src/lib/components/index.js +++ b/explorer/src/lib/components/index.js @@ -22,4 +22,5 @@ export { default as TransactionsCard } from "./transactions-card/TransactionsCar export { default as TransactionDetails } from "./transaction-details/TransactionDetails.svelte"; export { default as TransactionsList } from "./transactions-list/TransactionsList.svelte"; export { default as TransactionsTable } from "./transactions-table/TransactionsTable.svelte"; +export { default as TransactionStatus } from "./transaction-status/TransactionStatus.svelte"; export { default as WorldMap } from "./world-map/WorldMap.svelte"; diff --git a/explorer/src/lib/components/transaction-details/TransactionDetails.svelte b/explorer/src/lib/components/transaction-details/TransactionDetails.svelte index fe23cd19a3..7eaa49f5a0 100644 --- a/explorer/src/lib/components/transaction-details/TransactionDetails.svelte +++ b/explorer/src/lib/components/transaction-details/TransactionDetails.svelte @@ -7,6 +7,7 @@ DataGuard, ListItem, StaleDataNotice, + TransactionStatus, } from "$lib/components"; import { Badge, Card, RelativeTime, Switch } from "$lib/dusk/components"; import { createValueFormatter } from "$lib/dusk/value"; @@ -102,10 +103,10 @@ Status diff --git a/explorer/src/lib/components/transaction-status/TransactionStatus.css b/explorer/src/lib/components/transaction-status/TransactionStatus.css new file mode 100644 index 0000000000..55e99ca19f --- /dev/null +++ b/explorer/src/lib/components/transaction-status/TransactionStatus.css @@ -0,0 +1,3 @@ +.transaction-status[data-tooltip-id] { + cursor: help; +} diff --git a/explorer/src/lib/components/transaction-status/TransactionStatus.svelte b/explorer/src/lib/components/transaction-status/TransactionStatus.svelte new file mode 100644 index 0000000000..fa73714753 --- /dev/null +++ b/explorer/src/lib/components/transaction-status/TransactionStatus.svelte @@ -0,0 +1,40 @@ + + + + + diff --git a/explorer/src/lib/components/transactions-list/TransactionsList.svelte b/explorer/src/lib/components/transactions-list/TransactionsList.svelte index 6ec8525a1a..ba0a8e7250 100644 --- a/explorer/src/lib/components/transactions-list/TransactionsList.svelte +++ b/explorer/src/lib/components/transactions-list/TransactionsList.svelte @@ -1,7 +1,13 @@ -{text} +{text} diff --git a/explorer/src/lib/mock-data/gql-transactions.json b/explorer/src/lib/mock-data/gql-transactions.json index d29d060b4b..5283f8d26b 100644 --- a/explorer/src/lib/mock-data/gql-transactions.json +++ b/explorer/src/lib/mock-data/gql-transactions.json @@ -18,7 +18,7 @@ "blockHash": "6971e17e1603d0651b4145ce404f9c7eeaa72182d94ef8856da980d38530d800", "blockHeight": 1377411, "blockTimestamp": 1716961328, - "err": null, + "err": "Panic: failed deployment", "gasSpent": 215617, "id": "534f753d5a420fddc21c84d76ff26b87cc48ea75b450c082759d155153259c00", "tx": { diff --git a/explorer/src/routes/blocks/block/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/blocks/block/__tests__/__snapshots__/page.spec.js.snap index 54ef722bf2..275a5b5fbe 100644 --- a/explorer/src/routes/blocks/block/__tests__/__snapshots__/page.spec.js.snap +++ b/explorer/src/routes/blocks/block/__tests__/__snapshots__/page.spec.js.snap @@ -565,7 +565,7 @@ exports[`Block Details > should render the Block Details page and query the nece class="table__data-cell" > success @@ -640,7 +640,7 @@ exports[`Block Details > should render the Block Details page and query the nece class="table__data-cell" > success @@ -1235,11 +1235,10 @@ exports[`Block Details > should render the Transaction section of the Block Deta class="details-list__definition details-list__definition--table" > success -
should render the Transaction section of the Block Deta class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > - success + failed -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page with the mobile class="details-list__definition details-list__definition--table" > success -
should render the Transactions page, start polling class="table__data-cell" > success @@ -2061,9 +2046,9 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > - success + failed @@ -2136,7 +2121,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2211,7 +2196,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2286,7 +2271,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2361,7 +2346,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2436,7 +2421,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2511,7 +2496,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2586,7 +2571,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2661,7 +2646,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2736,7 +2721,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2811,7 +2796,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2886,7 +2871,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -2961,7 +2946,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success @@ -3036,7 +3021,7 @@ exports[`Transactions page > should render the Transactions page, start polling class="table__data-cell" > success diff --git a/explorer/src/routes/transactions/transaction/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/transactions/transaction/__tests__/__snapshots__/page.spec.js.snap index 173ca08b62..bab68fdd81 100644 --- a/explorer/src/routes/transactions/transaction/__tests__/__snapshots__/page.spec.js.snap +++ b/explorer/src/routes/transactions/transaction/__tests__/__snapshots__/page.spec.js.snap @@ -142,7 +142,7 @@ exports[`Transaction Details > should render the Transaction details page and qu class="details-list__definition" > success From 38377ba237fd5150f27922e2c624b97778b556ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 11:30:26 +0100 Subject: [PATCH 010/102] rusk-abi: add chain ID as queryable data Contracts are given access to the ID of the chain being operated. This will allow contracts to check if a call was made originally on this chain, - by mixing the chain ID into a signature for example - effectively allowing them to prevent replay of calls performed on other chains. --- contracts/host_fn/src/lib.rs | 9 +++++++++ rusk-abi/src/abi.rs | 5 +++++ rusk-abi/src/host.rs | 6 +++++- rusk-abi/src/lib.rs | 3 ++- rusk-abi/tests/lib.rs | 21 +++++++++++++++++++-- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/contracts/host_fn/src/lib.rs b/contracts/host_fn/src/lib.rs index bece500144..5b5a7c5f70 100644 --- a/contracts/host_fn/src/lib.rs +++ b/contracts/host_fn/src/lib.rs @@ -64,6 +64,10 @@ impl HostFnTest { rusk_abi::verify_bls(msg, pk, sig) } + pub fn chain_id(&self) -> u8 { + rusk_abi::chain_id() + } + pub fn block_height(&self) -> u64 { rusk_abi::block_height() } @@ -108,6 +112,11 @@ unsafe fn verify_bls(arg_len: u32) -> u32 { }) } +#[no_mangle] +unsafe fn chain_id(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |_: ()| STATE.chain_id()) +} + #[no_mangle] unsafe fn block_height(arg_len: u32) -> u32 { rusk_abi::wrap_call(arg_len, |_: ()| STATE.block_height()) diff --git a/rusk-abi/src/abi.rs b/rusk-abi/src/abi.rs index 24aceaaae9..15942c8880 100644 --- a/rusk-abi/src/abi.rs +++ b/rusk-abi/src/abi.rs @@ -55,6 +55,11 @@ pub fn verify_bls(msg: Vec, pk: BlsPublicKey, sig: BlsSignature) -> bool { host_query(Query::VERIFY_BLS, (msg, pk, sig)) } +/// Get the chain ID. +pub fn chain_id() -> u8 { + meta_data(Metadata::CHAIN_ID).unwrap() +} + /// Get the current block height. pub fn block_height() -> u64 { meta_data(Metadata::BLOCK_HEIGHT).unwrap() diff --git a/rusk-abi/src/host.rs b/rusk-abi/src/host.rs index a6fb2b4ff4..13cefec46d 100644 --- a/rusk-abi/src/host.rs +++ b/rusk-abi/src/host.rs @@ -32,20 +32,24 @@ use crate::{Metadata, Query}; pub fn new_session( vm: &VM, base: [u8; 32], + chain_id: u8, block_height: u64, ) -> Result { vm.session( SessionData::builder() .base(base) + .insert(Metadata::CHAIN_ID, chain_id)? .insert(Metadata::BLOCK_HEIGHT, block_height)?, ) } /// Create a new genesis session based on the given `vm`. The vm *must* have /// been created using [`new_vm`] or [`new_ephemeral_vm`]. -pub fn new_genesis_session(vm: &VM) -> Session { +pub fn new_genesis_session(vm: &VM, chain_id: u8) -> Session { vm.session( SessionData::builder() + .insert(Metadata::CHAIN_ID, chain_id) + .expect("Inserting chain ID in metadata should succeed") .insert(Metadata::BLOCK_HEIGHT, 0) .expect("Inserting block height in metadata should succeed"), ) diff --git a/rusk-abi/src/lib.rs b/rusk-abi/src/lib.rs index 6724ceb2a6..ba8342c5c6 100644 --- a/rusk-abi/src/lib.rs +++ b/rusk-abi/src/lib.rs @@ -28,7 +28,7 @@ pub use piecrust_uplink::debug as piecrust_debug; mod abi; #[cfg(feature = "abi")] pub use abi::{ - block_height, hash, owner, owner_raw, poseidon_hash, self_owner, + block_height, chain_id, hash, owner, owner_raw, poseidon_hash, self_owner, self_owner_raw, verify_bls, verify_proof, verify_schnorr, }; @@ -65,6 +65,7 @@ enum Metadata {} #[allow(dead_code)] impl Metadata { + pub const CHAIN_ID: &'static str = "chain_id"; pub const BLOCK_HEIGHT: &'static str = "block_height"; } diff --git a/rusk-abi/tests/lib.rs b/rusk-abi/tests/lib.rs index 61bf1a15fa..932885a96f 100644 --- a/rusk-abi/tests/lib.rs +++ b/rusk-abi/tests/lib.rs @@ -32,6 +32,7 @@ use ff::Field; use rusk_abi::{ContractData, Session, VM}; const POINT_LIMIT: u64 = 0x1000000; +const CHAIN_ID: u8 = 0xFA; #[test] fn hash_host() { @@ -62,7 +63,7 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { "../../target/dusk/wasm32-unknown-unknown/release/host_fn.wasm" ); - let mut session = rusk_abi::new_genesis_session(vm); + let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID); let contract_id = session .deploy( @@ -74,7 +75,7 @@ fn instantiate(vm: &VM, height: u64) -> (Session, ContractId) { let base = session.commit().expect("Committing should succeed"); - let session = rusk_abi::new_session(vm, base, height) + let session = rusk_abi::new_session(vm, base, CHAIN_ID, height) .expect("Instantiating new session should succeed"); (session, contract_id) @@ -336,6 +337,22 @@ fn plonk_proof() { assert!(!valid, "The proof should be invalid"); } +#[test] +fn chain_id() { + const HEIGHT: u64 = 123; + + let vm = + rusk_abi::new_ephemeral_vm().expect("Instantiating VM should succeed"); + let (mut session, contract_id) = instantiate(&vm, HEIGHT); + + let chain_id: u8 = session + .call(contract_id, "chain_id", &(), POINT_LIMIT) + .expect("Query should succeed") + .data; + + assert_eq!(chain_id, CHAIN_ID); +} + #[test] fn block_height() { const HEIGHT: u64 = 123; From 25f2e662f515a4e42e8b016bb4a9704d35f3d4f5 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 31 Aug 2024 09:40:12 +0200 Subject: [PATCH 011/102] wallet-core: Store the `Note`s together with their block-height --- wallet-core/src/lib.rs | 27 +++++++++++++++------------ wallet-core/tests/notes.rs | 38 +++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs index 98e64c467b..5adcb7e87a 100644 --- a/wallet-core/src/lib.rs +++ b/wallet-core/src/lib.rs @@ -35,24 +35,27 @@ use execution_core::{ BlsScalar, }; -/// Filter all notes that are owned by the given keys, mapped to their -/// nullifiers. +/// Tuple containing Note and block height +pub type EnrichedNote = (Note, u64); + +/// Filter all notes and their block height that are owned by the given keys, +/// mapped to their nullifiers. pub fn map_owned( keys: impl AsRef<[PhoenixSecretKey]>, - notes: impl AsRef<[Note]>, -) -> BTreeMap { - notes - .as_ref() - .iter() - .fold(BTreeMap::new(), |mut notes_map, note| { + notes: impl AsRef<[EnrichedNote]>, +) -> BTreeMap { + notes.as_ref().iter().fold( + BTreeMap::new(), + |mut notes_map, enriched_note| { for sk in keys.as_ref() { - if sk.owns(note.stealth_address()) { - let nullifier = note.gen_nullifier(sk); - notes_map.insert(nullifier, note.clone()); + if sk.owns(enriched_note.0.stealth_address()) { + let nullifier = enriched_note.0.gen_nullifier(sk); + notes_map.insert(nullifier, enriched_note.clone()); } } notes_map - }) + }, + ) } /// Calculate the sum for all the given [`Note`]s that belong to the given diff --git a/wallet-core/tests/notes.rs b/wallet-core/tests/notes.rs index c9ecfce44f..a6c425ad2b 100644 --- a/wallet-core/tests/notes.rs +++ b/wallet-core/tests/notes.rs @@ -44,37 +44,37 @@ fn test_map_owned() { PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); let value = 42; - let notes = vec![ - gen_note(&mut rng, true, &owner_1_pks[0], value), // owner 1 - gen_note(&mut rng, true, &owner_1_pks[1], value), // owner 1 - gen_note(&mut rng, true, &owner_2_pks[0], value), // owner 2 - gen_note(&mut rng, true, &owner_2_pks[1], value), // owner 2 - gen_note(&mut rng, true, &owner_1_pks[2], value), // owner 1 - gen_note(&mut rng, true, &owner_3_pk, value), // owner 3 + let enriched_notes = vec![ + (gen_note(&mut rng, true, &owner_1_pks[0], value), 1), // owner 1 + (gen_note(&mut rng, true, &owner_1_pks[1], value), 1), // owner 1 + (gen_note(&mut rng, true, &owner_2_pks[0], value), 1), // owner 2 + (gen_note(&mut rng, true, &owner_2_pks[1], value), 1), // owner 2 + (gen_note(&mut rng, true, &owner_1_pks[2], value), 1), // owner 1 + (gen_note(&mut rng, true, &owner_3_pk, value), 1), // owner 3 ]; // notes with idx 0, 1 and 4 are owned by owner_1 - let notes_by_1 = map_owned(&owner_1_sks, ¬es); + let notes_by_1 = map_owned(&owner_1_sks, &enriched_notes); assert_eq!(notes_by_1.len(), 3); - let note = ¬es[0]; + let note = &enriched_notes[0].0; let nullifier = note.gen_nullifier(&owner_1_sks[0]); - assert_eq!(¬es_by_1[&nullifier], note); - let note = ¬es[1]; + assert_eq!(¬es_by_1[&nullifier].0, note); + let note = &enriched_notes[1].0; let nullifier = note.gen_nullifier(&owner_1_sks[1]); - assert_eq!(¬es_by_1[&nullifier], note); - let note = ¬es[4]; + assert_eq!(¬es_by_1[&nullifier].0, note); + let note = &enriched_notes[4].0; let nullifier = note.gen_nullifier(&owner_1_sks[2]); - assert_eq!(¬es_by_1[&nullifier], note); + assert_eq!(¬es_by_1[&nullifier].0, note); // notes with idx 2 and 3 are owned by owner_2 - let notes_by_2 = map_owned(&owner_2_sks, ¬es); + let notes_by_2 = map_owned(&owner_2_sks, &enriched_notes); assert_eq!(notes_by_2.len(), 2); - let note = ¬es[2]; + let note = &enriched_notes[2].0; let nullifier = note.gen_nullifier(&owner_2_sks[0]); - assert_eq!(¬es_by_2[&nullifier], note); - let note = ¬es[3]; + assert_eq!(¬es_by_2[&nullifier].0, note); + let note = &enriched_notes[3].0; let nullifier = note.gen_nullifier(&owner_2_sks[1]); - assert_eq!(¬es_by_2[&nullifier], note); + assert_eq!(¬es_by_2[&nullifier].0, note); } #[test] From 983de8f337bc10a12dd42fcfd41bb4490179b14c Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 31 Aug 2024 09:49:33 +0200 Subject: [PATCH 012/102] test-wallet: Move `EnrichedNote` to `wallet-core` --- test-wallet/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs index 32d4fbe7bd..2df93dffec 100644 --- a/test-wallet/src/lib.rs +++ b/test-wallet/src/lib.rs @@ -31,8 +31,9 @@ use execution_core::{ BlsScalar, }; -pub use wallet_core::keys::{ - derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk, +pub use wallet_core::{ + keys::{derive_bls_sk, derive_phoenix_pk, derive_phoenix_sk}, + EnrichedNote, }; pub use imp::*; @@ -104,9 +105,6 @@ pub trait Store { } } -/// Tuple containing Note and block height -pub type EnrichedNote = (Note, u64); - /// Types that are clients of the state API. pub trait StateClient { /// Error returned by the node client. From e89d258fc18d2c6da8a3ccef1dfd9f0e83cde388 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Fri, 30 Aug 2024 14:21:37 +0200 Subject: [PATCH 013/102] rusk: add rues event from request --- rusk/src/lib/http/event.rs | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/rusk/src/lib/http/event.rs b/rusk/src/lib/http/event.rs index dcfe557908..73e5048843 100644 --- a/rusk/src/lib/http/event.rs +++ b/rusk/src/lib/http/event.rs @@ -846,6 +846,56 @@ pub struct RuesEvent { } impl RuesEvent { + pub fn is_binary(&self) -> bool { + self.headers + .get(CONTENT_TYPE) + .map(|h| h.to_string()) + .map(|v| v.eq_ignore_ascii_case(CONTENT_TYPE_BINARY)) + .unwrap_or_default() + } + pub async fn from_request(req: Request) -> anyhow::Result { + let (parts, body) = req.into_parts(); + + let uri = RuesEventUri::parse_from_path(parts.uri.path()) + .ok_or(anyhow::anyhow!("Invalid URL path"))?; + + let headers = parts + .headers + .iter() + .map(|(k, v)| { + let v = if v.is_empty() { + serde_json::Value::Null + } else { + serde_json::from_slice::(v.as_bytes()) + .unwrap_or(serde_json::Value::String( + v.to_str().unwrap().to_string(), + )) + }; + (k.to_string().to_lowercase(), v) + }) + .collect(); + + // HTTP REQUEST + let content_type = parts + .headers + .get(CONTENT_TYPE) + .and_then(|h| h.to_str().ok()) + .unwrap_or_default(); + + let bytes = body.collect().await?.to_bytes().to_vec(); + let data = match content_type { + CONTENT_TYPE_BINARY => bytes.into(), + _ => DataType::Text( + String::from_utf8(bytes) + .map_err(|e| anyhow::anyhow!("Invalid utf8"))?, + ), + }; + + let ret = RuesEvent { headers, data, uri }; + + Ok(ret) + } + pub fn add_header, V: Into>( &mut self, key: K, From c797c53b2579eb3572ff69b34507feb312346017 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Fri, 30 Aug 2024 14:22:00 +0200 Subject: [PATCH 014/102] rusk: remove SubscriptionAction::Dispatch --- rusk/src/lib/http.rs | 52 +++++++++++++------------------------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/rusk/src/lib/http.rs b/rusk/src/lib/http.rs index 9c62064208..d9ceca07a3 100644 --- a/rusk/src/lib/http.rs +++ b/rusk/src/lib/http.rs @@ -426,7 +426,6 @@ where enum SubscriptionAction { Subscribe(RuesEventUri), Unsubscribe(RuesEventUri), - Dispatch { uri: RuesEventUri, body: Incoming }, } async fn handle_stream_rues( @@ -459,17 +458,8 @@ async fn handle_stream_rues( const DISPATCH_BUFFER_SIZE: usize = 16; let mut subscription_set = HashSet::new(); - let (dispatch_sender, dispatch_events) = - mpsc::channel(DISPATCH_BUFFER_SIZE); - // Join the two event receivers together, allowing for reusing the exact - // same code when handling them either of them. let mut events = BroadcastStream::new(events); - let mut dispatch_events = ReceiverStream::new(dispatch_events); - - let mut events = events - .map_err(Either::Left) - .merge(dispatch_events.map_err(Either::Right)); loop { tokio::select! { @@ -502,36 +492,23 @@ async fn handle_stream_rues( SubscriptionAction::Unsubscribe(subscription) => { subscription_set.remove(&subscription); }, - SubscriptionAction::Dispatch { - uri, - body - } => { - // TODO figure out if we should subscribe to the event we dispatch - task::spawn(handle_dispatch(uri, body, handler.clone(), dispatch_sender.clone())); - } } } Some(event) = events.next() => { let mut event = match event { Ok(event) => event, - Err(err) => match err { - Either::Left(_berr) => { - // If the event channel is closed, it means the - // server has stopped producing events, so we - // should inform the client and stop. - let _ = stream.close(Some(CloseFrame { - code: CloseCode::Away, - reason: Cow::from("Shutting down"), - })).await; - break; + Err(err) => { + // If the event channel is closed, it means the + // server has stopped producing events, so we + // should inform the client and stop. + let _ = stream.close(Some(CloseFrame { + code: CloseCode::Away, + reason: Cow::from("Shutting down"), + })).await; + break; - } - Either::Right(_eerr) => { - // TODO handle execution error - continue; - }, - }, + } }; // The event is subscribed to if it matches any of the subscriptions. @@ -648,6 +625,11 @@ async fn handle_request_rues( )); Ok(response.map(Into::into)) + } else if req.method() == Method::POST { + let event = RuesEvent::from_request(req).await?; + unimplemented!("Handle rues dispatch here"); + // TODO: Handle rues dispatch + // handle_dispatch(uri, body, handler, sender) } else { let headers = req.headers(); @@ -684,10 +666,6 @@ async fn handle_request_rues( let action = match *req.method() { Method::GET => SubscriptionAction::Subscribe(uri), Method::DELETE => SubscriptionAction::Unsubscribe(uri), - Method::POST => SubscriptionAction::Dispatch { - uri, - body: req.into_body(), - }, _ => { return response( StatusCode::METHOD_NOT_ALLOWED, From 26d5f0b80a13ea9afd942eee155e1755759a8a4b Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Fri, 30 Aug 2024 15:22:49 +0200 Subject: [PATCH 015/102] rusk: add implementation for RUES dispatch Resolves #2203 --- rusk/src/lib/http.rs | 95 +++++++++++++++++++++++++++++++++++-- rusk/src/lib/http/chain.rs | 59 +++++++++++++++++++---- rusk/src/lib/http/event.rs | 72 ++++++++++++++++++++++++---- rusk/src/lib/http/prover.rs | 16 +++++++ rusk/src/lib/http/rusk.rs | 65 ++++++++++++++++++------- 5 files changed, 270 insertions(+), 37 deletions(-) diff --git a/rusk/src/lib/http.rs b/rusk/src/lib/http.rs index d9ceca07a3..f2e4ee60eb 100644 --- a/rusk/src/lib/http.rs +++ b/rusk/src/lib/http.rs @@ -68,7 +68,9 @@ use rand::rngs::OsRng; use crate::http::event::FullOrStreamBody; use crate::VERSION; -pub use self::event::{ContractEvent, RuesEvent, RUES_LOCATION_PREFIX}; +pub use self::event::{ + ContractEvent, RuesDispatchEvent, RuesEvent, RUES_LOCATION_PREFIX, +}; use self::event::{MessageRequest, ResponseData, RuesEventUri, SessionId}; use self::stream::{Listener, Stream}; @@ -147,6 +149,24 @@ impl HandleRequest for DataSources { } Err(anyhow::anyhow!("unsupported target type")) } + + fn can_handle_rues(&self, event: &RuesDispatchEvent) -> bool { + self.sources.iter().any(|s| s.can_handle_rues(event)) + } + + async fn handle_rues( + &self, + event: &RuesDispatchEvent, + ) -> anyhow::Result { + info!("Received event at {}", event.uri); + event.check_rusk_version()?; + for h in &self.sources { + if h.can_handle_rues(event) { + return h.handle_rues(event).await; + } + } + Err(anyhow::anyhow!("unsupported location")) + } } #[derive(Clone)] @@ -626,10 +646,30 @@ async fn handle_request_rues( Ok(response.map(Into::into)) } else if req.method() == Method::POST { - let event = RuesEvent::from_request(req).await?; - unimplemented!("Handle rues dispatch here"); - // TODO: Handle rues dispatch - // handle_dispatch(uri, body, handler, sender) + let event = RuesDispatchEvent::from_request(req).await?; + let is_binary = event.is_binary(); + let mut resp_headers = event.x_headers(); + let (responder, mut receiver) = mpsc::unbounded_channel(); + handle_execution_rues(handler, event, responder).await; + + let execution_response = receiver + .recv() + .await + .expect("An execution should always return a response"); + resp_headers.extend(execution_response.headers.clone()); + let mut resp = execution_response.into_http(is_binary)?; + + for (k, v) in resp_headers { + let k = HeaderName::from_str(&k)?; + let v = match v { + serde_json::Value::String(s) => HeaderValue::from_str(&s), + serde_json::Value::Null => HeaderValue::from_str(""), + _ => HeaderValue::from_str(&v.to_string()), + }?; + resp.headers_mut().append(k, v); + } + + Ok(resp) } else { let headers = req.headers(); @@ -775,6 +815,35 @@ async fn handle_execution( let _ = responder.send(rsp); } +async fn handle_execution_rues( + sources: Arc, + event: RuesDispatchEvent, + responder: mpsc::UnboundedSender, +) where + H: HandleRequest, +{ + let mut rsp = sources + .handle_rues(&event) + .await + .map(|data| { + let (data, mut headers) = data.into_inner(); + headers.append(&mut event.x_headers()); + EventResponse { + data, + error: None, + headers, + } + }) + .unwrap_or_else(|e| EventResponse { + headers: event.x_headers(), + data: DataType::None, + error: Some(e.to_string()), + }); + + rsp.set_header(RUSK_VERSION_HEADER, serde_json::json!(*VERSION)); + let _ = responder.send(rsp); +} + #[async_trait] pub trait HandleRequest: Send + Sync + 'static { fn can_handle(&self, request: &MessageRequest) -> bool; @@ -782,6 +851,12 @@ pub trait HandleRequest: Send + Sync + 'static { &self, request: &MessageRequest, ) -> anyhow::Result; + + fn can_handle_rues(&self, request: &RuesDispatchEvent) -> bool; + async fn handle_rues( + &self, + request: &RuesDispatchEvent, + ) -> anyhow::Result; } #[cfg(test)] @@ -812,6 +887,16 @@ mod tests { true } + fn can_handle_rues(&self, request: &RuesDispatchEvent) -> bool { + false + } + async fn handle_rues( + &self, + request: &RuesDispatchEvent, + ) -> anyhow::Result { + unimplemented!() + } + async fn handle( &self, request: &MessageRequest, diff --git a/rusk/src/lib/http/chain.rs b/rusk/src/lib/http/chain.rs index 220fc3e0a6..7a47dcddb7 100644 --- a/rusk/src/lib/http/chain.rs +++ b/rusk/src/lib/http/chain.rs @@ -22,7 +22,7 @@ use graphql::{DBContext, Query}; use async_graphql::{ EmptyMutation, EmptySubscription, Name, Schema, Variables, }; -use serde_json::json; +use serde_json::{json, Map, Value}; use super::*; use crate::node::RuskNode; @@ -30,10 +30,9 @@ use crate::{VERSION, VERSION_BUILD}; const GQL_VAR_PREFIX: &str = "rusk-gqlvar-"; -fn variables_from_request(request: &MessageRequest) -> Variables { +fn variables_from_headers(headers: &Map) -> Variables { let mut var = Variables::default(); - request - .headers + headers .iter() .filter_map(|(h, v)| { let h = h.to_lowercase(); @@ -47,17 +46,60 @@ fn variables_from_request(request: &MessageRequest) -> Variables { var } + #[async_trait] impl HandleRequest for RuskNode { fn can_handle(&self, request: &MessageRequest) -> bool { matches!(request.event.to_route(), (Target::Host(_), "Chain", _)) } + + fn can_handle_rues(&self, request: &RuesDispatchEvent) -> bool { + #[allow(clippy::match_like_matches_macro)] + match request.uri.inner() { + ("graphql", _, "query") => true, + ("transactions", _, "propagate") => true, + ("network", _, "peers") => true, + ("node", _, "info") => true, + ("blocks", _, "gas-price") => true, + _ => false, + } + } + async fn handle_rues( + &self, + request: &RuesDispatchEvent, + ) -> anyhow::Result { + match request.uri.inner() { + ("graphql", _, "query") => { + self.handle_gql(&request.data, &request.headers).await + } + ("transactions", _, "propagate") => { + self.propagate_tx(request.data.as_bytes()).await + } + ("network", _, "peers") => { + let amount = request.data.as_string().trim().parse()?; + self.alive_nodes(amount).await + } + ("node", _, "info") => self.get_info().await, + ("blocks", _, "gas-price") => { + let max_transactions = request + .data + .as_string() + .trim() + .parse::() + .unwrap_or(usize::MAX); + self.get_gas_price(max_transactions).await + } + _ => anyhow::bail!("Unsupported"), + } + } async fn handle( &self, request: &MessageRequest, ) -> anyhow::Result { match &request.event.to_route() { - (Target::Host(_), "Chain", "gql") => self.handle_gql(request).await, + (Target::Host(_), "Chain", "gql") => { + self.handle_gql(&request.event.data, &request.headers).await + } (Target::Host(_), "Chain", "propagate_tx") => { self.propagate_tx(request.event_data()).await } @@ -83,9 +125,10 @@ impl HandleRequest for RuskNode { impl RuskNode { async fn handle_gql( &self, - request: &MessageRequest, + data: &RequestData, + headers: &serde_json::Map, ) -> anyhow::Result { - let gql_query = request.event.data.as_string(); + let gql_query = data.as_string(); let schema = Schema::build(Query, EmptyMutation, EmptySubscription) .data(self.db()) @@ -95,7 +138,7 @@ impl RuskNode { return Ok(ResponseData::new(schema.sdl())); } - let variables = variables_from_request(request); + let variables = variables_from_headers(headers); let gql_query = async_graphql::Request::new(gql_query).variables(variables); diff --git a/rusk/src/lib/http/event.rs b/rusk/src/lib/http/event.rs index 73e5048843..66e02e76d2 100644 --- a/rusk/src/lib/http/event.rs +++ b/rusk/src/lib/http/event.rs @@ -755,6 +755,14 @@ impl Display for RuesEventUri { } impl RuesEventUri { + pub fn inner(&self) -> (&str, Option<&String>, &str) { + ( + self.component.as_ref(), + self.entity.as_ref(), + self.topic.as_ref(), + ) + } + pub fn parse_from_path(path: &str) -> Option { if !path.starts_with(RUES_LOCATION_PREFIX) { return None; @@ -779,9 +787,9 @@ impl RuesEventUri { None => (segment, None), })?; - let component = component.to_string(); + let component = component.to_string().to_lowercase(); let entity = entity.map(ToString::to_string); - let topic = path_split.next()?.to_string(); + let topic = path_split.next()?.to_string().to_lowercase(); Some(Self { component, @@ -845,7 +853,44 @@ pub struct RuesEvent { pub data: DataType, } -impl RuesEvent { +/// A RUES Dispatch request event +#[derive(Debug)] +pub struct RuesDispatchEvent { + pub uri: RuesEventUri, + pub headers: serde_json::Map, + pub data: RequestData, +} + +impl RuesDispatchEvent { + pub fn x_headers(&self) -> serde_json::Map { + let mut h = self.headers.clone(); + h.retain(|k, _| k.to_lowercase().starts_with("x-")); + h + } + + pub fn header(&self, name: &str) -> Option<&serde_json::Value> { + self.headers + .iter() + .find_map(|(k, v)| k.eq_ignore_ascii_case(name).then_some(v)) + } + + pub fn check_rusk_version(&self) -> anyhow::Result<()> { + if let Some(v) = self.header(RUSK_VERSION_HEADER) { + let req = match v.as_str() { + Some(v) => VersionReq::from_str(v), + None => VersionReq::from_str(&v.to_string()), + }?; + + let current = Version::from_str(&crate::VERSION)?; + if !req.matches(¤t) { + return Err(anyhow::anyhow!( + "Mismatched rusk version: requested {req} - current {current}", + )); + } + } + Ok(()) + } + pub fn is_binary(&self) -> bool { self.headers .get(CONTENT_TYPE) @@ -885,17 +930,28 @@ impl RuesEvent { let bytes = body.collect().await?.to_bytes().to_vec(); let data = match content_type { CONTENT_TYPE_BINARY => bytes.into(), - _ => DataType::Text( - String::from_utf8(bytes) - .map_err(|e| anyhow::anyhow!("Invalid utf8"))?, - ), + _ => { + let text = String::from_utf8(bytes) + .map_err(|e| anyhow::anyhow!("Invalid utf8"))?; + if let Some(hex) = text.strip_prefix("0x") { + if let Ok(bytes) = hex::decode(hex) { + bytes.into() + } else { + text.into() + } + } else { + text.into() + } + } }; - let ret = RuesEvent { headers, data, uri }; + let ret = RuesDispatchEvent { headers, data, uri }; Ok(ret) } +} +impl RuesEvent { pub fn add_header, V: Into>( &mut self, key: K, diff --git a/rusk/src/lib/http/prover.rs b/rusk/src/lib/http/prover.rs index 936b624d74..a5791f0f24 100644 --- a/rusk/src/lib/http/prover.rs +++ b/rusk/src/lib/http/prover.rs @@ -16,6 +16,22 @@ impl HandleRequest for LocalProver { fn can_handle(&self, request: &MessageRequest) -> bool { matches!(request.event.to_route(), (_, "rusk", topic) | (_, "prover", topic) if topic.starts_with("prove_")) } + fn can_handle_rues(&self, request: &RuesDispatchEvent) -> bool { + matches!(request.uri.inner(), ("prover", _, "prove")) + } + async fn handle_rues( + &self, + request: &RuesDispatchEvent, + ) -> anyhow::Result { + let data = request.data.as_bytes(); + let response = match request.uri.inner() { + ("prover", _, "prove") => { + LocalProver::prove(data).map_err(|e| anyhow!(e))? + } + _ => anyhow::bail!("Unsupported"), + }; + Ok(ResponseData::new(response)) + } async fn handle( &self, diff --git a/rusk/src/lib/http/rusk.rs b/rusk/src/lib/http/rusk.rs index 665443816e..7c9a66edcc 100644 --- a/rusk/src/lib/http/rusk.rs +++ b/rusk/src/lib/http/rusk.rs @@ -14,6 +14,7 @@ use serde::Serialize; use std::sync::{mpsc, Arc}; use std::thread; use tokio::task; +use tungstenite::http::request; use execution_core::ContractId; @@ -29,6 +30,35 @@ impl HandleRequest for Rusk { (Target::Contract(_), ..) | (Target::Host(_), "rusk", _) ) } + fn can_handle_rues(&self, request: &RuesDispatchEvent) -> bool { + #[allow(clippy::match_like_matches_macro)] + match request.uri.inner() { + ("contracts", Some(_), _) => true, + ("transactions", _, "preverify") => true, + ("node", _, "provisioners") => true, + ("node", _, "crs") => true, + _ => false, + } + } + async fn handle_rues( + &self, + request: &RuesDispatchEvent, + ) -> anyhow::Result { + info!("received event {request:?}"); + match request.uri.inner() { + ("contracts", Some(contract_id), method) => { + let feeder = request.header(RUSK_FEEDER_HEADER).is_some(); + let data = request.data.as_bytes(); + self.handle_contract_query(contract_id, method, data, feeder) + } + ("transactions", _, "preverify") => { + self.handle_preverify(request.data.as_bytes()) + } + ("node", _, "provisioners") => self.get_provisioners(), + ("node", _, "crs") => self.get_crs(), + _ => Err(anyhow::anyhow!("Unsupported")), + } + } async fn handle( &self, @@ -37,7 +67,7 @@ impl HandleRequest for Rusk { match &request.event.to_route() { (Target::Contract(_), ..) => { let feeder = request.header(RUSK_FEEDER_HEADER).is_some(); - self.handle_contract_query(&request.event, feeder) + self.handle_contract_query_legacy(&request.event, feeder) } (Target::Host(_), "rusk", "preverify") => { self.handle_preverify(request.event_data()) @@ -52,41 +82,44 @@ impl HandleRequest for Rusk { } impl Rusk { - fn handle_contract_query( + fn handle_contract_query_legacy( &self, event: &Event, feeder: bool, ) -> anyhow::Result { let contract = event.target.inner(); + let topic = &event.topic; + let data = event.data.as_bytes(); + + self.handle_contract_query(contract, topic, data, feeder) + } + fn handle_contract_query( + &self, + contract: &str, + topic: &str, + data: &[u8], + feeder: bool, + ) -> anyhow::Result { let contract_bytes = hex::decode(contract)?; let contract_bytes = contract_bytes .try_into() .map_err(|_| anyhow::anyhow!("Invalid contract bytes"))?; - + let contract_id = ContractId::from_bytes(contract_bytes); + let fn_name = topic.to_string(); + let data = data.to_vec(); if feeder { let (sender, receiver) = mpsc::channel(); let rusk = self.clone(); - let topic = event.topic.clone(); - let arg = event.data.as_bytes().to_vec(); thread::spawn(move || { - rusk.feeder_query_raw( - ContractId::from_bytes(contract_bytes), - topic, - arg, - sender, - ); + rusk.feeder_query_raw(contract_id, fn_name, data, sender); }); Ok(ResponseData::new(receiver)) } else { let data = self - .query_raw( - ContractId::from_bytes(contract_bytes), - event.topic.clone(), - event.data.as_bytes(), - ) + .query_raw(contract_id, fn_name, data) .map_err(|e| anyhow::anyhow!("{e}"))?; Ok(ResponseData::new(data)) } From 813e29f28bc4dc9d442f4be69485f30106309849 Mon Sep 17 00:00:00 2001 From: Kieran Hall Date: Fri, 23 Aug 2024 17:41:17 +0200 Subject: [PATCH 016/102] web-wallet: Add allocation page Resolves #2106 --- web-wallet/CHANGELOG.md | 2 + web-wallet/README.md | 2 +- .../src/__mocks__/mockedOperationsStore.js | 5 + .../(app) => }/__mocks__/mockedWalletStore.js | 4 +- .../lib/components/Allocate/Allocate.svelte | 413 ++++++++++++++++++ .../src/lib/components/GasFee/GasFee.svelte | 10 +- .../lib/components/__tests__/GasFee.spec.js | 7 +- .../__snapshots__/GasFee.spec.js.snap | 24 +- .../__snapshots__/GasSettings.spec.js.snap | 48 +- .../__tests__/__snapshots__/Send.spec.js.snap | 48 +- .../__snapshots__/Stake.spec.js.snap | 48 +- web-wallet/src/lib/components/index.js | 1 + .../AllocateContract/AllocateContract.svelte | 31 ++ .../TransferContract/TransferContract.svelte | 9 +- web-wallet/src/lib/containers/index.js | 3 +- .../contracts/__tests__/executeSend.spec.js | 58 +++ .../__tests__/updateOperation.spec.js | 11 + .../src/lib/contracts/contract-descriptors.js | 15 + web-wallet/src/lib/contracts/executeSend.js | 10 + web-wallet/src/lib/contracts/index.js | 2 + .../src/lib/contracts/updateOperation.js | 11 + .../lib/stores/__tests__/walletStore.spec.js | 2 +- web-wallet/src/lib/stores/operationsStore.js | 9 +- web-wallet/src/lib/stores/stores.d.ts | 4 + .../src/routes/(app)/dashboard/+page.svelte | 34 +- .../__tests__/__snapshots__/page.spec.js.snap | 60 +++ .../(app)/dashboard/__tests__/layout.spec.js | 2 +- .../(app)/dashboard/__tests__/page.spec.js | 2 +- .../(app)/dashboard/allocate/+page.svelte | 21 + .../__tests__/__snapshots__/page.spec.js.snap | 291 ++++++++++++ .../dashboard/allocate/__tests__/page.spec.js | 44 ++ .../(app)/dashboard/staking/+page.svelte | 38 +- .../__tests__/__snapshots__/page.spec.js.snap | 93 ++++ .../dashboard/staking/__tests__/page.spec.js | 17 + .../(app)/dashboard/transfer/+page.svelte | 35 +- .../__tests__/__snapshots__/page.spec.js.snap | 161 +++++++ .../dashboard/transfer/__tests__/page.spec.js | 15 + .../(app)/settings/__tests__/page.spec.js | 2 +- .../style/app-components/horizontal-rules.css | 15 + web-wallet/src/style/dusk/language.css | 18 +- web-wallet/src/style/main.css | 4 + web-wallet/vite.config.js | 4 +- 42 files changed, 1512 insertions(+), 121 deletions(-) create mode 100644 web-wallet/src/__mocks__/mockedOperationsStore.js rename web-wallet/src/{routes/(app) => }/__mocks__/mockedWalletStore.js (79%) create mode 100644 web-wallet/src/lib/components/Allocate/Allocate.svelte create mode 100644 web-wallet/src/lib/containers/AllocateContract/AllocateContract.svelte create mode 100644 web-wallet/src/lib/contracts/__tests__/executeSend.spec.js create mode 100644 web-wallet/src/lib/contracts/__tests__/updateOperation.spec.js create mode 100644 web-wallet/src/lib/contracts/executeSend.js create mode 100644 web-wallet/src/lib/contracts/updateOperation.js create mode 100644 web-wallet/src/routes/(app)/dashboard/allocate/+page.svelte create mode 100644 web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap create mode 100644 web-wallet/src/routes/(app)/dashboard/allocate/__tests__/page.spec.js create mode 100644 web-wallet/src/routes/(app)/dashboard/staking/__tests__/__snapshots__/page.spec.js.snap create mode 100644 web-wallet/src/routes/(app)/dashboard/staking/__tests__/page.spec.js create mode 100644 web-wallet/src/routes/(app)/dashboard/transfer/__tests__/__snapshots__/page.spec.js.snap create mode 100644 web-wallet/src/routes/(app)/dashboard/transfer/__tests__/page.spec.js diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index 6481471c8a..d706b92ea4 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added gas settings validation on Unstake / Widthdraw Rewards flows [#2000] +- Added allocation (shield/unshield) page and UI [#2196] ### Changed @@ -18,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update Transactions list design [#1922] - Update Buttons to match the design system [#1606] - Update `Stepper` component to new design [#2071] +- Update dashboard to use routes instead of `Tabs` for navigation pattern [#2075] ### Fixed diff --git a/web-wallet/README.md b/web-wallet/README.md index ec583725a2..fd86c74669 100644 --- a/web-wallet/README.md +++ b/web-wallet/README.md @@ -33,6 +33,7 @@ N.B. the current `0.1.2` version of the library has no option to pick the networ ``` # can be empty string, must start with a slash otherwise, must not end with a slash VITE_BASE_PATH="" +VITE_CONTRACT_ALLOCATE_DISABLED=true VITE_CONTRACT_STAKE_DISABLED=false VITE_CONTRACT_TRANSFER_DISABLED=false VITE_CURRENT_NODE=${VITE_LOCAL_NODE} @@ -51,7 +52,6 @@ VITE_TESTNET_NODE="https://nodes.dusk.network/" VITE_TESTNET_PROVER_NODE="https://provers.dusk.network/" VITE_TRANSFER_CONTRACT="0100000000000000000000000000000000000000000000000000000000000000" VITE_GET_QUOTE_API_ENDPOINT="https://api.dusk.network/v1/quote" -VITE_MOONLIGHT_DISABLED=true ``` To run a local node different steps are needed, so please read the [related section](#running-a-local-rusk-node). diff --git a/web-wallet/src/__mocks__/mockedOperationsStore.js b/web-wallet/src/__mocks__/mockedOperationsStore.js new file mode 100644 index 0000000000..a28d7e379c --- /dev/null +++ b/web-wallet/src/__mocks__/mockedOperationsStore.js @@ -0,0 +1,5 @@ +import { mockReadableStore } from "$lib/dusk/test-helpers"; + +const content = { currentOperation: "something" }; + +export default mockReadableStore(content); diff --git a/web-wallet/src/routes/(app)/__mocks__/mockedWalletStore.js b/web-wallet/src/__mocks__/mockedWalletStore.js similarity index 79% rename from web-wallet/src/routes/(app)/__mocks__/mockedWalletStore.js rename to web-wallet/src/__mocks__/mockedWalletStore.js index 3d2c378327..bd2c41e7bd 100644 --- a/web-wallet/src/routes/(app)/__mocks__/mockedWalletStore.js +++ b/web-wallet/src/__mocks__/mockedWalletStore.js @@ -14,4 +14,6 @@ const content = { isSyncing: false, }; -export default mockReadableStore(content); +const mockedWalletStore = mockReadableStore(content); + +export default mockedWalletStore; diff --git a/web-wallet/src/lib/components/Allocate/Allocate.svelte b/web-wallet/src/lib/components/Allocate/Allocate.svelte new file mode 100644 index 0000000000..e7714c5d07 --- /dev/null +++ b/web-wallet/src/lib/components/Allocate/Allocate.svelte @@ -0,0 +1,413 @@ + + + + +
+ + +
+

+ Edit the value to change the allocation of your Dusk between your + shielded or public account. +

+ +
+
Shielded
+ +
+ + {middleEllipsis( + shieldedAddress, + calculateAdaptiveCharCount(screenWidth, 320, 640, 5, 20) + )} +
+ +
+ { + unshieldedAmount = +( + shieldedBalance + + unshieldedBalance - + shieldedAmount + ).toFixed(9); + }} + /> + +
+ +
+ +
Unshielded
+ +
+ + {middleEllipsis( + unshieldedAddress, + calculateAdaptiveCharCount(screenWidth, 320, 640, 5, 20) + )} +
+ +
+ { + shieldedAmount = +( + unshieldedBalance + + shieldedBalance - + unshieldedAmount + ).toFixed(9); + }} + /> + +
+
+ + { + isGasValid = areValidGasSettings( + event.detail.price, + event.detail.limit + ); + + if (isGasValid) { + gasPrice = event.detail.price; + gasLimit = event.detail.limit; + } + }} + /> +
+
+ +
+ +
+
+ + Amount: +
+
+ + {isFromUnshielded + ? `${formatter(luxToDusk(duskToLux(unshieldedBalance) - duskToLux(unshieldedAmount)))} DUSK` + : `${formatter(luxToDusk(duskToLux(shieldedBalance) - duskToLux(shieldedAmount)))} DUSK`} + + +
+
+
+
+ + From +
+
+ + {isFromUnshielded ? unshieldedAddress : shieldedAddress} + +
+
+
+
+ + To +
+
+ + {isFromShielded ? unshieldedAddress : shieldedAddress} + +
+
+ +
+
+ + + + {#if hash} + + {/if} + + + +
+
+ + diff --git a/web-wallet/src/lib/components/GasFee/GasFee.svelte b/web-wallet/src/lib/components/GasFee/GasFee.svelte index 205cb44020..020b3f8fdf 100644 --- a/web-wallet/src/lib/components/GasFee/GasFee.svelte +++ b/web-wallet/src/lib/components/GasFee/GasFee.svelte @@ -7,9 +7,9 @@
- Max Gas Fee: + Fee:
- {fee} +
max {fee}
span:first-of-type { + font-weight: initial; + } } } diff --git a/web-wallet/src/lib/components/__tests__/GasFee.spec.js b/web-wallet/src/lib/components/__tests__/GasFee.spec.js index 311396e2dd..34b993001c 100644 --- a/web-wallet/src/lib/components/__tests__/GasFee.spec.js +++ b/web-wallet/src/lib/components/__tests__/GasFee.spec.js @@ -21,9 +21,10 @@ describe("GasFee", () => { }; const { container } = render(GasFee, baseProps); - expect(container.querySelector(".gas-fee__amount-value")?.innerHTML).toBe( - fee - ); + expect( + container.querySelector(".gas-fee__amount-value span:nth-child(2)") + ?.innerHTML + ).toBe(fee); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap b/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap index 3e98c31e82..6f8d22f8ed 100644 --- a/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap +++ b/web-wallet/src/lib/components/__tests__/__snapshots__/GasFee.spec.js.snap @@ -2,20 +2,30 @@ exports[`GasFee > renders the GasFee component 1`] = `
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
renders the GasSettings component closed 1`] = ` class="gas-settings__edit svelte-1daay07" >
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
renders the GasSettings component opened 1`] = ` class="gas-settings__edit svelte-1daay07" >
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
Amount step > should render the Send component Amount step 1`] = class="gas-settings__edit svelte-1daay07" >
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
Review step > should render the Send component Review step 1`] =
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
should render the Stake component 1`] = ` class="gas-settings__edit svelte-1daay07" >
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
should render the review step of the Stake component 1`] = `
- Max Gas Fee: + Fee:
- - 0.020000000 - + + max + + + + 0.020000000 + +
+ + + + diff --git a/web-wallet/src/lib/containers/TransferContract/TransferContract.svelte b/web-wallet/src/lib/containers/TransferContract/TransferContract.svelte index 2458419151..496fb0001a 100644 --- a/web-wallet/src/lib/containers/TransferContract/TransferContract.svelte +++ b/web-wallet/src/lib/containers/TransferContract/TransferContract.svelte @@ -11,9 +11,8 @@ setKey, when, } from "lamb"; - + import { executeSend } from "$lib/contracts"; import { createCurrencyFormatter } from "$lib/dusk/currency"; - import { getLastTransactionHash } from "$lib/transactions"; import { gasStore, operationsStore, @@ -30,12 +29,6 @@ /** @type {ContractDescriptor} */ export let descriptor; - /** @type {(to: string, amount: number, gasPrice:number, gasLimit:number) => Promise} */ - const executeSend = (to, amount, gasPrice, gasLimit) => - walletStore - .transfer(to, amount, gasPrice, gasLimit) - .then(getLastTransactionHash); - const collectSettings = collect([ pick(["gasLimit", "gasPrice"]), getKey("language"), diff --git a/web-wallet/src/lib/containers/index.js b/web-wallet/src/lib/containers/index.js index 2387cdbe37..0c1394e722 100644 --- a/web-wallet/src/lib/containers/index.js +++ b/web-wallet/src/lib/containers/index.js @@ -1,3 +1,4 @@ +export { default as AllocateContract } from "./AllocateContract/AllocateContract.svelte"; +export { default as MigrateContract } from "./MigrateContract/MigrateContract.svelte"; export { default as StakeContract } from "./StakeContract/StakeContract.svelte"; export { default as TransferContract } from "./TransferContract/TransferContract.svelte"; -export { default as MigrateContract } from "./MigrateContract/MigrateContract.svelte"; diff --git a/web-wallet/src/lib/contracts/__tests__/executeSend.spec.js b/web-wallet/src/lib/contracts/__tests__/executeSend.spec.js new file mode 100644 index 0000000000..f6588c1f6b --- /dev/null +++ b/web-wallet/src/lib/contracts/__tests__/executeSend.spec.js @@ -0,0 +1,58 @@ +import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; +import { walletStore } from "$lib/stores"; +import { getLastTransactionHash } from "$lib/transactions"; +import { executeSend } from ".."; + +vi.mock("$lib/stores", async (importOriginal) => { + /** @type {typeof import("$lib/stores")} */ + const original = await importOriginal(); + + return { + ...original, + walletStore: { + ...original.walletStore, + transfer: vi.fn().mockResolvedValue(undefined), + }, + }; +}); + +vi.mock("$lib/transactions", async (importOriginal) => { + /** @type {typeof import("$lib/transactions")} */ + const original = await importOriginal(); + + return { + ...original, + getLastTransactionHash: vi.fn(() => ""), + }; +}); + +afterEach(() => { + vi.mocked(walletStore.transfer).mockClear(); + vi.mocked(getLastTransactionHash).mockClear(); +}); + +afterAll(() => { + vi.doUnmock("$lib/stores/walletStore"); + vi.doUnmock("$lib/transactions"); +}); + +describe("executeSend", () => { + it("should call the walletStore transfer method", async () => { + const args = ["abc", 1000, 1, 2]; + // @ts-ignore + await executeSend(...args); + + expect(walletStore.transfer).toHaveBeenCalledTimes(1); + expect(walletStore.transfer).toHaveBeenCalledWith(...args); + expect(getLastTransactionHash).toHaveBeenCalledTimes(1); + }); + + it("should not call the getLastTransactionHash function when an error is emitted from the transfer function", async () => { + const err = new Error("some error"); + + vi.mocked(walletStore.transfer).mockRejectedValueOnce(err); + + await expect(executeSend("abc", 1000, 1, 2)).rejects.toBe(err); + expect(getLastTransactionHash).not.toHaveBeenCalled(); + }); +}); diff --git a/web-wallet/src/lib/contracts/__tests__/updateOperation.spec.js b/web-wallet/src/lib/contracts/__tests__/updateOperation.spec.js new file mode 100644 index 0000000000..cf461c3eb3 --- /dev/null +++ b/web-wallet/src/lib/contracts/__tests__/updateOperation.spec.js @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import { updateOperation } from ".."; +import { get } from "svelte/store"; +import { operationsStore } from "$lib/stores"; + +describe("updateOperation", () => { + it("should set the current operation in the operationStore", () => { + updateOperation("some operation"); + expect(get(operationsStore).currentOperation).toBe("some operation"); + }); +}); diff --git a/web-wallet/src/lib/contracts/contract-descriptors.js b/web-wallet/src/lib/contracts/contract-descriptors.js index 50afc04304..49d4f75f96 100644 --- a/web-wallet/src/lib/contracts/contract-descriptors.js +++ b/web-wallet/src/lib/contracts/contract-descriptors.js @@ -54,4 +54,19 @@ export default [ }, ], }, + { + disabled: + import.meta.env.VITE_CONTRACT_ALLOCATE_DISABLED && + import.meta.env.VITE_CONTRACT_ALLOCATE_DISABLED === "true", + id: "allocate", + label: "Shield / Unshield", + operations: [ + { + disabled: false, + id: "send", + label: "send", + primary: true, + }, + ], + }, ]; diff --git a/web-wallet/src/lib/contracts/executeSend.js b/web-wallet/src/lib/contracts/executeSend.js new file mode 100644 index 0000000000..6bc3e4cc63 --- /dev/null +++ b/web-wallet/src/lib/contracts/executeSend.js @@ -0,0 +1,10 @@ +import { getLastTransactionHash } from "$lib/transactions"; +import { walletStore } from "$lib/stores"; + +/** @type {(to: string, amount: number, gasPrice:number, gasLimit:number) => Promise} */ +const executeSend = (to, amount, gasPrice, gasLimit) => + walletStore + .transfer(to, amount, gasPrice, gasLimit) + .then(getLastTransactionHash); + +export default executeSend; diff --git a/web-wallet/src/lib/contracts/index.js b/web-wallet/src/lib/contracts/index.js index e71743ccff..bb1d1ea387 100644 --- a/web-wallet/src/lib/contracts/index.js +++ b/web-wallet/src/lib/contracts/index.js @@ -1,3 +1,5 @@ export { default as contractDescriptors } from "./contract-descriptors"; export { default as deductLuxFeeFrom } from "./deductLuxFeeFrom"; +export { default as executeSend } from "./executeSend"; export { default as areValidGasSettings } from "./areValidGasSettings"; +export { default as updateOperation } from "./updateOperation"; diff --git a/web-wallet/src/lib/contracts/updateOperation.js b/web-wallet/src/lib/contracts/updateOperation.js new file mode 100644 index 0000000000..a3ebdfe992 --- /dev/null +++ b/web-wallet/src/lib/contracts/updateOperation.js @@ -0,0 +1,11 @@ +import { operationsStore } from "$lib/stores"; + +/** @param {string} id */ +function updateOperation(id) { + operationsStore.update((store) => ({ + ...store, + currentOperation: id, + })); +} + +export default updateOperation; diff --git a/web-wallet/src/lib/stores/__tests__/walletStore.spec.js b/web-wallet/src/lib/stores/__tests__/walletStore.spec.js index 2381c601ad..467f28910c 100644 --- a/web-wallet/src/lib/stores/__tests__/walletStore.spec.js +++ b/web-wallet/src/lib/stores/__tests__/walletStore.spec.js @@ -500,7 +500,7 @@ describe("walletStore", async () => { walletStore.reset(); }); - it("should return a rejected promise with the operation error if an operation fails and try a sync afterwards nonetheless", async () => { + it("should return a rejected promise with the operation error if an operation fails and try a sync afterwards", async () => { await walletStore.init(wallet); await vi.advanceTimersToNextTimerAsync(); diff --git a/web-wallet/src/lib/stores/operationsStore.js b/web-wallet/src/lib/stores/operationsStore.js index b0fa02b8ce..b6c5615f5b 100644 --- a/web-wallet/src/lib/stores/operationsStore.js +++ b/web-wallet/src/lib/stores/operationsStore.js @@ -1,6 +1,9 @@ import { writable } from "svelte/store"; -/** @type {import("svelte/store").Writable<{ currentOperation: string }>} */ -const count = writable({ currentOperation: "" }); +/** @type OperationsStoreContent */ +const initialState = { currentOperation: "" }; -export default count; +/** @type OperationsStore */ +const store = writable(initialState); + +export default store; diff --git a/web-wallet/src/lib/stores/stores.d.ts b/web-wallet/src/lib/stores/stores.d.ts index 6a9d10678b..4f9abe431b 100644 --- a/web-wallet/src/lib/stores/stores.d.ts +++ b/web-wallet/src/lib/stores/stores.d.ts @@ -31,6 +31,10 @@ type TransactionsStoreContent = { transactions: Transaction[] }; type TransactionsStore = Readable; +type OperationsStoreContent = { currentOperation: string }; + +type OperationsStore = Writable; + type WalletStoreContent = { balance: { maximum: number; diff --git a/web-wallet/src/routes/(app)/dashboard/+page.svelte b/web-wallet/src/routes/(app)/dashboard/+page.svelte index 440bca589a..e7dc871644 100644 --- a/web-wallet/src/routes/(app)/dashboard/+page.svelte +++ b/web-wallet/src/routes/(app)/dashboard/+page.svelte @@ -2,7 +2,12 @@ + +{#if !!import.meta.env.VITE_CONTRACT_ALLOCATE_DISABLED} + + updateOperation(detail)} + /> + +{/if} diff --git a/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap new file mode 100644 index 0000000000..12335431b5 --- /dev/null +++ b/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/__snapshots__/page.spec.js.snap @@ -0,0 +1,291 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Allocate > should render the allocation page 1`] = ` +
+
+
+
+ + + + + +

+ Shield / Unshield +

+
+
+
+ +
+
+
+ + +
+

+ Edit the value to change the allocation of your Dusk between your + shielded or public account. +

+ +
+
+ Shielded +
+ +
+ + + + + + +
+ +
+ + + + + + + +
+ +
+ +
+ Unshielded +
+ +
+ + + + + + +
+ +
+ + + + + + + +
+
+ +
+
+
+ + Fee: + + +
+
+ + max + + + + 0.020000000 + +
+ + + + + +
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + Back + + + + + + + +
+ + + + + + +
+
+
+ +
+`; diff --git a/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/page.spec.js b/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/page.spec.js new file mode 100644 index 0000000000..b78431b634 --- /dev/null +++ b/web-wallet/src/routes/(app)/dashboard/allocate/__tests__/page.spec.js @@ -0,0 +1,44 @@ +import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; +import { get } from "svelte/store"; +import { cleanup, render } from "@testing-library/svelte"; +import { operationsStore } from "$lib/stores"; +import Allocation from "../+page.svelte"; + +vi.mock("$lib/stores", async (importOriginal) => { + /** @type {typeof import("$lib/stores")} */ + const original = await importOriginal(); + + return { + ...original, + operationsStore: { + ...original.operationsStore, + }, + }; +}); + +vi.useFakeTimers(); + +describe("Allocate", () => { + afterEach(cleanup); + afterAll(() => { + vi.doUnmock("$lib/stores"); + }); + + it("should render the allocation page", async () => { + const { container } = render(Allocation); + + await vi.advanceTimersToNextTimerAsync(); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should set the current operation to an empty string when destroyed", async () => { + const { component } = render(Allocation); + + component.$destroy(); + + expect(get(operationsStore)).toStrictEqual({ + currentOperation: "", + }); + }); +}); diff --git a/web-wallet/src/routes/(app)/dashboard/staking/+page.svelte b/web-wallet/src/routes/(app)/dashboard/staking/+page.svelte index fec550b8b0..49d20d3081 100644 --- a/web-wallet/src/routes/(app)/dashboard/staking/+page.svelte +++ b/web-wallet/src/routes/(app)/dashboard/staking/+page.svelte @@ -5,16 +5,8 @@ import { mdiDatabaseOutline } from "@mdi/js"; import { StakeContract } from "$lib/containers"; import { IconHeadingCard } from "$lib/containers/Cards"; - import { contractDescriptors } from "$lib/contracts"; - import { operationsStore, settingsStore } from "$lib/stores"; - - /** @param {string} id */ - function updateOperation(id) { - operationsStore.update((store) => ({ - ...store, - currentOperation: id, - })); - } + import { contractDescriptors, updateOperation } from "$lib/contracts"; + import { settingsStore } from "$lib/stores"; /** * @param {keyof SettingsStoreContent} property @@ -32,15 +24,17 @@ }); - - updateOperation(detail)} - on:suppressStakingNotice={() => updateSetting("hideStakingNotice", true)} - /> - +{#if !!import.meta.env.VITE_CONTRACT_STAKE_DISABLED} + + updateOperation(detail)} + on:suppressStakingNotice={() => updateSetting("hideStakingNotice", true)} + /> + +{/if} diff --git a/web-wallet/src/routes/(app)/dashboard/staking/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(app)/dashboard/staking/__tests__/__snapshots__/page.spec.js.snap new file mode 100644 index 0000000000..e3b506e309 --- /dev/null +++ b/web-wallet/src/routes/(app)/dashboard/staking/__tests__/__snapshots__/page.spec.js.snap @@ -0,0 +1,93 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Staking > should render the staking page 1`] = ` +
+
+
+
+ + + + + +

+ Staking +

+
+
+
+ +
+
+
+ + Failed to retrieve stake info + + +
+          			
+          No wallet instance to sync
+          
+		
+        
+
+ + + + + + + + + + + Back + + + + +
+ +
+ +
+`; diff --git a/web-wallet/src/routes/(app)/dashboard/staking/__tests__/page.spec.js b/web-wallet/src/routes/(app)/dashboard/staking/__tests__/page.spec.js new file mode 100644 index 0000000000..1d09dd3bb8 --- /dev/null +++ b/web-wallet/src/routes/(app)/dashboard/staking/__tests__/page.spec.js @@ -0,0 +1,17 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, render } from "@testing-library/svelte"; +import Staking from "../+page.svelte"; + +vi.useFakeTimers(); + +describe("Staking", () => { + afterEach(cleanup); + + it("should render the staking page", async () => { + const { container } = render(Staking); + + await vi.advanceTimersToNextTimerAsync(); // Wait until the stakeInfo promise has resolved + + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/web-wallet/src/routes/(app)/dashboard/transfer/+page.svelte b/web-wallet/src/routes/(app)/dashboard/transfer/+page.svelte index 72a175d315..294176a919 100644 --- a/web-wallet/src/routes/(app)/dashboard/transfer/+page.svelte +++ b/web-wallet/src/routes/(app)/dashboard/transfer/+page.svelte @@ -4,31 +4,24 @@ import { onDestroy } from "svelte"; import { TransferContract } from "$lib/containers"; import { IconHeadingCard } from "$lib/containers/Cards"; - import { contractDescriptors } from "$lib/contracts"; - import { operationsStore } from "$lib/stores"; + import { contractDescriptors, updateOperation } from "$lib/contracts"; import { mdiSwapVertical } from "@mdi/js"; - /** @param {string} id */ - function updateOperation(id) { - operationsStore.update((store) => ({ - ...store, - currentOperation: id, - })); - } - onDestroy(() => { updateOperation(""); }); - - updateOperation(detail)} - /> - +{#if !!import.meta.env.VITE_CONTRACT_TRANSFER_DISABLED} + + updateOperation(detail)} + /> + +{/if} diff --git a/web-wallet/src/routes/(app)/dashboard/transfer/__tests__/__snapshots__/page.spec.js.snap b/web-wallet/src/routes/(app)/dashboard/transfer/__tests__/__snapshots__/page.spec.js.snap new file mode 100644 index 0000000000..8e7202d565 --- /dev/null +++ b/web-wallet/src/routes/(app)/dashboard/transfer/__tests__/__snapshots__/page.spec.js.snap @@ -0,0 +1,161 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Transfer > should render the transfer page 1`] = ` +
+
+
+
+ + + + + +

+ Transfer +

+
+
+
+ +
+
+
+ Spendable + +
+
+ + 0.000000000 + + + + + + + +
+
+ + +
  • + +
  • +
  • + +
  • + +
  • + + + + + + + + Back + + + + +
  • +
    + +
    + +
    +`; diff --git a/web-wallet/src/routes/(app)/dashboard/transfer/__tests__/page.spec.js b/web-wallet/src/routes/(app)/dashboard/transfer/__tests__/page.spec.js new file mode 100644 index 0000000000..ae84243208 --- /dev/null +++ b/web-wallet/src/routes/(app)/dashboard/transfer/__tests__/page.spec.js @@ -0,0 +1,15 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, render } from "@testing-library/svelte"; +import Transfer from "../+page.svelte"; + +vi.useFakeTimers(); + +describe("Transfer", () => { + afterEach(cleanup); + + it("should render the transfer page", async () => { + const { container } = render(Transfer); + + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js b/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js index dacf27d0bd..d6c16fbb16 100644 --- a/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js +++ b/web-wallet/src/routes/(app)/settings/__tests__/page.spec.js @@ -9,7 +9,7 @@ import { } from "vitest"; import { act, cleanup, fireEvent, render } from "@testing-library/svelte"; -import mockedWalletStore from "../../__mocks__/mockedWalletStore"; +import mockedWalletStore from "../../../../__mocks__/mockedWalletStore"; import * as navigation from "$lib/navigation"; import { settingsStore, walletStore } from "$lib/stores"; import loginInfoStorage from "$lib/services/loginInfoStorage"; diff --git a/web-wallet/src/style/app-components/horizontal-rules.css b/web-wallet/src/style/app-components/horizontal-rules.css index 4d516c47f2..8c803e6383 100644 --- a/web-wallet/src/style/app-components/horizontal-rules.css +++ b/web-wallet/src/style/app-components/horizontal-rules.css @@ -1,5 +1,20 @@ hr { + text-align: center; + overflow: visible; border: none; border-top: 1px solid var(--divider-color-primary); width: 100%; + height: 1px; + padding: 0; +} + +hr.glyph:after { + display: inline-block; + position: relative; + top: -1.1em; + font-size: 1em; + border: solid thin var(--divider-border-color); + border-radius: 2em; + padding: 0.5em 1.25em; + background-color: var(--divider-background-color); } diff --git a/web-wallet/src/style/dusk/language.css b/web-wallet/src/style/dusk/language.css index b26b9e0452..74aff3f4d6 100644 --- a/web-wallet/src/style/dusk/language.css +++ b/web-wallet/src/style/dusk/language.css @@ -109,9 +109,15 @@ --checkbox-control-border-color: var(--smokey-black); --checkbox-control-checked-bg-color: var(--smokey-black); + /* Fieldset */ + --fieldset-background-color: var(--light-grey); + --fieldset-border-radius: 1.5em; + /* Dividers */ - --divider-color-primary: var(--taupe-grey); + --divider-color-primary: var(--smokey-black); + --divider-background-color: var(--light-gray); + --divider-border-color: var(--smokey-black); /* Home animation */ @@ -152,6 +158,16 @@ --checkbox-control-border-color: var(--light-grey); --checkbox-control-checked-bg-color: var(--light-grey); + /* Fieldset */ + + --fieldset-background-color: var(--smokey-black); + + /* Dividers */ + + --divider-color-primary: var(--magnolia); + --divider-background-color: var(--smokey-black); + --divider-border-color: var(--magnolia); + /* Home animation */ --home-grid-color: var(--smokey-black); diff --git a/web-wallet/src/style/main.css b/web-wallet/src/style/main.css index 3e99a04f9c..345fc9def8 100644 --- a/web-wallet/src/style/main.css +++ b/web-wallet/src/style/main.css @@ -92,6 +92,10 @@ svg { fill: currentColor; } +fieldset { + border: none; +} + #outer-container { background-color: var(--background-color); width: 60%; diff --git a/web-wallet/vite.config.js b/web-wallet/vite.config.js index 64861c573a..7029f9f94c 100644 --- a/web-wallet/vite.config.js +++ b/web-wallet/vite.config.js @@ -48,6 +48,7 @@ export default defineConfig(({ mode }) => { TESTNET_NODE: env.VITE_TESTNET_NODE, TESTNET_PROVER_NODE: env.VITE_TESTNET_PROVER_NODE, TRANSFER_CONTRACT: env.VITE_TRANSFER_CONTRACT, + VITE_CONTRACT_ALLOCATE_DISABLED: env.VITE_CONTRACT_ALLOCATE_DISABLED, VITE_CONTRACT_STAKE_DISABLED: env.VITE_CONTRACT_STAKE_DISABLED, VITE_CONTRACT_TRANSFER_DISABLED: env.VITE_CONTRACT_TRANSFER_DISABLED, VITE_GAS_LIMIT_DEFAULT: env.VITE_GAS_LIMIT_DEFAULT, @@ -58,8 +59,6 @@ export default defineConfig(({ mode }) => { VITE_GAS_PRICE_UPPER: env.VITE_GAS_PRICE_UPPER, VITE_GET_QUOTE_API_ENDPOINT: env.VITE_GET_QUOTE_API_ENDPOINT, VITE_MINIMUM_ALLOWED_STAKE: env.VITE_MINIMUM_ALLOWED_STAKE, - VITE_STAKING_ENABLED: env.VITE_STAKING_ENABLED, - VITE_TRANSFER_ENABLED: env.VITE_TRANSFER_ENABLED, }, }, plugins: @@ -98,6 +97,7 @@ export default defineConfig(({ mode }) => { "0200000000000000000000000000000000000000000000000000000000000000", TRANSFER_CONTRACT: "0100000000000000000000000000000000000000000000000000000000000000", + VITE_CONTRACT_ALLOCATE_DISABLED: "false", VITE_CONTRACT_STAKE_DISABLED: "false", VITE_CONTRACT_TRANSFER_DISABLED: "false", VITE_GAS_LIMIT_DEFAULT: "20000000", From de1292735b808dd155f1a213591d6130df8b48cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 14:05:28 +0100 Subject: [PATCH 017/102] execution-core: add chain ID to TXs and calls Adding the chain ID to both transaction models and to relevant calls allows contracts downstream to check if they are being executed on the intended chain. The cost is minimal - 1 byte in the payload - and the benefits are cross-chain replay attack protection. --- execution-core/src/stake.rs | 20 +++++++++++++++++--- execution-core/src/transfer.rs | 5 ++++- execution-core/src/transfer/moonlight.rs | 17 +++++++++++++++-- execution-core/src/transfer/phoenix.rs | 19 +++++++++++++++++-- execution-core/tests/serialization.rs | 6 +++++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs index 2fcabc2694..d51a93e290 100644 --- a/execution-core/src/stake.rs +++ b/execution-core/src/stake.rs @@ -43,6 +43,7 @@ pub const fn next_epoch(block_height: u64) -> u64 { #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct Stake { + chain_id: u8, account: BlsPublicKey, value: u64, nonce: u64, @@ -50,12 +51,18 @@ pub struct Stake { } impl Stake { - const MESSAGE_SIZE: usize = BlsPublicKey::SIZE + u64::SIZE + u64::SIZE; + const MESSAGE_SIZE: usize = 1 + BlsPublicKey::SIZE + u64::SIZE + u64::SIZE; /// Create a new stake. #[must_use] - pub fn new(sk: &BlsSecretKey, value: u64, nonce: u64) -> Self { + pub fn new( + sk: &BlsSecretKey, + value: u64, + nonce: u64, + chain_id: u8, + ) -> Self { let mut stake = Stake { + chain_id, account: BlsPublicKey::from(sk), value, nonce, @@ -91,6 +98,12 @@ impl Stake { self.nonce } + /// Returns the chain ID of the stake. + #[must_use] + pub fn chain_id(&self) -> u8 { + self.chain_id + } + /// Signature of the stake. #[must_use] pub fn signature(&self) -> &BlsSignature { @@ -102,7 +115,8 @@ impl Stake { pub fn signature_message(&self) -> [u8; Self::MESSAGE_SIZE] { let mut bytes = [0u8; Self::MESSAGE_SIZE]; - let mut offset = 0; + bytes[0] = self.chain_id; + let mut offset = 1; bytes[offset..offset + BlsPublicKey::SIZE] .copy_from_slice(&self.account.to_bytes()); diff --git a/execution-core/src/transfer.rs b/execution-core/src/transfer.rs index 910457e4d8..cf0abd05bc 100644 --- a/execution-core/src/transfer.rs +++ b/execution-core/src/transfer.rs @@ -73,6 +73,7 @@ impl Transaction { deposit: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, exec: Option>, ) -> Result { Ok(Self::Phoenix(PhoenixTransaction::new::( @@ -87,6 +88,7 @@ impl Transaction { deposit, gas_limit, gas_price, + chain_id, exec, )?)) } @@ -102,11 +104,12 @@ impl Transaction { gas_limit: u64, gas_price: u64, nonce: u64, + chain_id: u8, exec: Option>, ) -> Self { Self::Moonlight(MoonlightTransaction::new( from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, - exec, + chain_id, exec, )) } diff --git a/execution-core/src/transfer/moonlight.rs b/execution-core/src/transfer/moonlight.rs index a480d64b04..cc8b7e424a 100644 --- a/execution-core/src/transfer/moonlight.rs +++ b/execution-core/src/transfer/moonlight.rs @@ -54,9 +54,11 @@ impl Transaction { gas_limit: u64, gas_price: u64, nonce: u64, + chain_id: u8, exec: Option>, ) -> Self { let payload = Payload { + chain_id, from_account: AccountPublicKey::from(from_sk), to_account, value, @@ -121,6 +123,12 @@ impl Transaction { self.payload.nonce } + /// Returns the chain ID of the transaction. + #[must_use] + pub fn chain_id(&self) -> u8 { + self.payload.chain_id + } + /// Return the contract call data, if there is any. #[must_use] pub fn call(&self) -> Option<&ContractCall> { @@ -239,6 +247,8 @@ impl Transaction { #[derive(Debug, Clone, PartialEq, Eq, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] struct Payload { + /// ID of the chain for this transaction to execute on. + pub chain_id: u8, /// Key of the sender of this transaction. pub from_account: AccountPublicKey, /// Key of the receiver of the funds. @@ -265,7 +275,7 @@ impl Payload { /// Serialize the payload into a byte buffer. #[must_use] pub fn to_var_bytes(&self) -> Vec { - let mut bytes = Vec::new(); + let mut bytes = Vec::from([self.chain_id]); bytes.extend(self.from_account.to_bytes()); @@ -309,6 +319,8 @@ impl Payload { pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; + let chain_id = u8::from_reader(&mut buf)?; + let from_account = AccountPublicKey::from_reader(&mut buf)?; // deserialize recipient @@ -337,6 +349,7 @@ impl Payload { }; Ok(Self { + chain_id, from_account, to_account, value, @@ -354,7 +367,7 @@ impl Payload { /// for hashing and *cannot* be used to deserialize the payload again. #[must_use] pub fn signature_message(&self) -> Vec { - let mut bytes = Vec::new(); + let mut bytes = Vec::from([self.chain_id]); bytes.extend(self.from_account.to_bytes()); if let Some(to) = &self.to_account { diff --git a/execution-core/src/transfer/phoenix.rs b/execution-core/src/transfer/phoenix.rs index ebd2cafbc1..eeb92ee00b 100644 --- a/execution-core/src/transfer/phoenix.rs +++ b/execution-core/src/transfer/phoenix.rs @@ -86,6 +86,7 @@ impl Transaction { deposit: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, exec: Option>, ) -> Result { let sender_pk = PublicKey::from(sender_sk); @@ -175,6 +176,7 @@ impl Transaction { deposit, }; let payload = Payload { + chain_id, tx_skeleton, fee, exec: exec.map(Into::into), @@ -356,6 +358,12 @@ impl Transaction { self.payload.fee.gas_price } + /// Returns the chain ID of the transaction. + #[must_use] + pub fn chain_id(&self) -> u8 { + self.payload.chain_id + } + /// Returns the max fee to be spend by the transaction. #[must_use] pub fn max_fee(&self) -> u64 { @@ -556,6 +564,8 @@ impl Transaction { #[derive(Debug, Clone, Archive, Serialize, Deserialize)] #[archive_attr(derive(CheckBytes))] pub struct Payload { + /// ID of the chain for this transaction to execute on. + pub chain_id: u8, /// Transaction skeleton used for the phoenix transaction. pub tx_skeleton: TxSkeleton, /// Data used to calculate the transaction fee. @@ -576,7 +586,7 @@ 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(); + let mut bytes = Vec::from([self.chain_id]); // serialize the tx-skeleton let skeleton_bytes = self.tx_skeleton.to_var_bytes(); @@ -609,6 +619,8 @@ impl Payload { pub fn from_slice(buf: &[u8]) -> Result { let mut buf = buf; + let chain_id = u8::from_reader(&mut buf)?; + // deserialize the tx-skeleton #[allow(clippy::cast_possible_truncation)] let skeleton_len = usize::try_from(u64::from_reader(&mut buf)?) @@ -630,6 +642,7 @@ impl Payload { }; Ok(Self { + chain_id, tx_skeleton, fee, exec, @@ -642,7 +655,9 @@ impl Payload { /// 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(); + let mut bytes = Vec::from([self.chain_id]); + + bytes.extend(self.tx_skeleton.to_hash_input_bytes()); match &self.exec { Some(ContractExec::Deploy(d)) => { diff --git a/execution-core/tests/serialization.rs b/execution-core/tests/serialization.rs index 22827c652e..a60e8d91c9 100644 --- a/execution-core/tests/serialization.rs +++ b/execution-core/tests/serialization.rs @@ -25,6 +25,8 @@ use poseidon_merkle::{Item, Tree}; use rand::rngs::StdRng; use rand::{CryptoRng, Rng, RngCore, SeedableRng}; +const CHAIN_ID: u8 = 0xFA; + struct TxCircuitVecProver(); // use the serialized TxCircuitVec as proof. This way that serialization is also @@ -121,6 +123,7 @@ fn new_phoenix_tx( deposit, gas_limit, gas_price, + CHAIN_ID, exec, ) .expect("transcaction generation should work") @@ -141,7 +144,8 @@ fn new_moonlight_tx( let nonce: u64 = rng.gen(); Transaction::moonlight( - &from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, exec, + &from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, + CHAIN_ID, exec, ) } From 070e3f5eb406c1ee17c7f6fde3d2e767b2bf647c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:08:40 +0100 Subject: [PATCH 018/102] transfer-contract: add chain ID checks --- contracts/transfer/src/lib.rs | 5 +++++ contracts/transfer/src/state.rs | 12 ++++++++++++ contracts/transfer/tests/common/utils.rs | 10 ++++++++++ contracts/transfer/tests/transfer.rs | 23 ++++++++++++++++++++--- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/contracts/transfer/src/lib.rs b/contracts/transfer/src/lib.rs index dc2a13d24f..58ff9f7c69 100644 --- a/contracts/transfer/src/lib.rs +++ b/contracts/transfer/src/lib.rs @@ -77,6 +77,11 @@ unsafe fn num_notes(arg_len: u32) -> u32 { rusk_abi::wrap_call(arg_len, |_: ()| STATE.num_notes()) } +#[no_mangle] +unsafe fn chain_id(arg_len: u32) -> u32 { + rusk_abi::wrap_call(arg_len, |_: ()| STATE.chain_id()) +} + // "Feeder" queries #[no_mangle] diff --git a/contracts/transfer/src/state.rs b/contracts/transfer/src/state.rs index 521edc7002..d0b7e8881f 100644 --- a/contracts/transfer/src/state.rs +++ b/contracts/transfer/src/state.rs @@ -342,6 +342,10 @@ impl TransferState { transitory::put_transaction(tx); let phoenix_tx = transitory::unwrap_phoenix_tx(); + if phoenix_tx.chain_id() != self.chain_id() { + panic!("The tx must target the correct chain"); + } + // panic if the root is invalid if !self.root_exists(phoenix_tx.root()) { panic!("Root not found in the state!"); @@ -398,6 +402,10 @@ impl TransferState { transitory::put_transaction(tx); let moonlight_tx = transitory::unwrap_moonlight_tx(); + if moonlight_tx.chain_id() != self.chain_id() { + panic!("The tx must target the correct chain"); + } + // check the signature is valid and made by `from` if !rusk_abi::verify_bls( moonlight_tx.signature_message(), @@ -676,6 +684,10 @@ impl TransferState { let block_height = rusk_abi::block_height(); self.push_note(block_height, note) } + + pub fn chain_id(&self) -> u8 { + rusk_abi::chain_id() + } } fn verify_tx_proof(tx: &PhoenixTransaction) -> bool { diff --git a/contracts/transfer/tests/common/utils.rs b/contracts/transfer/tests/common/utils.rs index 79393f502f..3d87b35a0c 100644 --- a/contracts/transfer/tests/common/utils.rs +++ b/contracts/transfer/tests/common/utils.rs @@ -112,6 +112,12 @@ pub fn opening( .map(|r| r.data) } +pub fn chain_id(session: &mut Session) -> Result { + session + .call(TRANSFER_CONTRACT, "chain_id", &(), GAS_LIMIT) + .map(|r| r.data) +} + /// Executes a transaction. /// Returns result containing gas spent. pub fn execute( @@ -214,6 +220,9 @@ pub fn create_phoenix_transaction( inputs.push((note.clone(), opening)); } + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + PhoenixTransaction::new::( rng, sender_sk, @@ -226,6 +235,7 @@ pub fn create_phoenix_transaction( deposit, gas_limit, gas_price, + chain_id, exec.map(Into::into), ) .expect("creating the creation shouldn't fail") diff --git a/contracts/transfer/tests/transfer.rs b/contracts/transfer/tests/transfer.rs index ed5f8619fb..af40fe58da 100644 --- a/contracts/transfer/tests/transfer.rs +++ b/contracts/transfer/tests/transfer.rs @@ -7,7 +7,7 @@ pub mod common; use crate::common::utils::{ - account, contract_balance, create_phoenix_transaction, execute, + account, chain_id, contract_balance, create_phoenix_transaction, execute, filter_notes_owned_by, leaves_from_height, leaves_from_pos, num_notes, owned_notes_value, update_root, }; @@ -53,6 +53,7 @@ const BOB_ID: ContractId = { }; const OWNER: [u8; 32] = [0; 32]; +const CHAIN_ID: u8 = 0xFA; /// Instantiate the virtual machine with the transfer contract deployed, with a /// single note carrying the `GENESIS_VALUE` owned by the given public key. @@ -72,7 +73,7 @@ fn instantiate( "../../../target/dusk/wasm32-unknown-unknown/release/alice.wasm" ); - let mut session = rusk_abi::new_genesis_session(vm); + let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID); session .deploy( @@ -137,7 +138,7 @@ fn instantiate( // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - rusk_abi::new_session(vm, base, 1) + rusk_abi::new_session(vm, base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } @@ -265,6 +266,9 @@ fn moonlight_transfer() { "The receiver account should be empty" ); + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let transaction = MoonlightTransaction::new( &moonlight_sender_sk, Some(moonlight_receiver_pk), @@ -273,6 +277,7 @@ fn moonlight_transfer() { GAS_LIMIT, LUX, sender_account.nonce + 1, + chain_id, None::, ); @@ -396,6 +401,9 @@ fn moonlight_alice_ping() { "The account should have the genesis value" ); + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let transaction = MoonlightTransaction::new( &moonlight_sk, None, @@ -404,6 +412,7 @@ fn moonlight_alice_ping() { GAS_LIMIT, LUX, acc.nonce + 1, + chain_id, contract_call, ); @@ -750,6 +759,9 @@ fn moonlight_to_phoenix_swap() { .to_vec(), }; + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let tx = MoonlightTransaction::new( &moonlight_sk, None, @@ -758,6 +770,7 @@ fn moonlight_to_phoenix_swap() { GAS_LIMIT, LUX, nonce, + chain_id, Some(contract_call), ); @@ -849,6 +862,9 @@ fn swap_wrong_contract_targeted() { .to_vec(), }; + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + let tx = MoonlightTransaction::new( &moonlight_sk, None, @@ -857,6 +873,7 @@ fn swap_wrong_contract_targeted() { GAS_LIMIT, LUX, nonce, + chain_id, Some(contract_call), ); From 71af198c7b65ed9b57f5c822d95d25303ece39dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:09:34 +0100 Subject: [PATCH 019/102] stake-contract: add chain ID checks --- contracts/stake/src/state.rs | 8 ++++++++ contracts/stake/tests/common/init.rs | 5 +++-- contracts/stake/tests/common/utils.rs | 10 ++++++++++ contracts/stake/tests/stake.rs | 11 +++++++---- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index 62c61f4a3c..4a239373f2 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -72,6 +72,10 @@ impl StakeState { let nonce = stake.nonce(); let signature = *stake.signature(); + if stake.chain_id() != self.chain_id() { + panic!("The stake must target the correct chain"); + } + let loaded_stake = self.load_or_create_stake_mut(&account); // ensure the stake is at least the minimum and that there isn't an @@ -456,6 +460,10 @@ impl StakeState { } } + fn chain_id(&self) -> u8 { + rusk_abi::chain_id() + } + fn deduct_contract_balance(amount: u64) { // Update the module balance to reflect the change in the amount // withdrawable from the contract diff --git a/contracts/stake/tests/common/init.rs b/contracts/stake/tests/common/init.rs index e129149b29..b26a05bf19 100644 --- a/contracts/stake/tests/common/init.rs +++ b/contracts/stake/tests/common/init.rs @@ -20,6 +20,7 @@ use rusk_abi::{ContractData, Session, VM}; use crate::common::utils::update_root; const OWNER: [u8; 32] = [0; 32]; +pub const CHAIN_ID: u8 = 0xFA; const POINT_LIMIT: u64 = 0x100_000_000; /// Instantiate the virtual machine with the transfer contract deployed, with a @@ -37,7 +38,7 @@ pub fn instantiate( "../../../../target/dusk/wasm32-unknown-unknown/release/stake_contract.wasm" ); - let mut session = rusk_abi::new_genesis_session(vm); + let mut session = rusk_abi::new_genesis_session(vm, CHAIN_ID); session .deploy( @@ -81,6 +82,6 @@ pub fn instantiate( // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - rusk_abi::new_session(vm, base, 1) + rusk_abi::new_session(vm, base, CHAIN_ID, 1) .expect("Instantiating new session should succeed") } diff --git a/contracts/stake/tests/common/utils.rs b/contracts/stake/tests/common/utils.rs index d77de40c84..a794bc07e3 100644 --- a/contracts/stake/tests/common/utils.rs +++ b/contracts/stake/tests/common/utils.rs @@ -87,6 +87,12 @@ pub fn opening( .map(|r| r.data) } +pub fn chain_id(session: &mut Session) -> Result { + session + .call(TRANSFER_CONTRACT, "chain_id", &(), POINT_LIMIT) + .map(|r| r.data) +} + pub fn filter_notes_owned_by>( vk: PhoenixViewKey, iter: I, @@ -179,6 +185,9 @@ pub fn create_transaction( inputs.push((note.clone(), opening)); } + let chain_id = + chain_id(session).expect("Getting the chain ID should succeed"); + PhoenixTransaction::new::( rng, sender_sk, @@ -191,6 +200,7 @@ pub fn create_transaction( deposit, gas_limit, gas_price, + chain_id, exec.map(Into::into), ) .expect("creating the creation shouldn't fail") diff --git a/contracts/stake/tests/stake.rs b/contracts/stake/tests/stake.rs index 7501bb2f94..598c18bb6e 100644 --- a/contracts/stake/tests/stake.rs +++ b/contracts/stake/tests/stake.rs @@ -26,7 +26,7 @@ use execution_core::{ }; use crate::common::assert::assert_event; -use crate::common::init::instantiate; +use crate::common::init::{instantiate, CHAIN_ID}; use crate::common::utils::*; const GENESIS_VALUE: u64 = dusk(1_000_000.0); @@ -71,8 +71,11 @@ fn stake_withdraw_unstake() { let input_note_pos = 0; let deposit = INITIAL_STAKE; + let chain_id = + chain_id(&mut session).expect("Getting the chain ID should succeed"); + // Fashion a Stake struct - let stake = Stake::new(&stake_sk, deposit, 1); + let stake = Stake::new(&stake_sk, deposit, 1, chain_id); let stake_bytes = rkyv::to_bytes::<_, 1024>(&stake) .expect("Should serialize Stake correctly") .to_vec(); @@ -230,7 +233,7 @@ fn stake_withdraw_unstake() { // set different block height so that the new notes are easily located and // filtered let base = session.commit().expect("Committing should succeed"); - let mut session = rusk_abi::new_session(vm, base, 2) + let mut session = rusk_abi::new_session(vm, base, CHAIN_ID, 2) .expect("Instantiating new session should succeed"); let receipt = @@ -344,7 +347,7 @@ fn stake_withdraw_unstake() { // filtered // sets the block height for all subsequent operations to 1 let base = session.commit().expect("Committing should succeed"); - let mut session = rusk_abi::new_session(vm, base, 3) + let mut session = rusk_abi::new_session(vm, base, CHAIN_ID, 3) .expect("Instantiating new session should succeed"); let receipt = From 287eea6b05068e826cd1b5c10f936b0c4432d36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:10:08 +0100 Subject: [PATCH 020/102] license-contract: adapt to chain ID --- contracts/license/tests/license.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/license/tests/license.rs b/contracts/license/tests/license.rs index 2a37fe02a4..64b2f412f3 100644 --- a/contracts/license/tests/license.rs +++ b/contracts/license/tests/license.rs @@ -41,6 +41,7 @@ const LICENSE_CONTRACT_ID: ContractId = { const POINT_LIMIT: u64 = 0x10000000; const TEST_OWNER: [u8; 32] = [0; 32]; +const CHAIN_ID: u8 = 0xFA; const USER_ATTRIBUTES: u64 = 545072475273; static LABEL: &[u8] = b"dusk-network"; @@ -66,7 +67,7 @@ fn initialize() -> Session { "../../../target/dusk/wasm32-unknown-unknown/release/license_contract.wasm" ); - let mut session = rusk_abi::new_genesis_session(&vm); + let mut session = rusk_abi::new_genesis_session(&vm, CHAIN_ID); session .deploy( From b285d739ce0165230ab75ba2feca4277a083812b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:11:18 +0100 Subject: [PATCH 021/102] wallet-core: use chain ID in transaction creation --- wallet-core/src/transaction.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wallet-core/src/transaction.rs b/wallet-core/src/transaction.rs index bc21c64aae..923f6e7f6c 100644 --- a/wallet-core/src/transaction.rs +++ b/wallet-core/src/transaction.rs @@ -61,6 +61,7 @@ pub fn phoenix( deposit: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, exec: Option>, ) -> Result { Ok(PhoenixTransaction::new::( @@ -75,6 +76,7 @@ pub fn phoenix( deposit, gas_limit, gas_price, + chain_id, exec, )? .into()) @@ -99,6 +101,7 @@ pub fn phoenix_stake( root: BlsScalar, gas_limit: u64, gas_price: u64, + chain_id: u8, stake_value: u64, current_nonce: u64, ) -> Result { @@ -109,7 +112,7 @@ pub fn phoenix_stake( let obfuscated_transaction = false; let deposit = stake_value; - let stake = Stake::new(stake_sk, stake_value, current_nonce + 1); + let stake = Stake::new(stake_sk, stake_value, current_nonce + 1, chain_id); let contract_call = ContractCall::new(STAKE_CONTRACT, "stake", &stake)?; @@ -125,6 +128,7 @@ pub fn phoenix_stake( deposit, gas_limit, gas_price, + chain_id, Some(contract_call), ) } @@ -150,6 +154,7 @@ pub fn phoenix_stake_reward( reward_amount: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, ) -> Result { let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); let change_pk = receiver_pk; @@ -190,6 +195,7 @@ pub fn phoenix_stake_reward( deposit, gas_limit, gas_price, + chain_id, Some(contract_call), ) } @@ -214,6 +220,7 @@ pub fn phoenix_unstake( unstake_value: u64, gas_limit: u64, gas_price: u64, + chain_id: u8, ) -> Result { let receiver_pk = PhoenixPublicKey::from(phoenix_sender_sk); let change_pk = receiver_pk; @@ -254,6 +261,7 @@ pub fn phoenix_unstake( deposit, gas_limit, gas_price, + chain_id, Some(contract_call), ) } From 8008290515d09f5d7a75fbe0dbee8029ecd2fdba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:12:14 +0100 Subject: [PATCH 022/102] test-wallet: add chain ID in transaction creation --- test-wallet/src/imp.rs | 25 ++++++++++++++++++++++++- test-wallet/src/lib.rs | 3 +++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs index 6297bce503..fb0583b7e1 100644 --- a/test-wallet/src/imp.rs +++ b/test-wallet/src/imp.rs @@ -370,6 +370,9 @@ where let transfer_value = 0; let obfuscated_transaction = false; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_transaction::( rng, &sender_sk, @@ -382,6 +385,7 @@ where deposit, gas_limit, gas_price, + chain_id, Some(exec), )?; @@ -416,6 +420,9 @@ where let exec: Option = None; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_transaction::( rng, &sender_sk, @@ -428,6 +435,7 @@ where deposit, gas_limit, gas_price, + chain_id, exec, )?; @@ -465,6 +473,9 @@ where .map_err(Error::from_state_err)? .nonce; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_stake::( rng, &phoenix_sender_sk, @@ -473,6 +484,7 @@ where root, gas_limit, gas_price, + chain_id, stake_value, current_nonce, )?; @@ -517,6 +529,9 @@ where })? .value; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_unstake::( rng, &phoenix_sender_sk, @@ -526,6 +541,7 @@ where staked_amount, gas_limit, gas_price, + chain_id, )?; stake_sk.zeroize(); @@ -562,6 +578,9 @@ where .map_err(Error::from_state_err)? .reward; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = phoenix_stake_reward::( rng, &phoenix_sender_sk, @@ -571,6 +590,7 @@ where stake_reward, gas_limit, gas_price, + chain_id, )?; stake_sk.zeroize(); @@ -631,9 +651,12 @@ where } let nonce = account.nonce + 1; + let chain_id = + self.state.fetch_chain_id().map_err(Error::from_state_err)?; + let tx = MoonlightTransaction::new( &from_sk, to_account, value, deposit, gas_limit, gas_price, nonce, - exec, + chain_id, exec, ); seed.zeroize(); diff --git a/test-wallet/src/lib.rs b/test-wallet/src/lib.rs index 32d4fbe7bd..773deb0ad1 100644 --- a/test-wallet/src/lib.rs +++ b/test-wallet/src/lib.rs @@ -143,4 +143,7 @@ pub trait StateClient { &self, pk: &BlsPublicKey, ) -> Result; + + /// Queries for the chain ID. + fn fetch_chain_id(&self) -> Result; } From 776ee8308584241150160b5fb2bfe67845a48e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:12:41 +0100 Subject: [PATCH 023/102] rusk-recovery: use dummy chain ID to instantiate state --- rusk-recovery/src/state.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index fa57461e82..3d24c518f9 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -40,6 +40,7 @@ pub const DEFAULT_SNAPSHOT: &str = include_str!("../config/testnet_remote.toml"); const GENESIS_BLOCK_HEIGHT: u64 = 0; +const GENESIS_CHAIN_ID: u8 = 0xFA; pub static DUSK_KEY: Lazy = Lazy::new(|| { let addr = include_str!("../assets/dusk.address"); @@ -171,7 +172,7 @@ fn generate_empty_state>( let state_dir = state_dir.as_ref(); let vm = rusk_abi::new_vm(state_dir)?; - let mut session = rusk_abi::new_genesis_session(&vm); + let mut session = rusk_abi::new_genesis_session(&vm, GENESIS_CHAIN_ID); let transfer_code = include_bytes!( "../../target/dusk/wasm64-unknown-unknown/release/transfer_contract.wasm" @@ -259,8 +260,12 @@ where None => generate_empty_state(state_dir, snapshot), }?; - let mut session = - rusk_abi::new_session(&vm, old_commit_id, GENESIS_BLOCK_HEIGHT)?; + let mut session = rusk_abi::new_session( + &vm, + old_commit_id, + GENESIS_CHAIN_ID, + GENESIS_BLOCK_HEIGHT, + )?; generate_transfer_state(&mut session, snapshot)?; generate_stake_state(&mut session, snapshot)?; From fb480702c121b4a1cf3dc0089f74e8a24f7f4027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:13:33 +0100 Subject: [PATCH 024/102] rusk: pass configurable chain ID to host data --- rusk/src/bin/config/kadcast.rs | 4 ++++ rusk/src/bin/main.rs | 1 + rusk/src/lib/node.rs | 1 + rusk/src/lib/node/rusk.rs | 14 +++++++++++++- rusk/tests/common/state.rs | 7 +++++-- rusk/tests/common/wallet.rs | 5 +++++ rusk/tests/rusk-state.rs | 3 ++- rusk/tests/services/contract_deployment.rs | 10 ++++++---- 8 files changed, 37 insertions(+), 8 deletions(-) diff --git a/rusk/src/bin/config/kadcast.rs b/rusk/src/bin/config/kadcast.rs index a95a58ee35..62d9cf09fc 100644 --- a/rusk/src/bin/config/kadcast.rs +++ b/rusk/src/bin/config/kadcast.rs @@ -33,4 +33,8 @@ impl KadcastConfig { self.0.kadcast_id = Some(network_id) }; } + + pub fn chain_id(&self) -> u8 { + self.0.kadcast_id.unwrap_or_default() + } } diff --git a/rusk/src/bin/main.rs b/rusk/src/bin/main.rs index b798d78306..9a913b7408 100644 --- a/rusk/src/bin/main.rs +++ b/rusk/src/bin/main.rs @@ -64,6 +64,7 @@ async fn main() -> Result<(), Box> { let rusk = Rusk::new( state_dir, + config.kadcast.chain_id(), config.chain.generation_timeout(), config.chain.gas_per_deploy_byte(), config.chain.block_gas_limit(), diff --git a/rusk/src/lib/node.rs b/rusk/src/lib/node.rs index 4e33f6e8c4..ec6957a43f 100644 --- a/rusk/src/lib/node.rs +++ b/rusk/src/lib/node.rs @@ -42,6 +42,7 @@ pub struct Rusk { pub(crate) tip: Arc>, pub(crate) vm: Arc, dir: PathBuf, + pub(crate) chain_id: u8, pub(crate) generation_timeout: Option, pub(crate) gas_per_deploy_byte: Option, pub(crate) feeder_gas_limit: u64, diff --git a/rusk/src/lib/node/rusk.rs b/rusk/src/lib/node/rusk.rs index 1dba4d2978..df65da6347 100644 --- a/rusk/src/lib/node/rusk.rs +++ b/rusk/src/lib/node/rusk.rs @@ -53,6 +53,7 @@ const DEFAULT_GAS_PER_DEPLOY_BYTE: u64 = 100; impl Rusk { pub fn new>( dir: P, + chain_id: u8, generation_timeout: Option, gas_per_deploy_byte: Option, block_gas_limit: u64, @@ -87,6 +88,7 @@ impl Rusk { tip, vm, dir: dir.into(), + chain_id, generation_timeout, gas_per_deploy_byte, feeder_gas_limit, @@ -347,6 +349,11 @@ impl Rusk { self.query(TRANSFER_CONTRACT, "account", pk) } + /// Returns an account's information. + pub fn chain_id(&self) -> Result { + self.query(TRANSFER_CONTRACT, "chain_id", &()) + } + /// Fetches the previous state data for stake changes in the contract. /// /// Communicates with the stake contract to obtain information about the @@ -396,7 +403,12 @@ impl Rusk { tip.current }); - let session = rusk_abi::new_session(&self.vm, commit, block_height)?; + let session = rusk_abi::new_session( + &self.vm, + commit, + self.chain_id, + block_height, + )?; Ok(session) } diff --git a/rusk/tests/common/state.rs b/rusk/tests/common/state.rs index ee97c9c8a6..153be2ae5f 100644 --- a/rusk/tests/common/state.rs +++ b/rusk/tests/common/state.rs @@ -29,6 +29,8 @@ use tracing::info; use crate::common::keys::STAKE_SK; +const CHAIN_ID: u8 = 0xFA; + // Creates a Rusk initial state in the given directory pub fn new_state>( dir: P, @@ -42,8 +44,9 @@ pub fn new_state>( let (sender, _) = broadcast::channel(10); - let rusk = Rusk::new(dir, None, None, block_gas_limit, u64::MAX, sender) - .expect("Instantiating rusk should succeed"); + let rusk = + Rusk::new(dir, CHAIN_ID, None, None, block_gas_limit, u64::MAX, sender) + .expect("Instantiating rusk should succeed"); assert_eq!( commit_id, diff --git a/rusk/tests/common/wallet.rs b/rusk/tests/common/wallet.rs index 7b164edf88..612792f50c 100644 --- a/rusk/tests/common/wallet.rs +++ b/rusk/tests/common/wallet.rs @@ -121,6 +121,11 @@ impl wallet::StateClient for TestStateClient { let account = self.rusk.account(pk)?; Ok(account) } + + fn fetch_chain_id(&self) -> Result { + let chain_id = self.rusk.chain_id()?; + Ok(chain_id) + } } #[derive(Default, Debug, Clone)] diff --git a/rusk/tests/rusk-state.rs b/rusk/tests/rusk-state.rs index 813ca14eb4..4ef736a130 100644 --- a/rusk/tests/rusk-state.rs +++ b/rusk/tests/rusk-state.rs @@ -35,6 +35,7 @@ use tracing::info; use crate::common::state::new_state; const BLOCK_HEIGHT: u64 = 1; +const CHAIN_ID: u8 = 0xFA; const BLOCK_GAS_LIMIT: u64 = 100_000_000_000; const INITIAL_BALANCE: u64 = 10_000_000_000; @@ -83,7 +84,7 @@ where rusk.with_tip(|mut tip, vm| { let current_commit = tip.current; let mut session = - rusk_abi::new_session(vm, current_commit, BLOCK_HEIGHT) + rusk_abi::new_session(vm, current_commit, CHAIN_ID, BLOCK_HEIGHT) .expect("current commit should exist"); session diff --git a/rusk/tests/services/contract_deployment.rs b/rusk/tests/services/contract_deployment.rs index 1761dd6a54..efb6a439cc 100644 --- a/rusk/tests/services/contract_deployment.rs +++ b/rusk/tests/services/contract_deployment.rs @@ -44,6 +44,7 @@ const ALICE_CONTRACT_ID: ContractId = { }; const OWNER: [u8; 32] = [1; 32]; +const CHAIN_ID: u8 = 0xFA; const BOB_ECHO_VALUE: u64 = 775; const BOB_INIT_VALUE: u8 = 5; @@ -95,8 +96,9 @@ fn initial_state>(dir: P, deploy_bob: bool) -> Result { let (sender, _) = broadcast::channel(10); - let rusk = Rusk::new(dir, None, None, BLOCK_GAS_LIMIT, u64::MAX, sender) - .expect("Instantiating rusk should succeed"); + let rusk = + Rusk::new(dir, CHAIN_ID, None, None, BLOCK_GAS_LIMIT, u64::MAX, sender) + .expect("Instantiating rusk should succeed"); Ok(rusk) } @@ -215,7 +217,7 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = rusk_abi::new_vm(self.path.as_path()) .expect("VM creation should succeed"); - let mut session = rusk_abi::new_session(&vm, commit, 0) + let mut session = rusk_abi::new_session(&vm, commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, @@ -233,7 +235,7 @@ impl Fixture { let commit = self.rusk.state_root(); let vm = rusk_abi::new_vm(self.path.as_path()) .expect("VM creation should succeed"); - let mut session = rusk_abi::new_session(&vm, commit, 0) + let mut session = rusk_abi::new_session(&vm, commit, CHAIN_ID, 0) .expect("Session creation should succeed"); let result = session.call::<_, u64>( self.contract_id, From b394cd6b2bc68843d0e7a0c924d2d993a8f30d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Fri, 30 Aug 2024 16:15:23 +0100 Subject: [PATCH 025/102] node-data: adapt to chain ID in phoenix payload --- node-data/src/ledger/transaction.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/node-data/src/ledger/transaction.rs b/node-data/src/ledger/transaction.rs index bfcd2844da..1ea38619dc 100644 --- a/node-data/src/ledger/transaction.rs +++ b/node-data/src/ledger/transaction.rs @@ -156,6 +156,7 @@ pub mod faker { ContractCall::new([21; 32], "some_method", &()).unwrap(); let payload = PhoenixPayload { + chain_id: 0xFA, tx_skeleton, fee, exec: Some(ContractExec::Call(contract_call)), From a0e7cb26606347330af8d99abce3df30c8449a96 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:22:49 +0200 Subject: [PATCH 026/102] execution-core: add `StakeWithReceiverEvent` --- execution-core/src/stake.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs index d51a93e290..4e7654799f 100644 --- a/execution-core/src/stake.rs +++ b/execution-core/src/stake.rs @@ -196,11 +196,21 @@ impl Withdraw { pub struct StakeEvent { /// Account associated to the event. pub account: BlsPublicKey, - /// Value of the relevant operation, be it `stake`, `unstake`, `withdraw`, - /// `reward`, or `slash`. + /// Value of the relevant operation, be it `stake`, `reward` or `slash`. + /// + /// In case of `suspended` the amount refers to the next eligibility + pub value: u64, +} + +/// Event emitted after a stake contract operation is performed. +#[derive(Debug, Clone, Archive, Deserialize, Serialize)] +#[archive_attr(derive(CheckBytes))] +pub struct StakeWithReceiverEvent { + /// Account associated to the event. + pub account: BlsPublicKey, + /// Value of the relevant operation, be it `unstake` or `withdraw`. pub value: u64, - /// The receiver of the action, relevant in `withdraw` and `unstake` - /// operations. + /// The receiver of the action pub receiver: Option, } From 21b200fe4438a3de54c733ac3e04156d53b96eba Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:26:02 +0200 Subject: [PATCH 027/102] stake-contract: change events to use `StakeWithReceiverEvent` if needed --- contracts/stake/src/state.rs | 33 +++++------------- contracts/stake/tests/common/assert.rs | 46 ++++++++++++++++---------- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index 4a239373f2..be46fd0daa 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -12,8 +12,9 @@ use dusk_bytes::Serializable; use execution_core::{ signatures::bls::PublicKey as BlsPublicKey, stake::{ - next_epoch, Stake, StakeAmount, StakeData, StakeEvent, Withdraw, EPOCH, - MINIMUM_STAKE, STAKE_CONTRACT, STAKE_WARNINGS, + next_epoch, Stake, StakeAmount, StakeData, StakeEvent, + StakeWithReceiverEvent, Withdraw, EPOCH, MINIMUM_STAKE, STAKE_CONTRACT, + STAKE_WARNINGS, }, transfer::TRANSFER_CONTRACT, }; @@ -114,14 +115,7 @@ impl StakeState { loaded_stake.amount = Some(StakeAmount::new(value, rusk_abi::block_height())); - rusk_abi::emit( - "stake", - StakeEvent { - account, - value, - receiver: None, - }, - ); + rusk_abi::emit("stake", StakeEvent { account, value }); let key = account.to_bytes(); self.previous_block_state @@ -170,7 +164,7 @@ impl StakeState { rusk_abi::emit( "unstake", - StakeEvent { + StakeWithReceiverEvent { account, value: staked_value, receiver: Some(*transfer_withdraw.receiver()), @@ -217,10 +211,9 @@ impl StakeState { // update the state accordingly loaded_stake.reward = 0; - rusk_abi::emit( "withdraw", - StakeEvent { + StakeWithReceiverEvent { account, value, receiver: Some(*transfer_withdraw.receiver()), @@ -280,14 +273,8 @@ impl StakeState { stake.reward += value; - rusk_abi::emit( - "reward", - StakeEvent { - account: *account, - value, - receiver: None, - }, - ); + let account = *account; + rusk_abi::emit("reward", StakeEvent { account, value }); } /// Total amount burned since the genesis @@ -339,7 +326,6 @@ impl StakeState { StakeEvent { account: *account, value: stake_amount.eligibility, - receiver: None, }, ); } @@ -363,7 +349,6 @@ impl StakeState { StakeEvent { account: *account, value: to_slash, - receiver: None, }, ); } @@ -414,7 +399,6 @@ impl StakeState { StakeEvent { account: *account, value: stake_amount.eligibility, - receiver: None, }, ); @@ -437,7 +421,6 @@ impl StakeState { StakeEvent { account: *account, value: to_slash, - receiver: None, }, ); } diff --git a/contracts/stake/tests/common/assert.rs b/contracts/stake/tests/common/assert.rs index 322ea3e684..9b3cf0b0ac 100644 --- a/contracts/stake/tests/common/assert.rs +++ b/contracts/stake/tests/common/assert.rs @@ -8,7 +8,9 @@ use dusk_bytes::Serializable; use rkyv::{check_archived_root, Deserialize, Infallible}; use execution_core::{ - signatures::bls::PublicKey as BlsPublicKey, stake::StakeEvent, Event, + signatures::bls::PublicKey as BlsPublicKey, + stake::{StakeEvent, StakeWithReceiverEvent}, + Event, }; pub fn assert_event( @@ -19,20 +21,30 @@ pub fn assert_event( ) where S: AsRef, { - let event = - events - .iter() - .find(|e| e.topic == topic.as_ref()) - .expect(&format!( - "event: {} should exist in the event list", - topic.as_ref() - )); - let staking_event_data = - check_archived_root::(event.data.as_slice()) - .expect("Stake event data should deserialize correctly"); - 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.account.to_bytes(), should_pk.to_bytes()); + let topic = topic.as_ref(); + let event = events + .iter() + .find(|e| e.topic == topic) + .expect(&format!("event: {topic} should exist in the event list",)); + + if topic == "unstake" || topic == "withdraw" { + let staking_event_data = check_archived_root::( + event.data.as_slice(), + ) + .expect("Stake event data should deserialize correctly"); + let staking_event_data: StakeWithReceiverEvent = staking_event_data + .deserialize(&mut Infallible) + .expect("Infallible"); + assert_eq!(staking_event_data.value, should_amount); + assert_eq!(staking_event_data.account.to_bytes(), should_pk.to_bytes()); + } else { + let staking_event_data = + check_archived_root::(event.data.as_slice()) + .expect("Stake event data should deserialize correctly"); + 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.account.to_bytes(), should_pk.to_bytes()); + } } From 37cfeca0473c564ad4da90db5add219f4040d5ae Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:42:33 +0200 Subject: [PATCH 028/102] execution-core: add `locked` field to `StakeAmount` --- execution-core/src/stake.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/execution-core/src/stake.rs b/execution-core/src/stake.rs index 4e7654799f..eb6066c665 100644 --- a/execution-core/src/stake.rs +++ b/execution-core/src/stake.rs @@ -278,7 +278,11 @@ impl StakeData { ) -> Self { let amount = match value { 0 => None, - _ => Some(StakeAmount { value, eligibility }), + _ => Some(StakeAmount { + value, + eligibility, + locked: 0, + }), }; Self { @@ -381,6 +385,8 @@ impl Serializable for StakeData { pub struct StakeAmount { /// The value staked. pub value: u64, + /// The amount that has been locked (due to a soft slash). + pub locked: u64, /// The eligibility of the stake. pub eligibility: u64, } @@ -397,7 +403,11 @@ impl StakeAmount { /// the `eligibility`. #[must_use] pub const fn with_eligibility(value: u64, eligibility: u64) -> Self { - Self { value, eligibility } + Self { + value, + eligibility, + locked: 0, + } } /// Compute the eligibility of a stake from the starting block height. @@ -406,9 +416,14 @@ impl StakeAmount { let maturity_blocks = EPOCH; next_epoch(block_height) + maturity_blocks } + /// Move `amount` to locked value + pub fn lock_amount(&mut self, amount: u64) { + self.value -= amount; + self.locked += amount; + } } -const STAKE_AMOUNT_SIZE: usize = u64::SIZE + u64::SIZE; +const STAKE_AMOUNT_SIZE: usize = u64::SIZE + u64::SIZE + u64::SIZE; impl Serializable for StakeAmount { type Error = dusk_bytes::Error; @@ -420,9 +435,14 @@ impl Serializable for StakeAmount { let mut buf = &buf[..]; let value = u64::from_reader(&mut buf)?; + let locked = u64::from_reader(&mut buf)?; let eligibility = u64::from_reader(&mut buf)?; - Ok(Self { value, eligibility }) + Ok(Self { + value, + locked, + eligibility, + }) } #[allow(unused_must_use)] @@ -431,6 +451,7 @@ impl Serializable for StakeAmount { let mut writer = &mut buf[..]; writer.write(&self.value.to_bytes()); + writer.write(&self.locked.to_bytes()); writer.write(&self.eligibility.to_bytes()); buf From f7c29f7f00a1a2b1ae3f0daa58d1b9d706542c3c Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:43:23 +0200 Subject: [PATCH 029/102] stake-contract: change `slash` to lock amount instead of move funds to reward --- contracts/stake/src/state.rs | 7 +------ contracts/stake/tests/events.rs | 2 ++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index be46fd0daa..f907c589cf 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -337,12 +337,7 @@ impl StakeState { 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.value -= to_slash; - stake.reward += to_slash; - - Self::deduct_contract_balance(to_slash); + stake_amount.lock_amount(to_slash); rusk_abi::emit( "slash", diff --git a/contracts/stake/tests/events.rs b/contracts/stake/tests/events.rs index fa8183beed..780d0b08ff 100644 --- a/contracts/stake/tests/events.rs +++ b/contracts/stake/tests/events.rs @@ -51,6 +51,7 @@ fn reward_slash() -> Result<(), PiecrustError> { amount: Some(StakeAmount { value: stake_amount, eligibility: 0, + locked: 0, }), nonce: 0, faults: 0, @@ -142,6 +143,7 @@ fn stake_hard_slash() -> Result<(), PiecrustError> { amount: Some(StakeAmount { value: stake_amount, eligibility: block_height, + locked: 0, }), nonce: 0, faults: 0, From d2b2e968ef708d249edc0ba71fd9cf6533ff4ac0 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:43:49 +0200 Subject: [PATCH 030/102] rusk-recovery: use `locked` field for `StakeAmount` --- rusk-recovery/src/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index 3d24c518f9..6b63b4079c 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -127,6 +127,7 @@ fn generate_stake_state( let amount = (staker.amount > 0).then(|| StakeAmount { value: staker.amount, eligibility: staker.eligibility.unwrap_or_default(), + locked: 0, }); let stake = StakeData { From 7a5f2f689577f184e0eb8c27bbe2deaaa8ccbc6b Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:47:06 +0200 Subject: [PATCH 031/102] stake-contract: change unstake to withdraw the whole stake amount --- contracts/stake/src/state.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index f907c589cf..36e7f111b3 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -138,12 +138,13 @@ impl StakeState { // ensure there is a value staked, and that the withdrawal is exactly // the same amount - let staked_value = loaded_stake + let stake = loaded_stake .amount - .expect("There must be an amount to unstake") - .value; + .as_ref() + .expect("There must be an amount to unstake"); + let withdrawal_value = stake.locked + stake.value; - if value != staked_value { + if value != withdrawal_value { panic!("Value withdrawn different from staked amount"); } @@ -166,7 +167,7 @@ impl StakeState { "unstake", StakeWithReceiverEvent { account, - value: staked_value, + value: withdrawal_value, receiver: Some(*transfer_withdraw.receiver()), }, ); From f66542388ef80b2b55778def4487dbdd556a9a3f Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 14:52:02 +0200 Subject: [PATCH 032/102] stake-contract: allow partial reward withdrawal --- contracts/stake/src/state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/stake/src/state.rs b/contracts/stake/src/state.rs index 36e7f111b3..d46afddf9e 100644 --- a/contracts/stake/src/state.rs +++ b/contracts/stake/src/state.rs @@ -194,8 +194,8 @@ impl StakeState { panic!("There is no reward available to withdraw"); } - if value != loaded_stake.reward { - panic!("Value withdrawn different from available reward"); + if value > loaded_stake.reward { + panic!("Value withdrawn higher than available reward"); } // check signature is correct @@ -211,7 +211,7 @@ impl StakeState { .expect("Withdrawing reward should succeed"); // update the state accordingly - loaded_stake.reward = 0; + loaded_stake.reward -= value; rusk_abi::emit( "withdraw", StakeWithReceiverEvent { From 1c4ec2aef629ee721c409c48a0c3df327cf83410 Mon Sep 17 00:00:00 2001 From: Herr Seppia Date: Sat, 31 Aug 2024 15:43:05 +0200 Subject: [PATCH 033/102] rusk: adapt stake slash test to new lockout mechanism --- rusk/tests/services/stake.rs | 62 +++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/rusk/tests/services/stake.rs b/rusk/tests/services/stake.rs index 5033db1407..6aab285431 100644 --- a/rusk/tests/services/stake.rs +++ b/rusk/tests/services/stake.rs @@ -298,12 +298,14 @@ pub async fn slash() -> Result<()> { .expect("balance to exists"); let to_slash = wallet.account_public_key(0).unwrap(); let stake = wallet.get_stake(0).unwrap(); + let initial_stake_value = dusk(20.0); assert_eq!(stake.reward, dusk(3.0)); assert_eq!( stake.amount, Some(StakeAmount { - value: dusk(20.0), - eligibility: 0 + value: initial_stake_value, + eligibility: 0, + locked: 0 }) ); @@ -334,7 +336,8 @@ pub async fn slash() -> Result<()> { prev.amount, Some(StakeAmount { value: dusk(20.0), - eligibility: 0 + eligibility: 0, + locked: 0 }) ); @@ -342,25 +345,17 @@ pub async fn slash() -> Result<()> { let slashed_amount = prev_stake / 10; let after_slash = wallet.get_stake(0).unwrap(); - assert_eq!(after_slash.reward, dusk(5.0)); - assert_eq!(after_slash.reward, prev.reward + slashed_amount); + assert_eq!(after_slash.reward, dusk(3.0)); assert_eq!( after_slash.amount, Some(StakeAmount { value: prev_stake - slashed_amount, - eligibility: 4320 - }) - ); - assert_eq!( - after_slash.amount, - Some(StakeAmount { - value: dusk(18.0), - eligibility: 4320 + eligibility: 4320, + locked: dusk(2.0) }) ); let new_balance = rusk.contract_balance(STAKE_CONTRACT).unwrap(); - assert_eq!(new_balance, contract_balance - slashed_amount); - let contract_balance = new_balance; + assert_eq!(new_balance, contract_balance); generator_procedure( &rusk, @@ -375,40 +370,42 @@ pub async fn slash() -> Result<()> { let last_changes = rusk.last_provisioners_change(None).unwrap(); 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.reward, dusk(3.0)); assert_eq!( prev.amount, Some(StakeAmount { value: dusk(18.0), - eligibility: 4320 + eligibility: 4320, + locked: dusk(2.0) }) ); let prev_stake = prev.amount.unwrap().value; + let prev_locked = prev.amount.unwrap().locked; // 20% slash let slashed_amount = prev_stake / 10 * 2; let after_slash = wallet.get_stake(0).unwrap(); - assert_eq!(after_slash.reward, dusk(8.6)); - assert_eq!(after_slash.reward, prev.reward + slashed_amount); + assert_eq!(after_slash.reward, dusk(3.0)); assert_eq!( after_slash.amount, Some(StakeAmount { value: prev_stake - slashed_amount, - eligibility: 6480 + eligibility: 6480, + locked: prev_locked + slashed_amount }) ); assert_eq!( after_slash.amount, Some(StakeAmount { value: dusk(14.4), - eligibility: 6480 + eligibility: 6480, + locked: dusk(20.0) - dusk(14.4) }) ); let new_balance = rusk.contract_balance(STAKE_CONTRACT).unwrap(); - assert_eq!(new_balance, contract_balance - slashed_amount); - let contract_balance = new_balance; + assert_eq!(new_balance, contract_balance); generator_procedure( &rusk, @@ -423,29 +420,33 @@ pub async fn slash() -> Result<()> { let last_changes = rusk.last_provisioners_change(None).unwrap(); 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.reward, dusk(3.0)); assert_eq!( prev.amount, Some(StakeAmount { value: dusk(14.4), - eligibility: 6480 + eligibility: 6480, + locked: dusk(20.0) - dusk(14.4) }) ); let prev_stake = prev.amount.unwrap().value; + let prev_locked = prev.amount.unwrap().locked; // 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.reward, dusk(3.0)); assert_eq!( after_slash.amount, Some(StakeAmount { value: dusk(10.08), - eligibility: 17280 + eligibility: 17280, + locked: prev_locked + slashed_amount }) ); let new_balance = rusk.contract_balance(STAKE_CONTRACT).unwrap(); - assert_eq!(new_balance, contract_balance - slashed_amount); + assert_eq!(new_balance, contract_balance); generator_procedure( &rusk, @@ -461,12 +462,13 @@ pub async fn slash() -> Result<()> { let last_changes = rusk.last_provisioners_change(None).unwrap(); 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.reward, dusk(3.0)); assert_eq!( prev.amount, Some(StakeAmount { value: dusk(14.4), - eligibility: 6480 + eligibility: 6480, + locked: dusk(20.0) - dusk(14.4) }) ); From 3dab2b4f8f1867457a976bc863f86b83e731f84e Mon Sep 17 00:00:00 2001 From: Hein Dauven Date: Sun, 1 Sep 2024 11:47:25 +0200 Subject: [PATCH 034/102] ci: Fix bench CI filter predicate --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 6be23dff27..bd21ff9efc 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -21,7 +21,7 @@ jobs: run-ci: - 'rusk/**/* - 'node/**/* - predicate-quantifier: "any" + predicate-quantifier: 'some' benchmark: needs: changes From 4604a36d8044a8344b5b2986f4ae42e8a73b2860 Mon Sep 17 00:00:00 2001 From: Kieran Hall Date: Sat, 31 Aug 2024 18:37:32 +0200 Subject: [PATCH 035/102] web-wallet: Add UsageIndicator to Balance component Resolves #2234 --- .../lib/components/Allocate/Allocate.svelte | 2 +- .../src/lib/components/Balance/Balance.svelte | 25 +- .../UsageIndicator/UsageIndicator.svelte | 5 +- .../lib/components/__tests__/Balance.spec.js | 18 +- .../__snapshots__/Balance.spec.js.snap | 262 +++++++++-------- .../src/routes/(app)/dashboard/+layout.svelte | 8 + .../__snapshots__/layout.spec.js.snap | 272 ++++++++++++------ .../__tests__/__snapshots__/page.spec.js.snap | 20 +- .../components-showcase/Balances.svelte | 14 +- .../src/style/dusk-components/balance.css | 10 +- web-wallet/src/style/dusk/language.css | 1 + 11 files changed, 397 insertions(+), 240 deletions(-) diff --git a/web-wallet/src/lib/components/Allocate/Allocate.svelte b/web-wallet/src/lib/components/Allocate/Allocate.svelte index e7714c5d07..53af14e372 100644 --- a/web-wallet/src/lib/components/Allocate/Allocate.svelte +++ b/web-wallet/src/lib/components/Allocate/Allocate.svelte @@ -314,7 +314,7 @@ flex-direction: column; justify-content: center; align-items: flex-start; - gap: var(--gap-medium); + gap: var(--medium-gap); align-self: stretch; border-radius: var(--fieldset-border-radius); diff --git a/web-wallet/src/lib/components/Balance/Balance.svelte b/web-wallet/src/lib/components/Balance/Balance.svelte index bfc78777dc..d3da664847 100644 --- a/web-wallet/src/lib/components/Balance/Balance.svelte +++ b/web-wallet/src/lib/components/Balance/Balance.svelte @@ -1,8 +1,7 @@
    @@ -8,6 +11,15 @@ fiatPrice={10} locale="en" tokenCurrency="DUSK" - tokens={2000000} + tokens={balance} + /> + +
    diff --git a/web-wallet/src/style/dusk-components/balance.css b/web-wallet/src/style/dusk-components/balance.css index 68a8c5ae19..80f0081f4b 100644 --- a/web-wallet/src/style/dusk-components/balance.css +++ b/web-wallet/src/style/dusk-components/balance.css @@ -1,6 +1,7 @@ .dusk-balance { display: flex; flex-direction: column; + text-align: center; } .dusk-balance__header > h2 { @@ -13,6 +14,7 @@ display: flex; align-items: center; gap: 0.5em; + justify-content: center; } .dusk-balance__dusk > strong { @@ -28,14 +30,14 @@ line-height: 150%; letter-spacing: -0.01em; visibility: hidden; + color: var(--muted-color); } .dusk-balance__fiat--visible > strong { visibility: unset; } -.dusk-icon.dusk-balance__icon { - width: 1.3125em; - height: 1.3125em; - flex-shrink: 0; +.dusk-balance__usage { + padding: 0.62em 0.75em; + margin-top: 0.62em; } diff --git a/web-wallet/src/style/dusk/language.css b/web-wallet/src/style/dusk/language.css index 74aff3f4d6..821f6e5fd7 100644 --- a/web-wallet/src/style/dusk/language.css +++ b/web-wallet/src/style/dusk/language.css @@ -15,6 +15,7 @@ --success-color: var(--success-500); --success-color-variant-dark: var(--success-700); --warning-color: var(--warning); + --muted-color: var(--granite-grey); --on-background-color: var(--smokey-black); --on-primary-color: var(--light-grey); From 50bab510493903eb99ecbaff3ab6a820ce70224e Mon Sep 17 00:00:00 2001 From: Kieran Hall Date: Sat, 31 Aug 2024 14:46:30 +0200 Subject: [PATCH 036/102] web-wallet: Split transfer contract UI Resolves #2175 Fix --- web-wallet/CHANGELOG.md | 3 + .../lib/components/Allocate/Allocate.svelte | 2 +- .../src/lib/components/Receive/Receive.svelte | 10 +- .../src/lib/components/Send/Send.svelte | 14 +- .../__tests__/__snapshots__/Send.spec.js.snap | 14 +- .../TransferContract/TransferContract.svelte | 80 ------ web-wallet/src/lib/containers/index.js | 1 - .../src/lib/contracts/contract-descriptors.js | 27 +- .../src/routes/(app)/dashboard/+page.svelte | 8 + .../__tests__/__snapshots__/page.spec.js.snap | 72 ++++- .../__tests__/__snapshots__/page.spec.js.snap | 20 +- .../(app)/dashboard/receive/+page.svelte | 19 ++ .../__tests__/__snapshots__/page.spec.js.snap | 131 +++++++++ .../dashboard/receive/__tests__/page.spec.js | 17 ++ .../routes/(app)/dashboard/send/+page.svelte | 43 +++ .../__tests__/__snapshots__/page.spec.js.snap | 257 ++++++++++++++++++ .../{transfer => send}/__tests__/page.spec.js | 10 +- .../(app)/dashboard/staking/+page.svelte | 2 +- .../(app)/dashboard/transfer/+page.svelte | 27 -- .../__tests__/__snapshots__/page.spec.js.snap | 161 ----------- 20 files changed, 589 insertions(+), 329 deletions(-) delete mode 100644 web-wallet/src/lib/containers/TransferContract/TransferContract.svelte create mode 100644 web-wallet/src/routes/(app)/dashboard/receive/+page.svelte create mode 100644 web-wallet/src/routes/(app)/dashboard/receive/__tests__/__snapshots__/page.spec.js.snap create mode 100644 web-wallet/src/routes/(app)/dashboard/receive/__tests__/page.spec.js create mode 100644 web-wallet/src/routes/(app)/dashboard/send/+page.svelte create mode 100644 web-wallet/src/routes/(app)/dashboard/send/__tests__/__snapshots__/page.spec.js.snap rename web-wallet/src/routes/(app)/dashboard/{transfer => send}/__tests__/page.spec.js (53%) delete mode 100644 web-wallet/src/routes/(app)/dashboard/transfer/+page.svelte delete mode 100644 web-wallet/src/routes/(app)/dashboard/transfer/__tests__/__snapshots__/page.spec.js.snap diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md index d706b92ea4..91faa7eb2e 100644 --- a/web-wallet/CHANGELOG.md +++ b/web-wallet/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update Buttons to match the design system [#1606] - Update `Stepper` component to new design [#2071] - Update dashboard to use routes instead of `Tabs` for navigation pattern [#2075] +- Update dashboard by splitting the transfer operations into send and receive operations [#2175] ### Fixed @@ -238,7 +239,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#2026]: https://github.com/dusk-network/rusk/issues/2026 [#2000]: https://github.com/dusk-network/rusk/issues/2000 [#2071]: https://github.com/dusk-network/rusk/issues/2071 +[#2071]: https://github.com/dusk-network/rusk/issues/2075 [#2118]: https://github.com/dusk-network/rusk/issues/2118 +[#2118]: https://github.com/dusk-network/rusk/issues/2175 diff --git a/web-wallet/src/lib/components/Allocate/Allocate.svelte b/web-wallet/src/lib/components/Allocate/Allocate.svelte index e7714c5d07..53af14e372 100644 --- a/web-wallet/src/lib/components/Allocate/Allocate.svelte +++ b/web-wallet/src/lib/components/Allocate/Allocate.svelte @@ -314,7 +314,7 @@ flex-direction: column; justify-content: center; align-items: flex-start; - gap: var(--gap-medium); + gap: var(--medium-gap); align-self: stretch; border-radius: var(--fieldset-border-radius); diff --git a/web-wallet/src/lib/components/Receive/Receive.svelte b/web-wallet/src/lib/components/Receive/Receive.svelte index 49ed0b4c96..0952d546db 100644 --- a/web-wallet/src/lib/components/Receive/Receive.svelte +++ b/web-wallet/src/lib/components/Receive/Receive.svelte @@ -1,9 +1,9 @@ @@ -37,12 +35,10 @@
    {#if !hideBackButton} - + + +