From 9935b892a1e5be8268a3029fb3ccba04d08e14bb Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 09:35:57 -0800 Subject: [PATCH 01/30] proto(balance): define Balance message --- proto/penumbra/penumbra/core/asset/v1/asset.proto | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proto/penumbra/penumbra/core/asset/v1/asset.proto b/proto/penumbra/penumbra/core/asset/v1/asset.proto index 2b9bdbd9a6..2bf11f5cf2 100644 --- a/proto/penumbra/penumbra/core/asset/v1/asset.proto +++ b/proto/penumbra/penumbra/core/asset/v1/asset.proto @@ -92,6 +92,13 @@ message Value { AssetId asset_id = 2; } +message Balance { + // Indicates if the balance is negated. + bool negated = 1; + // Represents the vector of 'Values' in the balance. + repeated Value balance = 2; +} + // Represents a value of a known or unknown denomination. message ValueView { // A value whose asset ID is known and has metadata. From 30138f8c16106e15c249be22ebbe8c9b7aab8804 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 09:37:16 -0800 Subject: [PATCH 02/30] regenerate protos for Balance message --- .../src/gen/proto_descriptor.bin.no_lfs | Bin 82425 -> 99467 bytes .../proto/src/gen/penumbra.core.asset.v1.rs | 17 +++ .../src/gen/penumbra.core.asset.v1.serde.rs | 112 ++++++++++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 627367 -> 644756 bytes 4 files changed, 129 insertions(+) diff --git a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs index 09181ca63e0d92d8142235c3003f76694fd27e4c..cb5f8b9377fc15a61da07be47f5ef6ad339b0300 100644 GIT binary patch delta 31833 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 diff --git a/crates/proto/src/gen/penumbra.core.asset.v1.rs b/crates/proto/src/gen/penumbra.core.asset.v1.rs index 9ec732783c..a062c80991 100644 --- a/crates/proto/src/gen/penumbra.core.asset.v1.rs +++ b/crates/proto/src/gen/penumbra.core.asset.v1.rs @@ -141,6 +141,23 @@ impl ::prost::Name for Value { ::prost::alloc::format!("penumbra.core.asset.v1.{}", Self::NAME) } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Balance { + /// Indicates if the balance is negated. + #[prost(bool, tag = "1")] + pub negated: bool, + /// Represents the vector of 'Values' in the balance. + #[prost(message, repeated, tag = "2")] + pub balance: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for Balance { + const NAME: &'static str = "Balance"; + const PACKAGE: &'static str = "penumbra.core.asset.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.asset.v1.{}", Self::NAME) + } +} /// Represents a value of a known or unknown denomination. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs b/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs index bf0faaf4d8..40ed473489 100644 --- a/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs @@ -392,6 +392,118 @@ impl<'de> serde::Deserialize<'de> for asset_image::Theme { deserializer.deserialize_struct("penumbra.core.asset.v1.AssetImage.Theme", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for Balance { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.negated { + len += 1; + } + if !self.balance.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.asset.v1.Balance", len)?; + if self.negated { + struct_ser.serialize_field("negated", &self.negated)?; + } + if !self.balance.is_empty() { + struct_ser.serialize_field("balance", &self.balance)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Balance { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "negated", + "balance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Negated, + Balance, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "negated" => Ok(GeneratedField::Negated), + "balance" => Ok(GeneratedField::Balance), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Balance; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.asset.v1.Balance") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut negated__ = None; + let mut balance__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Negated => { + if negated__.is_some() { + return Err(serde::de::Error::duplicate_field("negated")); + } + negated__ = Some(map_.next_value()?); + } + GeneratedField::Balance => { + if balance__.is_some() { + return Err(serde::de::Error::duplicate_field("balance")); + } + balance__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(Balance { + negated: negated__.unwrap_or_default(), + balance: balance__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.asset.v1.Balance", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for BalanceCommitment { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index f2452b1c4b474c5738ad69f0c7d1b1e5f47508b9..70fef211723356f21bcabb49bfa544ba3da13e1a 100644 GIT binary patch delta 35031 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 Date: Sun, 24 Nov 2024 09:38:06 -0800 Subject: [PATCH 03/30] proto(TransactionSummary): define TransactionSummary message --- .../penumbra/core/transaction/v1/transaction.proto | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto index 8ea68e85b9..db2910a2ad 100644 --- a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto +++ b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto @@ -54,6 +54,14 @@ message TransactionParameters { component.fee.v1.Fee fee = 3; } +message TransactionSummary { + message Effects { + keys.v1.AddressView address = 1; + asset.v1.Balance balance = 2; + } + repeated Effects effects = 1; +} + // Detection data used by a detection server performing Fuzzy Message Detection. message DetectionData { // A list of clues for use with Fuzzy Message Detection. From 06bf64171c22539cc457eff27ab8d8cc0e9faf0c Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 09:38:55 -0800 Subject: [PATCH 04/30] regenerate protos for TransactionSummary message --- .../src/gen/penumbra.core.transaction.v1.rs | 33 +++ .../gen/penumbra.core.transaction.v1.serde.rs | 207 ++++++++++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 644756 -> 645258 bytes 3 files changed, 240 insertions(+) diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.rs index b981d684dd..f7b5180c27 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.rs @@ -71,6 +71,39 @@ impl ::prost::Name for TransactionParameters { ::prost::alloc::format!("penumbra.core.transaction.v1.{}", Self::NAME) } } +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionSummary { + #[prost(message, repeated, tag = "1")] + pub effects: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `TransactionSummary`. +pub mod transaction_summary { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Effects { + #[prost(message, optional, tag = "1")] + pub address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub balance: ::core::option::Option, + } + impl ::prost::Name for Effects { + const NAME: &'static str = "Effects"; + const PACKAGE: &'static str = "penumbra.core.transaction.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.core.transaction.v1.TransactionSummary.{}", Self::NAME + ) + } + } +} +impl ::prost::Name for TransactionSummary { + const NAME: &'static str = "TransactionSummary"; + const PACKAGE: &'static str = "penumbra.core.transaction.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.transaction.v1.{}", Self::NAME) + } +} /// Detection data used by a detection server performing Fuzzy Message Detection. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs index 63d055c45a..bd6767749d 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs @@ -4062,6 +4062,213 @@ impl<'de> serde::Deserialize<'de> for TransactionPlan { deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionPlan", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for TransactionSummary { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.effects.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.transaction.v1.TransactionSummary", len)?; + if !self.effects.is_empty() { + struct_ser.serialize_field("effects", &self.effects)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TransactionSummary { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "effects", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Effects, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "effects" => Ok(GeneratedField::Effects), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TransactionSummary; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.transaction.v1.TransactionSummary") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut effects__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Effects => { + if effects__.is_some() { + return Err(serde::de::Error::duplicate_field("effects")); + } + effects__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TransactionSummary { + effects: effects__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionSummary", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for transaction_summary::Effects { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.address.is_some() { + len += 1; + } + if self.balance.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.transaction.v1.TransactionSummary.Effects", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if let Some(v) = self.balance.as_ref() { + struct_ser.serialize_field("balance", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for transaction_summary::Effects { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "balance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Balance, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "address" => Ok(GeneratedField::Address), + "balance" => Ok(GeneratedField::Balance), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = transaction_summary::Effects; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.transaction.v1.TransactionSummary.Effects") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut balance__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = map_.next_value()?; + } + GeneratedField::Balance => { + if balance__.is_some() { + return Err(serde::de::Error::duplicate_field("balance")); + } + balance__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(transaction_summary::Effects { + address: address__, + balance: balance__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionSummary.Effects", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for TransactionView { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 70fef211723356f21bcabb49bfa544ba3da13e1a..7dc95843058b70fad3d158c5b83eb5205d12196c 100644 GIT binary patch delta 9435 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 GDgOtan?{lV delta 8951 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 Uf0g{p@~<2JatCVnRIMHLe<^wO>Hq)$ From 9add44d910df2577cbbb051e16fc2b60f90cc033 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 14:10:27 -0800 Subject: [PATCH 05/30] add corresponding domain types to Balance --- crates/core/asset/src/balance.rs | 38 +++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index d41be59b55..829f0da801 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -3,7 +3,6 @@ use ark_r1cs_std::uint8::UInt8; use ark_relations::r1cs::SynthesisError; use penumbra_num::{Amount, AmountVar}; use serde::{Deserialize, Serialize}; -use serde_with::serde_as; use std::{ collections::{btree_map, BTreeMap}, fmt::{self, Debug, Formatter}, @@ -29,17 +28,50 @@ use decaf377::{r1cs::ElementVar, Fq, Fr}; use imbalance::Imbalance; use self::commitment::BalanceCommitmentVar; +use penumbra_proto::{penumbra::core::asset::v1 as pb, DomainType}; /// A `Balance` is a "vector of [`Value`]s", where some values may be required, while others may be /// provided. For a transaction to be valid, its balance must be zero. -#[serde_as] #[derive(Clone, Eq, Default, Serialize, Deserialize)] +#[serde(try_from = "pb::Balance", into = "pb::Balance")] pub struct Balance { negated: bool, - #[serde_as(as = "Vec<(_, _)>")] balance: BTreeMap>, } +/* Protobuf impls */ +impl DomainType for Balance { + type Proto = pb::Balance; +} + +impl TryFrom for Balance { + type Error = anyhow::Error; + + fn try_from(v: pb::Balance) -> Result { + let mut balance_map = BTreeMap::new(); + + for imbalance_value in v.balance.into_iter().map(TryInto::try_into) { + let value: Value = imbalance_value?; + let amount = NonZeroU128::new(value.amount.into()) + .ok_or_else(|| anyhow::anyhow!("amount must be non-zero"))?; + + let imbalance = Imbalance::Provided(amount); // todo: fix this placeholder + balance_map.insert(value.asset_id, imbalance); + } + + Ok(Self { + negated: v.negated, + balance: balance_map, + }) + } +} + +impl From for pb::Balance { + fn from(v: Balance) -> Self { + Self::try_from(v).unwrap() + } +} + impl Debug for Balance { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Balance") From abd9a297db69ea5b6061f3d2140b3694ba619994 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 18:49:28 -0800 Subject: [PATCH 06/30] modify message and regenerate protos for Balance --- .../src/gen/penumbra.core.transaction.v1.rs | 39 ++- .../gen/penumbra.core.transaction.v1.serde.rs | 224 +++++++++--------- .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 645258 -> 645346 bytes .../core/transaction/v1/transaction.proto | 11 +- 4 files changed, 137 insertions(+), 137 deletions(-) diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.rs index f7b5180c27..4a64e76538 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.rs @@ -71,32 +71,29 @@ impl ::prost::Name for TransactionParameters { ::prost::alloc::format!("penumbra.core.transaction.v1.{}", Self::NAME) } } +/// Represents an individual effect of a transaction. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct TransactionSummary { - #[prost(message, repeated, tag = "1")] - pub effects: ::prost::alloc::vec::Vec, +pub struct Effects { + #[prost(message, optional, tag = "1")] + pub address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub balance: ::core::option::Option, } -/// Nested message and enum types in `TransactionSummary`. -pub mod transaction_summary { - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Message)] - pub struct Effects { - #[prost(message, optional, tag = "1")] - pub address: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub balance: ::core::option::Option, - } - impl ::prost::Name for Effects { - const NAME: &'static str = "Effects"; - const PACKAGE: &'static str = "penumbra.core.transaction.v1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!( - "penumbra.core.transaction.v1.TransactionSummary.{}", Self::NAME - ) - } +impl ::prost::Name for Effects { + const NAME: &'static str = "Effects"; + const PACKAGE: &'static str = "penumbra.core.transaction.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.transaction.v1.{}", Self::NAME) } } +/// Represents a transaction summary containing multiple effects. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionSummary { + #[prost(message, repeated, tag = "1")] + pub effects: ::prost::alloc::vec::Vec, +} impl ::prost::Name for TransactionSummary { const NAME: &'static str = "TransactionSummary"; const PACKAGE: &'static str = "penumbra.core.transaction.v1"; diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs index bd6767749d..acec359e80 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs @@ -1754,6 +1754,118 @@ impl<'de> serde::Deserialize<'de> for DetectionDataPlan { deserializer.deserialize_struct("penumbra.core.transaction.v1.DetectionDataPlan", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for Effects { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.address.is_some() { + len += 1; + } + if self.balance.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.transaction.v1.Effects", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if let Some(v) = self.balance.as_ref() { + struct_ser.serialize_field("balance", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Effects { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "balance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Balance, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "address" => Ok(GeneratedField::Address), + "balance" => Ok(GeneratedField::Balance), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Effects; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.transaction.v1.Effects") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut balance__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = map_.next_value()?; + } + GeneratedField::Balance => { + if balance__.is_some() { + return Err(serde::de::Error::duplicate_field("balance")); + } + balance__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(Effects { + address: address__, + balance: balance__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.transaction.v1.Effects", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for MemoCiphertext { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -4157,118 +4269,6 @@ impl<'de> serde::Deserialize<'de> for TransactionSummary { deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionSummary", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for transaction_summary::Effects { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.address.is_some() { - len += 1; - } - if self.balance.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("penumbra.core.transaction.v1.TransactionSummary.Effects", len)?; - if let Some(v) = self.address.as_ref() { - struct_ser.serialize_field("address", v)?; - } - if let Some(v) = self.balance.as_ref() { - struct_ser.serialize_field("balance", v)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for transaction_summary::Effects { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "address", - "balance", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Address, - Balance, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "address" => Ok(GeneratedField::Address), - "balance" => Ok(GeneratedField::Balance), - _ => Ok(GeneratedField::__SkipField__), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = transaction_summary::Effects; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct penumbra.core.transaction.v1.TransactionSummary.Effects") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut address__ = None; - let mut balance__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Address => { - if address__.is_some() { - return Err(serde::de::Error::duplicate_field("address")); - } - address__ = map_.next_value()?; - } - GeneratedField::Balance => { - if balance__.is_some() { - return Err(serde::de::Error::duplicate_field("balance")); - } - balance__ = map_.next_value()?; - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(transaction_summary::Effects { - address: address__, - balance: balance__, - }) - } - } - deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionSummary.Effects", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for TransactionView { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 7dc95843058b70fad3d158c5b83eb5205d12196c..8188c005e9ca8772feac4ab4cfb71060bc53e03b 100644 GIT binary patch delta 9387 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 delta 9327 zcmZ9Sd5~4rwZ`vWXZPjyxzOA`J%G*N1(dlN8$=Kl8ypY^Pyq)t3>_trhA;>Q@q)HO zVnszFCr?dMDh{!ni%6+?>eE-47ekCGVxdOlC3%?m>b=Cg#F&ui`>nJ0zPHLhsJh=- z`&(=6;q3LT)BO6G5#M}f#MU0uBR$bx`}P$3!{3@Xz2Kv|@%!qh+Rb&N{>Q}0#cP*# zuUoo&{mRwd7j0OzYU$eRlLc|Ft}lPNYx(+h{Y^ONsy7H^&(lgi&p2 z&kTFZzAv3(e>blF(d*-0NnI{?>%RG4weRFBaV$1U?6lmeCXVCCMka}-724w7v9(c@ zOYF&VN(8D?gze`sQ5+DCW)_oxa5J+>XT%&Nhm)Cu2FI5( zZ=2X?R&I9st#x)$Vi`88&=SwayH)R<=_l*#>8J7==bhd;2XLc z0o{)tBL798v=zs`1w|!i;EOmnETszU5bVbj#CkCXp;bQ}WcJBBI zo+e2&&kpk@fi}+`7tlg@^V$Xn2ydR>g*pM8XUBO{1n)c>2I@&V@Ay%{rX$C;$CI*Y zvZC|TqZ{lv=4N5dw_!HA4fDsG=2;xgX#N?qeUf!aw7_F%i1NhP0IL-aguE*Uf3JI&+fl9{LZ=)IC? zp`Gnby`U|$VJ&+>TiAYTut8fmtJBj8NwmlYQvuo{8`h*?l1j8_^fb>az*{t9rsvfs z(c&tv9^T?Aubyy=tF(G(i>tIai7vJ0d5|pd zbcLPLRxDq*dfiIBZPtY)D&ph{JH*q9IJsiz_<(S7#iS0Oa_=NsVo&m>-q4oVp#ec# zGAx|S-q4nGOodj_L*FNfuCz1#$Ug9{te)9E@U9#leD;BN<@8g0xP~OU$}aY_2548= zkpV%wYE)SD2548YA$kMUNdr4_)$B#tHB?sts&&r7cu=+ueUoUJ9oJSWkKE8bYDL$z zT~{yF)d-8xmoUq$yLRfNFJYFIL(slBS~jNLXIDz1<#v*fS%S8_x`ria%ZFx%XC4Wa z;4L3N(eoOUXhn4~8sV+5?y{+qMtCbmgvDrtwt~fQc_&G9b-ML-+fs3}OkiGJeKQl7 zSC0=Xkifk9%(H#KrX;$?p6e5Bf_9A!+uH=~nhA4)4cawl&-JwCBwATLXwA@8x`Q^1 z88yROSv_gZ@K(;9=M!y7qHEK}uiN8z^;@7_YbSgEEwHZbm>&?VYcF2tqqQc{s%o@W zXsfDoYlXIId^pyv&{h#li$tCDOQLSO$b0Mut-Bhn-y(S3)o}gbbyvgnPog#U6wm7q zZ%y^`?GJ6uiBp0N+L~!I;s*^^em<$YF89#x&G!2hy-2Pr49?QX>#e>~90kDTx6zjq z5Y9Ii-OUSvw($h_!h&$Vk#eGQ$?MI&N!)<~;AB(PiQe;@iUYHpu$g2~v}sU#fOy$7 zVPd>K^TI{HIh}EXU2|$)1SgxTPW0B_Ty!@fHo|Qlho z(6g482tQk@ezXc~DY`0v4L@6scNZl{sT7?lnICAC^r9`c-TR>+N0q7{tqYavq_i$n z28DIfx=J%;FxNL>IHhX#nB9y4rIfcWV#W5=OQ*cxO~{#GO3Fp)E>P_nn0Sfxu| z%^U!`-EbN9MJi<&?>uM@4=TFzJZTvep6KTDpse!A`Cz~f!>JEG*mQl+ul67t(SKC! z0WNkJimqy!y!1-!ZAMONn_8aLeSLW?ciVMKyVsYyHm&bUzf!TI&SVISz?iTIR5@Vr zct2~(9I!kwK-}MExW;dlY!ytBzstzo={ptsc_kll4DDTpBjYAubP;wL+wRi?Ifp-XLvu{eDx)?->uCTj0+xw`SKteF>v(NRUZZ) z->uEJKC$07vfNLhp7{d9e32EvV%Si01#r@mFc?qYH(y#7-;~W);>7Mr=ih8^p#dz8_e_)qaKf1Z;%JXKM{A7OUP=BDlY7hVOk$S`w%GvJp(j|M;h_(oI z7KB$|bAXI})U=f2iZ|o-Q8P?cv1K&Y9~f~dfZzh%(FJDK@@Vf$7wHFXk;E2~{NpC~ zl+OrJW*;{NRln92G@~bsxDx@w>l3CT12aEh20vj24G$2vPnc19hbkB2lQphp1RUbW zYDQ0*p@FIyJ*m|3tWkZEf7aw)Fs5xtdEv^gjpfx}E-x)#*wwvZ)v~oq%jvvZY|^B= z^{grMi^meIUK8D$e(e@JvV-4*+H0gG3k784UeoH<0!XO6X6UeZc}B$eoJqG`YcHFo z>sp^J-E&6VS^={GW2Sm%n&@-p1Hq-7Y$V-<-6U!bt-X;w4jbQv*VL z$+Ws_8c2wj%!$L}5t$#1ucZ68+hHAXHmg^R^viMss?GEt7a+t}%*09YR-IL>vwAHZ zw8P#2Q)l&>k-mPY&gwN&8XO?Z*9;BOZH@`tpB~s@x5K0q+WocMsfECzJI`2ksOi@~ zzL@nWCi$P3-2bIJzF{wzgvho(F@=PCz}g}WWBv^zfAZx8jQI^yXo^o(v#!>GwEmlR zN`>D*xu=3iW3OG2*DK z&zr`M@J3xBU}aFiaNjh8-L4d~jek>j#c}cUbAvQ?De4*l!&qITpPOOr!I}+5y*Mgo z;Vom&@J1AZ!Z6kjf%}#jcS8((IX=ptMWHBkZhs+myPrkXh!CGE1hb;6i zJY?uTy%kr74DF}Og2MHVq5pJ7tyI9V0YZJp(1Cg@)OSqQ@#sEN1Ku_ClFnFPfeQ{B zt@p^ghIZ1M;l69=CtX-B{r3#Y+Zpwq2ZlqTukCvVq3^9w-!q7BM=d7y{o3)Oja0~G zP|1#_p7Hk$ouuOy;l6KZB^|eSVhWb2-K4idJ*+y!QTrtJ149Sp zjI@&qT}del{iLmYVCX2l8SV#$F3NEm5_`nZQ#vC(rNZzsHNZV$=qkMx>JeRJ580q2 zhQ3m3cQI=#!St2RbKk`NTGbnaE12mc6}u}Li0fY)I!Wh?P7*jq6};|WDY3t)y)g8W z3f--W5$bPLL^vwVB#>@$a7JxR?0*@14*hKB>Q$>Ybgx{0eL213b{q2&&|Io999_Cg zAWfokM0*LOOLRFTi9Kp)6P=OnQeoIU+DjmfqPIdlYUmUlwJEV5){YPzrou`%L-d&7 z>KST+`=Op8mw$6&e{byNDh4OL2g`)A)0bx67Jb}|L309o_-YmY% zC(@GGk3u5MpYBs}m_O|&kVevZZh`wz?J&0{_P!6WR7dQ@;3u*%wSNZ|o|kt#{g|NrI+SjZOvt@$^SSkMEM8D+N}MXaB^0Z0Jj! zk-k*n2?0X=*wC4JE7Xq-ttmCs_D=GjnB3-32# zo^!~yx0aJn(_41hIg|NKh)<0S$b14B`KcM`u6-aOJ~gAq#5ZI_jK|V{?zEd`_tyT@ z9t)KZh@WGo=yC(6ie_nm#EyX}RhNpF@r1SvO%7!8`-&UD>wJFAo5On+yH0VKvw@$v@ZnuYrKDcM0mCGh?$0cc(X|_O!_j znFP99q$yiwAjG?++1XO_mGAxS^Vo`wZ5&*I&_e#-a2ZVaBG!Kpk=&Zp+xmQqV zbZ25qN&W%Jy_r7$9lN`tm*)fYgqhGPsnB{*1OC|l<@s!w#6HGw3U{@xAaGw${n1FDf5-*ra`jOyi z@q!5i73lO55bD$7^%4;3)8h3~NwwB9;uR`LwHDaj31GO-h*zjUxX%a*)$JBq>sj$y zt0bzmfUZsgp*}0VP6DAmD_(1r1g*7Kd}RPZ1yUJQYk}eJ6<->FaQ6zO!G%R@Jy$!M zsQiFZXtz_%aBB3Jm8b>!zw4V{IP+yce zuBex!FFX69oPBvF(iEz>LC(1>u4Fl(v|dh!e$Q_1D2eK&r3{86oJEsf{o>VKNsw8ui&u9b zs6eOgfKXo-ukL_QUl*_LN`k2RaqZBmrUH%$r&dMPk87t^Mb(e>)Vc{Es@@R&V?ayx zNv9;LtKO(xv8t=y5Wdm78&`GJ8^Q-Tw{pm;pVlI)kOGGJs*w7rcr{lN6;eN~y)Fo; z1LDG~oCCr=Ajr9?l#j+F{|m{zlXl*3Ctj&L`wJ(-5Pw-;6t#N3!o1_<|{B<@2skRS(T;GnFhZ`51;?erJ-+bNUz z&4v25NH0&+V)3>V`UD8^Z7DUz!?PSH7KhU557?m{jUo^ai4^=ub$UqZ`vnN`khBfR z1Y(lpeVhoN}iS^|7Xel)k_|ISNIYU z`xj|-+8>D5zsQJ@F@OEDwng*#1l`~VYeqnK4lrg`pAiu5Cn2NOB>y+b-5NO&rtjb1 z^dRVWs8;E#g^^WTO45TY$Fkd!O-m=*y2pq)J+YaS%E2T+blllwDAHiGEK zRI&yi4ETqZuHZto5)WEx53&(_&8&{V#Xn@I{znp9AuNA~_*`UL0JkVVs7&B|pok<|no~XHHS$@sh h)$3PJj{jxq_LYxX{?+j>&%a*$E9_hOX#MpQ{vS^6CS?Es diff --git a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto index db2910a2ad..4169ac6d01 100644 --- a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto +++ b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto @@ -54,11 +54,14 @@ message TransactionParameters { component.fee.v1.Fee fee = 3; } +// Represents an individual effect of a transaction. +message Effects { + keys.v1.AddressView address = 1; + asset.v1.Balance balance = 2; +} + +// Represents a transaction summary containing multiple effects. message TransactionSummary { - message Effects { - keys.v1.AddressView address = 1; - asset.v1.Balance balance = 2; - } repeated Effects effects = 1; } From 8c12d8e3acc468856a63e6c84faa304afd01391b Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 18:50:34 -0800 Subject: [PATCH 07/30] stub fallible conversion (proto message to domain type) --- crates/core/asset/src/balance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index 829f0da801..22891fc327 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -67,8 +67,8 @@ impl TryFrom for Balance { } impl From for pb::Balance { - fn from(v: Balance) -> Self { - Self::try_from(v).unwrap() + fn from(_v: Balance) -> Self { + todo!() // todo: implement fallible conversion } } From 8382e6a8f0111bc65336762c6c88a70d4ce7b106 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 18:56:52 -0800 Subject: [PATCH 08/30] add corresponding domain types to TransactionSummary and Effects --- crates/core/transaction/src/transaction.rs | 74 +++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index f3ff17e8e4..8f1ff002c2 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -7,6 +7,7 @@ use anyhow::{Context, Error}; use ark_ff::Zero; use decaf377::Fr; use decaf377_rdsa::{Binding, Signature, VerificationKey, VerificationKeyBytes}; +use penumbra_asset::Balance; use penumbra_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend}; use penumbra_dex::{ lp::action::{PositionClose, PositionOpen}, @@ -14,7 +15,7 @@ use penumbra_dex::{ }; use penumbra_governance::{DelegatorVote, ProposalSubmit, ProposalWithdraw, ValidatorVote}; use penumbra_ibc::IbcRelay; -use penumbra_keys::{FullViewingKey, PayloadKey}; +use penumbra_keys::{AddressView, FullViewingKey, PayloadKey}; use penumbra_proto::{ core::transaction::v1::{self as pbt}, DomainType, Message, @@ -44,6 +45,21 @@ pub struct TransactionBody { pub memo: Option, } +/// Represents a transaction summary containing multiple effects. +#[derive(Clone, Default, Serialize, Deserialize)] +#[serde(try_from = "pbt::TransactionSummary", into = "pbt::TransactionSummary")] +pub struct TransactionSummary { + effects: Vec, +} + +/// Represents an individual effect of a transaction. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(try_from = "pbt::Effects", into = "pbt::Effects")] +pub struct Effects { + address: AddressView, + balance: Balance, +} + impl EffectingData for TransactionBody { fn effect_hash(&self) -> EffectHash { let mut state = blake2b_simd::Params::new() @@ -591,6 +607,62 @@ impl Transaction { } } +impl DomainType for TransactionSummary { + type Proto = pbt::TransactionSummary; +} + +impl From for pbt::TransactionSummary { + fn from(pbt: TransactionSummary) -> Self { + pbt::TransactionSummary { + effects: pbt.effects.into_iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for TransactionSummary { + type Error = anyhow::Error; + + fn try_from(pbt: pbt::TransactionSummary) -> Result { + let effects = pbt + .effects + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + + Ok(Self { effects }) + } +} + +impl DomainType for Effects { + type Proto = pbt::Effects; +} + +impl From for pbt::Effects { + fn from(effect: Effects) -> Self { + pbt::Effects { + address: Some(effect.address.into()), + balance: Some(effect.balance.into()), + } + } +} + +impl TryFrom for Effects { + type Error = anyhow::Error; + + fn try_from(pbt: pbt::Effects) -> Result { + Ok(Self { + address: pbt + .address + .ok_or_else(|| anyhow::anyhow!("missing address field"))? + .try_into()?, + balance: pbt + .balance + .ok_or_else(|| anyhow::anyhow!("missing balance field"))? + .try_into()?, + }) + } +} + impl DomainType for TransactionBody { type Proto = pbt::TransactionBody; } From ff86c9a2fd502796156c244c66458fcada046603 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 24 Nov 2024 19:52:03 -0800 Subject: [PATCH 09/30] stub summary method in TransactionView --- crates/core/asset/src/balance.rs | 2 +- crates/core/transaction/src/transaction.rs | 6 +-- crates/core/transaction/src/view.rs | 49 +++++++++++++++++++++- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index 22891fc327..04d2ae4ce5 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -68,7 +68,7 @@ impl TryFrom for Balance { impl From for pb::Balance { fn from(_v: Balance) -> Self { - todo!() // todo: implement fallible conversion + todo!() // todo: implement fallible conversion } } diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index 8f1ff002c2..a4e5e9055c 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -49,15 +49,15 @@ pub struct TransactionBody { #[derive(Clone, Default, Serialize, Deserialize)] #[serde(try_from = "pbt::TransactionSummary", into = "pbt::TransactionSummary")] pub struct TransactionSummary { - effects: Vec, + pub effects: Vec, } /// Represents an individual effect of a transaction. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(try_from = "pbt::Effects", into = "pbt::Effects")] pub struct Effects { - address: AddressView, - balance: Balance, + pub address: AddressView, + pub balance: Balance, } impl EffectingData for TransactionBody { diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index 91563b136b..b14ff28d07 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -1,8 +1,10 @@ use anyhow::Context; use decaf377_rdsa::{Binding, Signature}; +use penumbra_asset::Balance; use penumbra_keys::AddressView; use penumbra_proto::{core::transaction::v1 as pbt, DomainType}; +use penumbra_shielded_pool::{OutputView, SpendView}; use serde::{Deserialize, Serialize}; pub mod action_view; @@ -13,8 +15,9 @@ use penumbra_tct as tct; pub use transaction_perspective::TransactionPerspective; use crate::{ - memo::MemoCiphertext, Action, DetectionData, Transaction, TransactionBody, - TransactionParameters, + memo::MemoCiphertext, + transaction::{Effects, TransactionSummary}, + Action, DetectionData, Transaction, TransactionBody, TransactionParameters, }; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -94,6 +97,48 @@ impl TransactionView { pub fn action_views(&self) -> impl Iterator { self.body_view.action_views.iter() } + + pub fn summary(&self) -> TransactionSummary { + let mut effects = Vec::new(); + + for action_view in &self.body_view.action_views { + match action_view { + ActionView::Spend(spend_view) => match spend_view { + SpendView::Visible { spend: _, note } => { + let value = note.value.value(); + let balance = Balance::from(value); + let address = AddressView::Opaque { + address: note.address(), + }; + + effects.push(Effects { address, balance }); + } + SpendView::Opaque { spend: _ } => continue, + }, + ActionView::Output(output_view) => match output_view { + OutputView::Visible { + output: _, + note, + payload_key: _, + } => { + let value = note.value.value(); + let balance = -Balance::from(value); + let address = AddressView::Opaque { + address: note.address(), + }; + + effects.push(Effects { address, balance }); + } + OutputView::Opaque { output: _ } => continue, + }, + ActionView::Swap(_) => todo!(), + ActionView::SwapClaim(_) => todo!(), + _ => {} + } + } + + TransactionSummary { effects } + } } impl DomainType for TransactionView { From 425e54977c3ad8207251851ef55692c7a93c4f1d Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 30 Nov 2024 12:55:41 -0800 Subject: [PATCH 10/30] proto(TransactionSummary): combine into a sub-message --- .../src/gen/penumbra.core.transaction.v1.rs | 39 +-- .../gen/penumbra.core.transaction.v1.serde.rs | 224 +++++++++--------- .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 645346 -> 645381 bytes .../core/transaction/v1/transaction.proto | 11 +- 4 files changed, 139 insertions(+), 135 deletions(-) diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.rs index 4a64e76538..0cf2173a8b 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.rs @@ -71,28 +71,33 @@ impl ::prost::Name for TransactionParameters { ::prost::alloc::format!("penumbra.core.transaction.v1.{}", Self::NAME) } } -/// Represents an individual effect of a transaction. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Effects { - #[prost(message, optional, tag = "1")] - pub address: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub balance: ::core::option::Option, -} -impl ::prost::Name for Effects { - const NAME: &'static str = "Effects"; - const PACKAGE: &'static str = "penumbra.core.transaction.v1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!("penumbra.core.transaction.v1.{}", Self::NAME) - } -} /// Represents a transaction summary containing multiple effects. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransactionSummary { #[prost(message, repeated, tag = "1")] - pub effects: ::prost::alloc::vec::Vec, + pub effects: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `TransactionSummary`. +pub mod transaction_summary { + /// Represents an individual effect of a transaction. + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Effects { + #[prost(message, optional, tag = "1")] + pub address: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub balance: ::core::option::Option, + } + impl ::prost::Name for Effects { + const NAME: &'static str = "Effects"; + const PACKAGE: &'static str = "penumbra.core.transaction.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.core.transaction.v1.TransactionSummary.{}", Self::NAME + ) + } + } } impl ::prost::Name for TransactionSummary { const NAME: &'static str = "TransactionSummary"; diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs index acec359e80..bd6767749d 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs @@ -1754,118 +1754,6 @@ impl<'de> serde::Deserialize<'de> for DetectionDataPlan { deserializer.deserialize_struct("penumbra.core.transaction.v1.DetectionDataPlan", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for Effects { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.address.is_some() { - len += 1; - } - if self.balance.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("penumbra.core.transaction.v1.Effects", len)?; - if let Some(v) = self.address.as_ref() { - struct_ser.serialize_field("address", v)?; - } - if let Some(v) = self.balance.as_ref() { - struct_ser.serialize_field("balance", v)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for Effects { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "address", - "balance", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Address, - Balance, - __SkipField__, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "address" => Ok(GeneratedField::Address), - "balance" => Ok(GeneratedField::Balance), - _ => Ok(GeneratedField::__SkipField__), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = Effects; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct penumbra.core.transaction.v1.Effects") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut address__ = None; - let mut balance__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Address => { - if address__.is_some() { - return Err(serde::de::Error::duplicate_field("address")); - } - address__ = map_.next_value()?; - } - GeneratedField::Balance => { - if balance__.is_some() { - return Err(serde::de::Error::duplicate_field("balance")); - } - balance__ = map_.next_value()?; - } - GeneratedField::__SkipField__ => { - let _ = map_.next_value::()?; - } - } - } - Ok(Effects { - address: address__, - balance: balance__, - }) - } - } - deserializer.deserialize_struct("penumbra.core.transaction.v1.Effects", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for MemoCiphertext { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -4269,6 +4157,118 @@ impl<'de> serde::Deserialize<'de> for TransactionSummary { deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionSummary", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for transaction_summary::Effects { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.address.is_some() { + len += 1; + } + if self.balance.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.transaction.v1.TransactionSummary.Effects", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if let Some(v) = self.balance.as_ref() { + struct_ser.serialize_field("balance", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for transaction_summary::Effects { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "balance", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Balance, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "address" => Ok(GeneratedField::Address), + "balance" => Ok(GeneratedField::Balance), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = transaction_summary::Effects; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.transaction.v1.TransactionSummary.Effects") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut balance__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = map_.next_value()?; + } + GeneratedField::Balance => { + if balance__.is_some() { + return Err(serde::de::Error::duplicate_field("balance")); + } + balance__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(transaction_summary::Effects { + address: address__, + balance: balance__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.transaction.v1.TransactionSummary.Effects", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for TransactionView { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 8188c005e9ca8772feac4ab4cfb71060bc53e03b..aa25bace9f2b5974ef6d891407403a49413ba6cd 100644 GIT binary patch delta 9329 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 delta 9431 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 diff --git a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto index 4169ac6d01..beffedd441 100644 --- a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto +++ b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto @@ -54,14 +54,13 @@ message TransactionParameters { component.fee.v1.Fee fee = 3; } -// Represents an individual effect of a transaction. -message Effects { - keys.v1.AddressView address = 1; - asset.v1.Balance balance = 2; -} - // Represents a transaction summary containing multiple effects. message TransactionSummary { + // Represents an individual effect of a transaction. + message Effects { + keys.v1.AddressView address = 1; + asset.v1.Balance balance = 2; + } repeated Effects effects = 1; } From 95aacc01503f0fb8b4aef921128bfecde1e81d8e Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 30 Nov 2024 12:56:51 -0800 Subject: [PATCH 11/30] domain type (TransactionSummary): combine into a sub-message --- crates/core/transaction/src/transaction.rs | 61 +++++++++------------- crates/core/transaction/src/view.rs | 6 +-- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index a4e5e9055c..7c9036c9b5 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -49,13 +49,12 @@ pub struct TransactionBody { #[derive(Clone, Default, Serialize, Deserialize)] #[serde(try_from = "pbt::TransactionSummary", into = "pbt::TransactionSummary")] pub struct TransactionSummary { - pub effects: Vec, + pub effects: Vec, } /// Represents an individual effect of a transaction. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(try_from = "pbt::Effects", into = "pbt::Effects")] -pub struct Effects { +pub struct TransactionEffect { pub address: AddressView, pub balance: Balance, } @@ -612,9 +611,16 @@ impl DomainType for TransactionSummary { } impl From for pbt::TransactionSummary { - fn from(pbt: TransactionSummary) -> Self { + fn from(summary: TransactionSummary) -> Self { pbt::TransactionSummary { - effects: pbt.effects.into_iter().map(Into::into).collect(), + effects: summary + .effects + .into_iter() + .map(|effect| pbt::transaction_summary::Effects { + address: Some(effect.address.into()), + balance: Some(effect.balance.into()), + }) + .collect(), } } } @@ -626,43 +632,24 @@ impl TryFrom for TransactionSummary { let effects = pbt .effects .into_iter() - .map(TryInto::try_into) - .collect::, _>>()?; + .map(|effect| { + Ok(TransactionEffect { + address: effect + .address + .ok_or_else(|| anyhow::anyhow!("missing address field"))? + .try_into()?, + balance: effect + .balance + .ok_or_else(|| anyhow::anyhow!("missing balance field"))? + .try_into()?, + }) + }) + .collect::, anyhow::Error>>()?; Ok(Self { effects }) } } -impl DomainType for Effects { - type Proto = pbt::Effects; -} - -impl From for pbt::Effects { - fn from(effect: Effects) -> Self { - pbt::Effects { - address: Some(effect.address.into()), - balance: Some(effect.balance.into()), - } - } -} - -impl TryFrom for Effects { - type Error = anyhow::Error; - - fn try_from(pbt: pbt::Effects) -> Result { - Ok(Self { - address: pbt - .address - .ok_or_else(|| anyhow::anyhow!("missing address field"))? - .try_into()?, - balance: pbt - .balance - .ok_or_else(|| anyhow::anyhow!("missing balance field"))? - .try_into()?, - }) - } -} - impl DomainType for TransactionBody { type Proto = pbt::TransactionBody; } diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index b14ff28d07..d1f8abe08b 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -16,7 +16,7 @@ pub use transaction_perspective::TransactionPerspective; use crate::{ memo::MemoCiphertext, - transaction::{Effects, TransactionSummary}, + transaction::{TransactionEffect, TransactionSummary}, Action, DetectionData, Transaction, TransactionBody, TransactionParameters, }; @@ -111,7 +111,7 @@ impl TransactionView { address: note.address(), }; - effects.push(Effects { address, balance }); + effects.push(TransactionEffect { address, balance }); } SpendView::Opaque { spend: _ } => continue, }, @@ -127,7 +127,7 @@ impl TransactionView { address: note.address(), }; - effects.push(Effects { address, balance }); + effects.push(TransactionEffect { address, balance }); } OutputView::Opaque { output: _ } => continue, }, From 78741f85774c17438efd785365dcbb86f068c57e Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 30 Nov 2024 13:11:06 -0800 Subject: [PATCH 12/30] proto(Balance): encapsulate list of value pairs --- .../proto/src/gen/penumbra.core.asset.v1.rs | 27 +++- .../src/gen/penumbra.core.asset.v1.serde.rs | 141 +++++++++++++++--- .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 645381 -> 645527 bytes .../penumbra/core/asset/v1/asset.proto | 11 +- 4 files changed, 146 insertions(+), 33 deletions(-) diff --git a/crates/proto/src/gen/penumbra.core.asset.v1.rs b/crates/proto/src/gen/penumbra.core.asset.v1.rs index a062c80991..7865016a58 100644 --- a/crates/proto/src/gen/penumbra.core.asset.v1.rs +++ b/crates/proto/src/gen/penumbra.core.asset.v1.rs @@ -144,12 +144,27 @@ impl ::prost::Name for Value { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Balance { - /// Indicates if the balance is negated. - #[prost(bool, tag = "1")] - pub negated: bool, - /// Represents the vector of 'Values' in the balance. - #[prost(message, repeated, tag = "2")] - pub balance: ::prost::alloc::vec::Vec, + /// Represents the vector of 'Value's in the balance. + #[prost(message, repeated, tag = "1")] + pub values: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `Balance`. +pub mod balance { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct SignedValue { + #[prost(message, optional, tag = "1")] + pub value: ::core::option::Option, + #[prost(bool, tag = "2")] + pub negated: bool, + } + impl ::prost::Name for SignedValue { + const NAME: &'static str = "SignedValue"; + const PACKAGE: &'static str = "penumbra.core.asset.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.core.asset.v1.Balance.{}", Self::NAME) + } + } } impl ::prost::Name for Balance { const NAME: &'static str = "Balance"; diff --git a/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs b/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs index 40ed473489..d760c768a6 100644 --- a/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.asset.v1.serde.rs @@ -400,37 +400,132 @@ impl serde::Serialize for Balance { { use serde::ser::SerializeStruct; let mut len = 0; - if self.negated { + if !self.values.is_empty() { len += 1; } - if !self.balance.is_empty() { + let mut struct_ser = serializer.serialize_struct("penumbra.core.asset.v1.Balance", len)?; + if !self.values.is_empty() { + struct_ser.serialize_field("values", &self.values)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for Balance { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "values", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Values, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "values" => Ok(GeneratedField::Values), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = Balance; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.asset.v1.Balance") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut values__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Values => { + if values__.is_some() { + return Err(serde::de::Error::duplicate_field("values")); + } + values__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(Balance { + values: values__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.asset.v1.Balance", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for balance::SignedValue { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.value.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("penumbra.core.asset.v1.Balance", len)?; if self.negated { - struct_ser.serialize_field("negated", &self.negated)?; + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.asset.v1.Balance.SignedValue", len)?; + if let Some(v) = self.value.as_ref() { + struct_ser.serialize_field("value", v)?; } - if !self.balance.is_empty() { - struct_ser.serialize_field("balance", &self.balance)?; + if self.negated { + struct_ser.serialize_field("negated", &self.negated)?; } struct_ser.end() } } -impl<'de> serde::Deserialize<'de> for Balance { +impl<'de> serde::Deserialize<'de> for balance::SignedValue { #[allow(deprecated)] fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ + "value", "negated", - "balance", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { + Value, Negated, - Balance, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -453,8 +548,8 @@ impl<'de> serde::Deserialize<'de> for Balance { E: serde::de::Error, { match value { + "value" => Ok(GeneratedField::Value), "negated" => Ok(GeneratedField::Negated), - "balance" => Ok(GeneratedField::Balance), _ => Ok(GeneratedField::__SkipField__), } } @@ -464,44 +559,44 @@ impl<'de> serde::Deserialize<'de> for Balance { } struct GeneratedVisitor; impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = Balance; + type Value = balance::SignedValue; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct penumbra.core.asset.v1.Balance") + formatter.write_str("struct penumbra.core.asset.v1.Balance.SignedValue") } - fn visit_map(self, mut map_: V) -> std::result::Result + fn visit_map(self, mut map_: V) -> std::result::Result where V: serde::de::MapAccess<'de>, { + let mut value__ = None; let mut negated__ = None; - let mut balance__ = None; while let Some(k) = map_.next_key()? { match k { + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = map_.next_value()?; + } GeneratedField::Negated => { if negated__.is_some() { return Err(serde::de::Error::duplicate_field("negated")); } negated__ = Some(map_.next_value()?); } - GeneratedField::Balance => { - if balance__.is_some() { - return Err(serde::de::Error::duplicate_field("balance")); - } - balance__ = Some(map_.next_value()?); - } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } } } - Ok(Balance { + Ok(balance::SignedValue { + value: value__, negated: negated__.unwrap_or_default(), - balance: balance__.unwrap_or_default(), }) } } - deserializer.deserialize_struct("penumbra.core.asset.v1.Balance", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct("penumbra.core.asset.v1.Balance.SignedValue", FIELDS, GeneratedVisitor) } } impl serde::Serialize for BalanceCommitment { diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index aa25bace9f2b5974ef6d891407403a49413ba6cd..0fbc2f4ef00f46e37e8a02cc8765f9534dd7f0c1 100644 GIT binary patch delta 1893 zcmYjR&u>&!7@c$9oj32@cIfm?e@qLsLrW+qMJN$sFhWrbA!?IKnkYsX?F9Ri;sD)f z2dJfLNYjwQtqW;QN_AmEYOP(682$xa8kf3o<6c&t@6Eha`KF6kz- z!=x(E73{~D;g?K9j2+@2Tcgum(9Ko%v4lcW3|=iB65wZIOnxETWKKzGl6+Wo+4n}# zrFu&C^HJnpO=vNiO1Ub*dWP#-B1E;}*4+`J)^L80Ib)RO7)BCb+22NSCOhIanttl1 zr1ddXcG{2Hjw{${-xx!8X3Vsh18kmN$v~rHA!eQl=6SQq zqI=5zK8CZ0r*wTSN;;iNS4JT$kTsQSGjCT}DsW2n+&BjMOV6C;NF7z!nR+*iE^5EKobIe z3noW~%mSQ+z75^QT}#)*ZfIzU4P@K~=TeJs#K6Hs5)7DyZmR`=5;WAs21b-;QQs6> zs4>V_AMv%&P(A8a~ z(m4;k-=vR=u#(bmx{^1vF%;6gn4$(qT_sMe93@F;5CujCnjBLh@uR9`D$_2W6{c zA!hVFaEK?lJVL2u$v(P@{_+yE38x#H8MlPGU9o|>OX$ppE0YKY+y}pZ*=dNAp}~s{ zVT9ih8z^xf`~}F?g`xue06fJ|qAUh#&=PA%mIv^Inuemr12oHLEt90eGE`+3L|hGF zsyad;FT+bmkkn;l8qL?>Zv4P~h{X3m`)B#{pA_>&``4!^mmk8bX!|mm6{yO|5K)`3 zl8TVpE67$(hLGAT(aHFdcok|-n3#xE!iXvhTSazPQQlXZF zrh}!`REiBnyo*92!T1v!LTD2fZrr+aVO+T}aia?px2`;Q-prdW?&5skIp_QC_uU!& zxckmQ_wp0GJ7j(ThSL6D>CVIDA^c6;o^`>=ssSaXEmEF! zQRJJu{MTkLO^&g^ygxbaFHDsD$km^m_oLD$m8w>0BT5^U9U~fE%+$unBC7M$%b`6} zqoS>Oz#l5R6RJ`5DBEPst*5j#jc5C&WTUE8nR1j> zrV4EdW}PYjhT?;{5HB5$&mbrImSmyAv#47t_9WQwPV{m%W)ASuH^s;L z+beh}FsoyP{F|z%YC>C>Nd&SZk`uD6pfVC-(u2xKh-n{iB(;$oo?M1(QrU2X5L}xH zR3(9sQC+Ws3dJ&%LXjS9(MfssEr>g?*b*CbSW(`B<8jwavp=~4d9<7~?HQPhA-OB4 zYmXtFSJ2TJNtwJ2c`Qoyo7RZE2y+mG`OX+pdmCNds+W(-@$9=ueW?pqaUy%yG-Ey% z$27C5hpys<-c<-ER5D1TRk+nLq|qv}xk$p~ntnpbTWg8L)Lu&@pk&QR%tRbMUi&`8 z7tutB$-WOx^fZS~XxdoUr#{4BunwuhQHNpj)=|}(1n9etc0b&fVUX|uvc5t>!bFr3#!|DeZRA_yET!9Ku-`z*jC9SNcJ$H$zU$qI zx6B|#J8)Yfkr~4do@rNSxnhG> zd=2N3eFUeV&e2MlTkq+C>lp6egQyR+4AN>34P7y$)gF3|tNu{J Date: Sat, 30 Nov 2024 14:24:19 -0800 Subject: [PATCH 13/30] domain type (Balance): refactor conversions --- crates/core/asset/src/balance.rs | 48 ++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index 04d2ae4ce5..36409b3628 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -39,36 +39,68 @@ pub struct Balance { balance: BTreeMap>, } -/* Protobuf impls */ impl DomainType for Balance { type Proto = pb::Balance; } +/// Serialization should normalize the `Balance`, where the top-level +/// negated field is exlcuded during serialization. Rather, the +/// sign information is captured in the `SignedValue` pairs. impl TryFrom for Balance { type Error = anyhow::Error; fn try_from(v: pb::Balance) -> Result { let mut balance_map = BTreeMap::new(); - for imbalance_value in v.balance.into_iter().map(TryInto::try_into) { - let value: Value = imbalance_value?; + for signed_value in v.values { + let proto_value = signed_value + .value + .ok_or_else(|| anyhow::anyhow!("missing value"))?; + let value: Value = proto_value.try_into()?; let amount = NonZeroU128::new(value.amount.into()) - .ok_or_else(|| anyhow::anyhow!("amount must be non-zero"))?; + .ok_or_else(|| anyhow::anyhow!("amount is zero"))?; + + // Negated flag for `SignedValue` pairs determines the imbalance. + let imbalance = if signed_value.negated { + Imbalance::Required(amount) + } else { + Imbalance::Provided(amount) + }; - let imbalance = Imbalance::Provided(amount); // todo: fix this placeholder balance_map.insert(value.asset_id, imbalance); } + // Normalize the `Balance`. Ok(Self { - negated: v.negated, + negated: false, balance: balance_map, }) } } impl From for pb::Balance { - fn from(_v: Balance) -> Self { - todo!() // todo: implement fallible conversion + fn from(v: Balance) -> Self { + let values = v + .balance + .into_iter() + .map(|(id, imbalance)| { + // Decompose imbalance into it sign and magnitude, and convert + // magnitude into raw amount and determine negation based on the sign. + let (sign, magnitude) = imbalance.into_inner(); + let amount = u128::from(magnitude); + let negated = sign.is_required(); + + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some(id.into()), + amount: Some(Amount::from(amount).into()), + }), + negated, + } + }) + .collect(); + + pb::Balance { values } } } From e434fa424c5e094c8d884d5b08b174a087f71773 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 30 Nov 2024 14:24:37 -0800 Subject: [PATCH 14/30] fix infinite recursion --- crates/core/transaction/src/transaction.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index 7c9036c9b5..ad3e712f37 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -721,7 +721,12 @@ impl From for pbt::Transaction { impl From<&Transaction> for pbt::Transaction { fn from(msg: &Transaction) -> Self { - msg.into() + Transaction { + transaction_body: msg.transaction_body.clone(), + anchor: msg.anchor.clone(), + binding_sig: msg.binding_sig.clone(), + } + .into() } } From 7b5670cb1b15d6d827f437ce764af58631521bb9 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 30 Nov 2024 15:09:31 -0800 Subject: [PATCH 15/30] fill other action views --- crates/core/transaction/src/view.rs | 86 ++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index d1f8abe08b..339d7bd883 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -1,6 +1,7 @@ use anyhow::Context; use decaf377_rdsa::{Binding, Signature}; -use penumbra_asset::Balance; +use penumbra_asset::{Balance, Value}; +use penumbra_dex::{swap::SwapView, swap_claim::SwapClaimView}; use penumbra_keys::AddressView; use penumbra_proto::{core::transaction::v1 as pbt, DomainType}; @@ -105,8 +106,9 @@ impl TransactionView { match action_view { ActionView::Spend(spend_view) => match spend_view { SpendView::Visible { spend: _, note } => { - let value = note.value.value(); - let balance = Balance::from(value); + // Provided imbalance (+) + let balance = Balance::from(note.value.value()); + let address = AddressView::Opaque { address: note.address(), }; @@ -121,8 +123,9 @@ impl TransactionView { note, payload_key: _, } => { - let value = note.value.value(); - let balance = -Balance::from(value); + // Required imbalance (-) + let balance = -Balance::from(note.value.value()); + let address = AddressView::Opaque { address: note.address(), }; @@ -131,9 +134,76 @@ impl TransactionView { } OutputView::Opaque { output: _ } => continue, }, - ActionView::Swap(_) => todo!(), - ActionView::SwapClaim(_) => todo!(), - _ => {} + ActionView::Swap(swap_view) => match swap_view { + SwapView::Visible { + swap: _, + swap_plaintext, + output_1, + output_2: _, + claim_tx: _, + asset_1_metadata: _, + asset_2_metadata: _, + batch_swap_output_data: _, + } => { + let address = AddressView::Opaque { + address: output_1.clone().expect("sender address").address(), + }; + + let value_fee = Value { + amount: swap_plaintext.claim_fee.amount(), + asset_id: swap_plaintext.claim_fee.asset_id(), + }; + let value_1 = Value { + amount: swap_plaintext.delta_1_i, + asset_id: swap_plaintext.trading_pair.asset_1(), + }; + let value_2 = Value { + amount: swap_plaintext.delta_2_i, + asset_id: swap_plaintext.trading_pair.asset_2(), + }; + + // Required imbalance (-) + let mut balance = Balance::default(); + balance -= value_1; + balance -= value_2; + balance -= value_fee; + + effects.push(TransactionEffect { address, balance }); + } + SwapView::Opaque { + swap: _, + batch_swap_output_data: _, + output_1: _, + output_2: _, + asset_1_metadata: _, + asset_2_metadata: _, + } => continue, + }, + ActionView::SwapClaim(swap_claim_view) => match swap_claim_view { + SwapClaimView::Visible { + swap_claim, + output_1, + output_2: _, + swap_tx: _, + } => { + let address = AddressView::Opaque { + address: output_1.address(), + }; + + let value_fee = Value { + amount: swap_claim.body.fee.amount(), + asset_id: swap_claim.body.fee.asset_id(), + }; + + // Provided imbalance (+) + let mut balance = Balance::default(); + balance += value_fee; + + effects.push(TransactionEffect { address, balance }); + } + SwapClaimView::Opaque { swap_claim: _ } => continue, + }, + _ => {} // Fill in other action views as neccessary } } From f8123307a8b4e78ae830366672a6730eef16dc23 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 8 Dec 2024 11:10:00 -0800 Subject: [PATCH 16/30] proto(Balance): modify serialization and add unit testing --- crates/core/asset/src/balance.rs | 357 ++++++++++++++++++++++++++++++- 1 file changed, 350 insertions(+), 7 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index 36409b3628..bdff2df975 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -46,6 +46,10 @@ impl DomainType for Balance { /// Serialization should normalize the `Balance`, where the top-level /// negated field is exlcuded during serialization. Rather, the /// sign information is captured in the `SignedValue` pairs. +/// +/// Since the underlying BTreeMap can't hold multiple imbalances for +/// the same asset ID, we implement an ordering-agnostic accumulation +/// scheme that explicitly combines imbalances. impl TryFrom for Balance { type Error = anyhow::Error; @@ -60,14 +64,85 @@ impl TryFrom for Balance { let amount = NonZeroU128::new(value.amount.into()) .ok_or_else(|| anyhow::anyhow!("amount is zero"))?; - // Negated flag for `SignedValue` pairs determines the imbalance. - let imbalance = if signed_value.negated { - Imbalance::Required(amount) - } else { - Imbalance::Provided(amount) - }; + // The 'negated' flag in SignedValue determines the imbalance type: + // true = Required, false = Provided - balance_map.insert(value.asset_id, imbalance); + match balance_map.entry(value.asset_id) { + // First entry for this asset ID in BTreeMap + std::collections::btree_map::Entry::Vacant(entry) => { + let imbalance = if signed_value.negated { + Imbalance::Required(amount) + } else { + Imbalance::Provided(amount) + }; + entry.insert(imbalance); + } + // Subsequent entries for this asset ID in BTreeMap + // + // 1. Asset ID has a Required imbalance - accumulate another required amount + // 2. Asset ID has a Required imbalance - accumulate another provided amount + // 3. Asset ID has a Provided imbalance - accumulate another required amount + // 4. Asset ID has a Provided imbalance - accumulate another provided amount + std::collections::btree_map::Entry::Occupied(mut entry) => { + let existing = entry.get_mut(); + match (existing, signed_value.negated) { + (Imbalance::Required(existing_amount), true) => { + *existing_amount = NonZeroU128::new( + existing_amount + .get() + .checked_add(amount.get()) + .ok_or_else(|| anyhow::anyhow!("Combining required amounts"))?, + ) + .ok_or_else(|| anyhow::anyhow!("Combining required amounts"))?; + } + (Imbalance::Required(existing_amount), false) => { + match existing_amount.get().checked_sub(amount.get()) { + Some(diff) if diff > 0 => { + *existing_amount = NonZeroU128::new(diff) + .ok_or_else(|| anyhow::anyhow!("Reduce required amount"))?; + } + _ => { + *entry.get_mut() = Imbalance::Provided( + NonZeroU128::new(amount.get() - existing_amount.get()) + .ok_or_else(|| { + anyhow::anyhow!( + "Convert required amount to provided amount" + ) + })?, + ); + } + }; + } + (Imbalance::Provided(existing_amount), true) => { + match existing_amount.get().checked_sub(amount.get()) { + Some(diff) if diff > 0 => { + *existing_amount = NonZeroU128::new(diff) + .ok_or_else(|| anyhow::anyhow!("Reduce provided amount"))?; + } + _ => { + *entry.get_mut() = Imbalance::Required( + NonZeroU128::new(amount.get() - existing_amount.get()) + .ok_or_else(|| { + anyhow::anyhow!( + "Convert provided amount to required amount" + ) + })?, + ); + } + }; + } + (Imbalance::Provided(existing_amount), false) => { + *existing_amount = NonZeroU128::new( + existing_amount + .get() + .checked_add(amount.get()) + .ok_or_else(|| anyhow::anyhow!("Combining provided amounts"))?, + ) + .ok_or_else(|| anyhow::anyhow!("Combining provided amounts"))?; + } + } + } + } } // Normalize the `Balance`. @@ -463,7 +538,9 @@ mod test { use ark_ff::Zero; use decaf377::Fr; use once_cell::sync::Lazy; + use penumbra_proto::core::num::v1::Amount as ProtoAmount; use proptest::prelude::*; + use rand_core::OsRng; use super::*; @@ -626,4 +703,270 @@ mod test { assert_eq!(commitment, balance_commitment); } } + + /// Implement fallible conversion (protobuf to domain type) for multiple entries + /// with the same asset ID. + #[test] + fn try_from_fallible_conversion_same_asset_id() { + let proto_balance_0 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: true, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(50u128).into()), + }), + negated: false, + }, + ], + }; + + let proto_balance_1 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: true, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(200u128).into()), + }), + negated: false, + }, + ], + }; + + let proto_balance_2 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: true, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(200u128).into()), + }), + negated: true, + }, + ], + }; + + let proto_balance_3 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: false, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(50u128).into()), + }), + negated: true, + }, + ], + }; + + let proto_balance_4 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: false, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(200u128).into()), + }), + negated: true, + }, + ], + }; + + let proto_balance_5 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: false, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(200u128).into()), + }), + negated: false, + }, + ], + }; + + let balance_0 = Balance::try_from(proto_balance_0).expect("fallible conversion"); + let balance_1 = Balance::try_from(proto_balance_1).expect("fallible conversion"); + let balance_2 = Balance::try_from(proto_balance_2).expect("fallible conversion"); + let balance_3 = Balance::try_from(proto_balance_3).expect("fallible conversion"); + let balance_4 = Balance::try_from(proto_balance_4).expect("fallible conversion"); + let balance_5 = Balance::try_from(proto_balance_5).expect("fallible conversion"); + + assert!(matches!( + balance_0.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Required(amount)) if amount == &NonZeroU128::new(50).unwrap() + )); + + assert!(matches!( + balance_1.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Provided(amount)) if amount == &NonZeroU128::new(100).unwrap() + )); + + assert!(matches!( + balance_2.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Required(amount)) if amount == &NonZeroU128::new(300).unwrap() + )); + + assert!(matches!( + balance_3.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Provided(amount)) if amount == &NonZeroU128::new(50).unwrap() + )); + + assert!(matches!( + balance_4.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Required(amount)) if amount == &NonZeroU128::new(100).unwrap() + )); + + assert!(matches!( + balance_5.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Provided(amount)) if amount == &NonZeroU128::new(300).unwrap() + )); + } + + /// Implement fallible conversion (protobuf to domain type) for multiple entries + /// with different asset IDs. + #[test] + fn try_from_fallible_conversion_different_asset_id() { + let rand_asset_id = Id(Fq::rand(&mut OsRng)); + + let proto_balance = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: true, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some(rand_asset_id.into()), + amount: Some(Amount::from(50u128).into()), + }), + negated: false, + }, + ], + }; + + let balance = Balance::try_from(proto_balance).expect("fallible conversion"); + + assert!(matches!( + balance.balance.get(&STAKING_TOKEN_ASSET_ID), + Some(Imbalance::Required(amount)) if amount == &NonZeroU128::new(100).unwrap() + )); + + assert!(matches!( + balance.balance.get(&rand_asset_id), + Some(Imbalance::Provided(amount)) if amount == &NonZeroU128::new(50).unwrap() + )); + } + + /// Implement fallible conversion (protobuf to domain type) with missing fields. + #[test] + fn try_from_fallible_conversion_failure() { + let proto_balance = pb::Balance { + values: vec![pb::balance::SignedValue { + value: None, + negated: false, + }], + }; + + assert!(Balance::try_from(proto_balance).is_err()); + } + + /// Implement infallible conversion (domain type to protobuf). + #[test] + fn from_infallible_conversion() { + let rand_asset_id = Id(Fq::rand(&mut OsRng)); + + let balance = Balance { + negated: false, + balance: [ + ( + *STAKING_TOKEN_ASSET_ID, + Imbalance::Provided(NonZeroU128::new(100).unwrap()), + ), + ( + rand_asset_id, + Imbalance::Required(NonZeroU128::new(200).unwrap()), + ), + ] + .iter() + .cloned() + .collect(), + }; + + let proto_balance: pb::Balance = balance.into(); + + let first_value = proto_balance + .values + .iter() + .find(|v| v.value.as_ref().unwrap().asset_id == Some((*STAKING_TOKEN_ASSET_ID).into())) + .expect("asset should exist"); + let second_value = proto_balance + .values + .iter() + .find(|v| v.value.as_ref().unwrap().asset_id == Some(rand_asset_id.into())) + .expect("asset should exist"); + + assert_eq!(proto_balance.values.len(), 2); + + assert_eq!( + first_value.value.as_ref().unwrap().asset_id, + Some((*STAKING_TOKEN_ASSET_ID).into()) + ); + assert_eq!( + second_value.value.as_ref().unwrap().asset_id, + Some(rand_asset_id.into()) + ); + + let proto_amount: ProtoAmount = Amount::from(100u128).into(); + assert_eq!( + first_value.value.as_ref().unwrap().amount, + Some(proto_amount) + ); + + let proto_amount: ProtoAmount = Amount::from(200u128).into(); + assert_eq!( + second_value.value.as_ref().unwrap().amount, + Some(proto_amount) + ); + } } From f2142d876e45e0cfda39df117451c27b0de323aa Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 8 Dec 2024 17:53:39 -0800 Subject: [PATCH 17/30] proto(Balance): fix negation flag --- crates/core/asset/src/balance.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index bdff2df975..3052f407a6 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -35,8 +35,8 @@ use penumbra_proto::{penumbra::core::asset::v1 as pb, DomainType}; #[derive(Clone, Eq, Default, Serialize, Deserialize)] #[serde(try_from = "pb::Balance", into = "pb::Balance")] pub struct Balance { - negated: bool, - balance: BTreeMap>, + pub negated: bool, + pub balance: BTreeMap>, } impl DomainType for Balance { @@ -161,16 +161,15 @@ impl From for pb::Balance { .map(|(id, imbalance)| { // Decompose imbalance into it sign and magnitude, and convert // magnitude into raw amount and determine negation based on the sign. - let (sign, magnitude) = imbalance.into_inner(); + let (_sign, magnitude) = imbalance.into_inner(); let amount = u128::from(magnitude); - let negated = sign.is_required(); pb::balance::SignedValue { value: Some(pb::Value { asset_id: Some(id.into()), amount: Some(Amount::from(amount).into()), }), - negated, + negated: v.negated, } }) .collect(); @@ -386,7 +385,7 @@ impl SubAssign for Balance { } impl From for Balance { - fn from(Value { amount, asset_id }: Value) -> Self { + fn from(Value { amount, asset_id }: Value) -> Self { let mut balance = BTreeMap::new(); if let Some(amount) = NonZeroU128::new(amount.into()) { balance.insert(asset_id, Imbalance::Provided(amount)); From 746d981fde200e03cf9d4aaa400d94821c09aa8f Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 8 Dec 2024 17:54:52 -0800 Subject: [PATCH 18/30] TransactionSummary: add unit testing --- crates/core/keys/src/address/view.rs | 2 +- crates/core/transaction/src/transaction.rs | 2 +- crates/core/transaction/src/view.rs | 286 ++++++++++++++++++++- 3 files changed, 287 insertions(+), 3 deletions(-) diff --git a/crates/core/keys/src/address/view.rs b/crates/core/keys/src/address/view.rs index fd1cf6db0a..ad8a1ab7c6 100644 --- a/crates/core/keys/src/address/view.rs +++ b/crates/core/keys/src/address/view.rs @@ -11,7 +11,7 @@ use super::Address; /// /// This type allows working with addresses and address indexes without knowing /// the corresponding FVK. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] #[serde(try_from = "pb::AddressView", into = "pb::AddressView")] pub enum AddressView { Opaque { diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index ad3e712f37..39a7113248 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -46,7 +46,7 @@ pub struct TransactionBody { } /// Represents a transaction summary containing multiple effects. -#[derive(Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(try_from = "pbt::TransactionSummary", into = "pbt::TransactionSummary")] pub struct TransactionSummary { pub effects: Vec, diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index 339d7bd883..718cf3607c 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -1,8 +1,11 @@ +use std::collections::BTreeSet; + use anyhow::Context; use decaf377_rdsa::{Binding, Signature}; use penumbra_asset::{Balance, Value}; use penumbra_dex::{swap::SwapView, swap_claim::SwapClaimView}; use penumbra_keys::AddressView; +use penumbra_proto::core::asset::v1::Balance as ProtoBalance; use penumbra_proto::{core::transaction::v1 as pbt, DomainType}; use penumbra_shielded_pool::{OutputView, SpendView}; @@ -99,6 +102,61 @@ impl TransactionView { self.body_view.action_views.iter() } + /// Acts as a higher-order translator that summarizes a TransactionSummary by consolidating + /// effects for each unique address. + pub fn accumulate_effects(summary: &TransactionSummary) -> TransactionSummary { + let mut transaction_summary = TransactionSummary::default(); + + // Get unique addresses + let addresses: Vec<_> = summary + .effects + .iter() + .map(|e| &e.address) + .collect::>() + .into_iter() + .collect(); + + // For each address, get unique asset IDs and combine the balances for each asset ID + for address in addresses { + let asset_ids: Vec<_> = summary + .effects + .iter() + .filter(|effect| effect.address == *address) + .flat_map(|effect| effect.balance.balance.keys()) + .collect(); + + let mut combined_balances = Balance::default(); + for asset_id in asset_ids { + let grouped_proto = summary + .effects + .iter() + .filter(|effect| effect.address == *address) + .filter(|effect| effect.balance.balance.contains_key(asset_id)) + .flat_map(|effect| ProtoBalance::from(effect.balance.clone()).values) + .collect(); + + let combined_proto = ProtoBalance { + values: grouped_proto, + }; + + let combined_balance: Balance = combined_proto + .try_into() + .expect("combined balance should be valid"); + + // Combine all asset balances for the address + combined_balances.balance.extend(combined_balance.balance); + } + + transaction_summary.effects.push(TransactionEffect { + address: address.clone(), + balance: combined_balances, + }); + } + + transaction_summary + } + + /// Produces a TransactionSummary, iterating through each visible action and collecting the effects of the transaction. pub fn summary(&self) -> TransactionSummary { let mut effects = Vec::new(); @@ -207,7 +265,9 @@ impl TransactionView { } } - TransactionSummary { effects } + let mut summary = TransactionSummary { effects }; + + Self::accumulate_effects(&mut summary) } } @@ -409,3 +469,227 @@ impl TryFrom for MemoPlaintextView { }) } } + +#[cfg(test)] +mod test { + use super::*; + + use decaf377::{Element, Fq}; + use decaf377_rdsa::{Domain, VerificationKey}; + use penumbra_asset::{ + asset::{Cache, Id}, + balance::Commitment, + STAKING_TOKEN_ASSET_ID, + }; + use penumbra_fee::Fee; + use penumbra_keys::{ + symmetric::{OvkWrappedKey, WrappedMemoKey}, + test_keys, FullViewingKey, PayloadKey, + }; + use penumbra_proof_params::GROTH16_PROOF_LENGTH_BYTES; + use penumbra_sct::Nullifier; + use penumbra_shielded_pool::{output, spend, Note, NoteView, OutputPlan, SpendPlan}; + use penumbra_tct::structure::Hash; + use rand_core::OsRng; + use std::ops::Deref; + + use crate::{ + plan::{CluePlan, DetectionDataPlan}, + view, ActionPlan, TransactionPlan, + }; + + #[cfg(test)] + fn dummy_sig() -> Signature { + Signature::from([0u8; 64]) + } + + #[cfg(test)] + fn dummy_pk() -> VerificationKey { + VerificationKey::try_from(Element::default().vartime_compress().0) + .expect("creating a dummy verification key should work") + } + + #[cfg(test)] + fn dummy_commitment() -> Commitment { + Commitment(Element::default()) + } + + #[cfg(test)] + fn dummy_proof_spend() -> spend::SpendProof { + spend::SpendProof::try_from( + penumbra_proto::penumbra::core::component::shielded_pool::v1::ZkSpendProof { + inner: vec![0u8; GROTH16_PROOF_LENGTH_BYTES], + }, + ) + .expect("creating a dummy proof should work") + } + + #[cfg(test)] + fn dummy_proof_output() -> output::OutputProof { + output::OutputProof::try_from( + penumbra_proto::penumbra::core::component::shielded_pool::v1::ZkOutputProof { + inner: vec![0u8; GROTH16_PROOF_LENGTH_BYTES], + }, + ) + .expect("creating a dummy proof should work") + } + + #[cfg(test)] + fn dummy_spend() -> spend::Spend { + spend::Spend { + body: spend::Body { + balance_commitment: dummy_commitment(), + nullifier: Nullifier(Fq::default()), + rk: dummy_pk(), + }, + auth_sig: dummy_sig(), + proof: dummy_proof_spend(), + } + } + + #[cfg(test)] + fn dummy_output() -> output::Output { + output::Output { + body: output::Body { + note_payload: penumbra_shielded_pool::NotePayload { + note_commitment: penumbra_shielded_pool::note::StateCommitment(Fq::default()), + ephemeral_key: [0u8; 32] + .as_slice() + .try_into() + .expect("can create dummy ephemeral key"), + encrypted_note: penumbra_shielded_pool::NoteCiphertext([0u8; 176]), + }, + balance_commitment: dummy_commitment(), + ovk_wrapped_key: OvkWrappedKey([0u8; 48]), + wrapped_memo_key: WrappedMemoKey([0u8; 48]), + }, + proof: dummy_proof_output(), + } + } + + #[cfg(test)] + fn convert_note(cache: &Cache, fvk: &FullViewingKey, note: &Note) -> NoteView { + NoteView { + value: note.value().view_with_cache(cache), + rseed: note.rseed(), + address: fvk.view_address(note.address()), + } + } + + #[cfg(test)] + fn convert_action( + cache: &Cache, + fvk: &FullViewingKey, + action: &ActionPlan, + ) -> Option { + use view::action_view::SpendView; + + match action { + ActionPlan::Output(x) => Some(ActionView::Output( + penumbra_shielded_pool::OutputView::Visible { + output: dummy_output(), + note: convert_note(cache, fvk, &x.output_note()), + payload_key: PayloadKey::from([0u8; 32]), + }, + )), + ActionPlan::Spend(x) => Some(ActionView::Spend(SpendView::Visible { + spend: dummy_spend(), + note: convert_note(cache, fvk, &x.note), + })), + ActionPlan::ValidatorDefinition(_) => None, + ActionPlan::Swap(_) => None, + ActionPlan::SwapClaim(_) => None, + ActionPlan::ProposalSubmit(_) => None, + ActionPlan::ProposalWithdraw(_) => None, + ActionPlan::DelegatorVote(_) => None, + ActionPlan::ValidatorVote(_) => None, + ActionPlan::ProposalDepositClaim(_) => None, + ActionPlan::PositionOpen(_) => None, + ActionPlan::PositionClose(_) => None, + ActionPlan::PositionWithdraw(_) => None, + ActionPlan::Delegate(_) => None, + ActionPlan::Undelegate(_) => None, + ActionPlan::UndelegateClaim(_) => None, + ActionPlan::Ics20Withdrawal(_) => None, + ActionPlan::CommunityPoolSpend(_) => None, + ActionPlan::CommunityPoolOutput(_) => None, + ActionPlan::CommunityPoolDeposit(_) => None, + ActionPlan::ActionDutchAuctionSchedule(_) => None, + ActionPlan::ActionDutchAuctionEnd(_) => None, + ActionPlan::ActionDutchAuctionWithdraw(_) => None, + ActionPlan::IbcAction(_) => todo!(), + } + } + + #[test] + fn test_transaction_summary() { + // Generate two notes controlled by the test address. + let value = Value { + amount: 100u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }; + let note = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value); + let value2 = Value { + amount: 50u64.into(), + asset_id: Id(Fq::rand(&mut OsRng)), + }; + let note2 = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value2); + + // Record that note in an SCT, where we can generate an auth path. + let mut sct = tct::Tree::new(); + for _ in 0..5 { + let random_note = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value); + sct.insert(tct::Witness::Keep, random_note.commit()) + .unwrap(); + } + sct.insert(tct::Witness::Keep, note.commit()).unwrap(); + sct.insert(tct::Witness::Keep, note2.commit()).unwrap(); + + let auth_path = sct.witness(note.commit()).unwrap(); + let auth_path2 = sct.witness(note2.commit()).unwrap(); + + // Add a single spend and output to the transaction plan such that the + // transaction balances. + let plan = TransactionPlan { + transaction_parameters: TransactionParameters { + expiry_height: 0, + fee: Fee::default(), + chain_id: "".into(), + }, + actions: vec![ + SpendPlan::new(&mut OsRng, note, auth_path.position()).into(), + SpendPlan::new(&mut OsRng, note2, auth_path2.position()).into(), + OutputPlan::new(&mut OsRng, value, test_keys::ADDRESS_1.deref().clone()).into(), + ], + detection_data: Some(DetectionDataPlan { + clue_plans: vec![CluePlan::new( + &mut OsRng, + test_keys::ADDRESS_1.deref().clone(), + 1.try_into().unwrap(), + )], + }), + memo: None, + }; + + let transaction_view = TransactionView { + anchor: penumbra_tct::Root(Hash::zero()), + binding_sig: Signature::from([0u8; 64]), + body_view: TransactionBodyView { + action_views: plan + .actions + .iter() + .filter_map(|x| { + convert_action(&Cache::with_known_assets(), &test_keys::FULL_VIEWING_KEY, x) + }) + .collect(), + transaction_parameters: plan.transaction_parameters.clone(), + detection_data: None, + memo_view: None, + }, + }; + + let transaction_summary = TransactionView::summary(&transaction_view); + + assert_eq!(transaction_summary.effects.len(), 2); + } +} From 86ede41e1b916f60fca1883d6dced0610cc032fc Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 8 Dec 2024 18:03:23 -0800 Subject: [PATCH 19/30] cargo clippy --- crates/core/asset/src/balance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index 3052f407a6..9fc7a0c016 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -385,7 +385,7 @@ impl SubAssign for Balance { } impl From for Balance { - fn from(Value { amount, asset_id }: Value) -> Self { + fn from(Value { amount, asset_id }: Value) -> Self { let mut balance = BTreeMap::new(); if let Some(amount) = NonZeroU128::new(amount.into()) { balance.insert(asset_id, Imbalance::Provided(amount)); From 0ef739a62a9ab6fc332ad64c2fa5eab7c26914a3 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 8 Dec 2024 19:47:31 -0800 Subject: [PATCH 20/30] change visibility to private --- crates/core/transaction/src/view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index 718cf3607c..75af4f2c53 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -104,7 +104,7 @@ impl TransactionView { /// Acts as a higher-order translator that summarizes a TransactionSummary by consolidating /// effects for each unique address. - pub fn accumulate_effects(summary: &TransactionSummary) -> TransactionSummary { + fn accumulate_effects(summary: &TransactionSummary) -> TransactionSummary { let mut transaction_summary = TransactionSummary::default(); // Get unique addresses From ef70e9c5e3a807a8f5fcade082b025bcc911e2eb Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 10 Dec 2024 19:04:59 -0800 Subject: [PATCH 21/30] simplify accumulate_summary method --- crates/core/transaction/src/view.rs | 65 +++++++++++++++-------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index 75af4f2c53..3be69fdae9 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeSet; - use anyhow::Context; use decaf377_rdsa::{Binding, Signature}; use penumbra_asset::{Balance, Value}; @@ -7,9 +5,9 @@ use penumbra_dex::{swap::SwapView, swap_claim::SwapClaimView}; use penumbra_keys::AddressView; use penumbra_proto::core::asset::v1::Balance as ProtoBalance; use penumbra_proto::{core::transaction::v1 as pbt, DomainType}; - use penumbra_shielded_pool::{OutputView, SpendView}; use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; pub mod action_view; mod transaction_perspective; @@ -104,52 +102,51 @@ impl TransactionView { /// Acts as a higher-order translator that summarizes a TransactionSummary by consolidating /// effects for each unique address. - fn accumulate_effects(summary: &TransactionSummary) -> TransactionSummary { + fn accumulate_summary(summary: &TransactionSummary) -> TransactionSummary { let mut transaction_summary = TransactionSummary::default(); - // Get unique addresses - let addresses: Vec<_> = summary + // Get list of unique addresses + let addresses = summary .effects .iter() .map(|e| &e.address) - .collect::>() - .into_iter() - .collect(); + .collect::>(); - // For each address, get unique asset IDs and combine the balances for each asset ID - for address in addresses { - let asset_ids: Vec<_> = summary + for address in &addresses { + // Get unique asset IDs for this address + let asset_ids = summary .effects .iter() - .filter(|effect| effect.address == *address) + .filter(|effect| effect.address == **address) .flat_map(|effect| effect.balance.balance.keys()) - .collect(); + .collect::>(); + + // For each unique asset, collect all proto values + let mut all_proto_values = Vec::new(); - let mut combined_balances = Balance::default(); for asset_id in asset_ids { - let grouped_proto = summary + let proto_values = summary .effects .iter() - .filter(|effect| effect.address == *address) + .filter(|effect| effect.address == **address) .filter(|effect| effect.balance.balance.contains_key(asset_id)) .flat_map(|effect| ProtoBalance::from(effect.balance.clone()).values) - .collect(); + .collect::>(); - let combined_proto = ProtoBalance { - values: grouped_proto, - }; + all_proto_values.extend(proto_values); + } - let combined_balance: Balance = combined_proto - .try_into() - .expect("combined balance should be valid"); + let combined_proto = ProtoBalance { + values: all_proto_values, + }; - // Combine all asset balances for the address - combined_balances.balance.extend(combined_balance.balance); - } + // Single conversion to domain type + let combined_balance: Balance = + combined_proto.try_into().expect("Domain type conversion"); transaction_summary.effects.push(TransactionEffect { - address: address.clone(), - balance: combined_balances, + address: (*address).clone(), + balance: combined_balance, }); } @@ -267,7 +264,7 @@ impl TransactionView { let mut summary = TransactionSummary { effects }; - Self::accumulate_effects(&mut summary) + Self::accumulate_summary(&mut summary) } } @@ -629,12 +626,18 @@ mod test { asset_id: *STAKING_TOKEN_ASSET_ID, }; let note = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value); + let value2 = Value { amount: 50u64.into(), asset_id: Id(Fq::rand(&mut OsRng)), }; let note2 = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value2); + let value3 = Value { + amount: 75u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }; + // Record that note in an SCT, where we can generate an auth path. let mut sct = tct::Tree::new(); for _ in 0..5 { @@ -659,7 +662,7 @@ mod test { actions: vec![ SpendPlan::new(&mut OsRng, note, auth_path.position()).into(), SpendPlan::new(&mut OsRng, note2, auth_path2.position()).into(), - OutputPlan::new(&mut OsRng, value, test_keys::ADDRESS_1.deref().clone()).into(), + OutputPlan::new(&mut OsRng, value3, test_keys::ADDRESS_1.deref().clone()).into(), ], detection_data: Some(DetectionDataPlan { clue_plans: vec![CluePlan::new( From 6b593907c9bf3eafb507dbc6c33558fce07a1ce7 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 10 Dec 2024 20:13:56 -0800 Subject: [PATCH 22/30] swap unit test and dummy data --- crates/core/transaction/src/view.rs | 237 +++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 4 deletions(-) diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index 3be69fdae9..e6fdad6bc3 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -136,6 +136,8 @@ impl TransactionView { all_proto_values.extend(proto_values); } + println!("all_proto_values: {:?}", all_proto_values); + let combined_proto = ProtoBalance { values: all_proto_values, }; @@ -471,22 +473,35 @@ impl TryFrom for MemoPlaintextView { mod test { use super::*; + use decaf377::Fr; use decaf377::{Element, Fq}; use decaf377_rdsa::{Domain, VerificationKey}; use penumbra_asset::{ - asset::{Cache, Id}, + asset::{self, Cache, Id}, balance::Commitment, STAKING_TOKEN_ASSET_ID, }; + use penumbra_dex::swap::proof::SwapProof; + use penumbra_dex::swap::{SwapCiphertext, SwapPayload}; + use penumbra_dex::Swap; + use penumbra_dex::{ + swap::{SwapPlaintext, SwapPlan}, + TradingPair, + }; use penumbra_fee::Fee; + use penumbra_keys::keys::Bip44Path; + use penumbra_keys::keys::{SeedPhrase, SpendKey}; use penumbra_keys::{ symmetric::{OvkWrappedKey, WrappedMemoKey}, - test_keys, FullViewingKey, PayloadKey, + test_keys, Address, FullViewingKey, PayloadKey, }; + use penumbra_num::Amount; use penumbra_proof_params::GROTH16_PROOF_LENGTH_BYTES; use penumbra_sct::Nullifier; + use penumbra_shielded_pool::Rseed; use penumbra_shielded_pool::{output, spend, Note, NoteView, OutputPlan, SpendPlan}; use penumbra_tct::structure::Hash; + use penumbra_tct::StateCommitment; use rand_core::OsRng; use std::ops::Deref; @@ -531,6 +546,16 @@ mod test { .expect("creating a dummy proof should work") } + #[cfg(test)] + fn dummy_proof_swap() -> SwapProof { + SwapProof::try_from( + penumbra_proto::penumbra::core::component::dex::v1::ZkSwapProof { + inner: vec![0u8; GROTH16_PROOF_LENGTH_BYTES], + }, + ) + .expect("creating a dummy proof should work") + } + #[cfg(test)] fn dummy_spend() -> spend::Spend { spend::Spend { @@ -564,6 +589,98 @@ mod test { } } + #[cfg(test)] + fn dummy_swap_plaintext() -> SwapPlaintext { + let seed_phrase = SeedPhrase::generate(OsRng); + let sk_recipient = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk_recipient = sk_recipient.full_viewing_key(); + let ivk_recipient = fvk_recipient.incoming(); + let (claim_address, _dtk_d) = ivk_recipient.payment_address(0u32.into()); + + let gm = asset::Cache::with_known_assets().get_unit("gm").unwrap(); + let gn = asset::Cache::with_known_assets().get_unit("gn").unwrap(); + let trading_pair = TradingPair::new(gm.id(), gn.id()); + + let delta_1 = Amount::from(1u64); + let delta_2 = Amount::from(0u64); + let fee = Fee::default(); + + let swap_plaintext = SwapPlaintext::new( + &mut OsRng, + trading_pair, + delta_1, + delta_2, + fee, + claim_address, + ); + + swap_plaintext + } + + #[cfg(test)] + fn dummy_swap() -> Swap { + use penumbra_dex::swap::Body; + + let seed_phrase = SeedPhrase::generate(OsRng); + let sk_recipient = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk_recipient = sk_recipient.full_viewing_key(); + let ivk_recipient = fvk_recipient.incoming(); + let (claim_address, _dtk_d) = ivk_recipient.payment_address(0u32.into()); + + let gm = asset::Cache::with_known_assets().get_unit("gm").unwrap(); + let gn = asset::Cache::with_known_assets().get_unit("gn").unwrap(); + let trading_pair = TradingPair::new(gm.id(), gn.id()); + + let delta_1 = Amount::from(1u64); + let delta_2 = Amount::from(0u64); + let fee = Fee::default(); + + let swap_plaintext = SwapPlaintext::new( + &mut OsRng, + trading_pair, + delta_1, + delta_2, + fee, + claim_address, + ); + + let fee_blinding = Fr::from(0u64); + let fee_commitment = swap_plaintext.claim_fee.commit(fee_blinding); + + let swap_payload = SwapPayload { + encrypted_swap: SwapCiphertext([0u8; 272]), + commitment: StateCommitment::try_from([0; 32]).expect("state commitment"), + }; + + Swap { + body: Body { + trading_pair: trading_pair, + delta_1_i: delta_1, + delta_2_i: delta_2, + fee_commitment: fee_commitment, + payload: swap_payload, + }, + proof: dummy_proof_swap(), + } + } + + #[cfg(test)] + fn dummy_note_view( + address: Address, + value: Value, + cache: &Cache, + fvk: &FullViewingKey, + ) -> NoteView { + let note = Note::from_parts(address, value, Rseed::generate(&mut OsRng)) + .expect("generate dummy note"); + + NoteView { + value: note.value().view_with_cache(cache), + rseed: note.rseed(), + address: fvk.view_address(note.address()), + } + } + #[cfg(test)] fn convert_note(cache: &Cache, fvk: &FullViewingKey, note: &Note) -> NoteView { NoteView { @@ -594,7 +711,21 @@ mod test { note: convert_note(cache, fvk, &x.note), })), ActionPlan::ValidatorDefinition(_) => None, - ActionPlan::Swap(_) => None, + ActionPlan::Swap(x) => Some(ActionView::Swap(SwapView::Visible { + swap: dummy_swap(), + swap_plaintext: dummy_swap_plaintext(), + output_1: Some(dummy_note_view( + x.swap_plaintext.claim_address.clone(), + x.swap_plaintext.claim_fee.0, + cache, + fvk, + )), + output_2: None, + claim_tx: None, + asset_1_metadata: None, + asset_2_metadata: None, + batch_swap_output_data: None, + })), ActionPlan::SwapClaim(_) => None, ActionPlan::ProposalSubmit(_) => None, ActionPlan::ProposalWithdraw(_) => None, @@ -619,7 +750,7 @@ mod test { } #[test] - fn test_transaction_summary() { + fn test_internal_transfer_transaction_summary() { // Generate two notes controlled by the test address. let value = Value { amount: 100u64.into(), @@ -695,4 +826,102 @@ mod test { assert_eq!(transaction_summary.effects.len(), 2); } + + #[test] + fn test_swap_transaction_summary() { + // Generate two notes controlled by the test address. + let value = Value { + amount: 100u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }; + let note = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value); + + let value2 = Value { + amount: 50u64.into(), + asset_id: Id(Fq::rand(&mut OsRng)), + }; + let note2 = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value2); + + let value3 = Value { + amount: 75u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }; + + // Record that note in an SCT, where we can generate an auth path. + let mut sct = tct::Tree::new(); + for _ in 0..5 { + let random_note = Note::generate(&mut OsRng, &test_keys::ADDRESS_0, value); + sct.insert(tct::Witness::Keep, random_note.commit()) + .unwrap(); + } + sct.insert(tct::Witness::Keep, note.commit()).unwrap(); + sct.insert(tct::Witness::Keep, note2.commit()).unwrap(); + + let auth_path = sct.witness(note.commit()).unwrap(); + let auth_path2 = sct.witness(note2.commit()).unwrap(); + + let gm = asset::Cache::with_known_assets().get_unit("gm").unwrap(); + let gn = asset::Cache::with_known_assets().get_unit("gn").unwrap(); + let trading_pair = TradingPair::new(gm.id(), gn.id()); + + let delta_1 = Amount::from(100_000u64); + let delta_2 = Amount::from(0u64); + let fee = Fee::default(); + let claim_address: Address = test_keys::ADDRESS_0.deref().clone(); + let plaintext = SwapPlaintext::new( + &mut OsRng, + trading_pair, + delta_1, + delta_2, + fee, + claim_address, + ); + + // Add a single spend and output to the transaction plan such that the + // transaction balances. + let plan = TransactionPlan { + transaction_parameters: TransactionParameters { + expiry_height: 0, + fee: Fee::default(), + chain_id: "".into(), + }, + actions: vec![ + SpendPlan::new(&mut OsRng, note, auth_path.position()).into(), + SpendPlan::new(&mut OsRng, note2, auth_path2.position()).into(), + OutputPlan::new(&mut OsRng, value3, test_keys::ADDRESS_1.deref().clone()).into(), + SwapPlan::new(&mut OsRng, plaintext.clone()).into(), + ], + detection_data: Some(DetectionDataPlan { + clue_plans: vec![CluePlan::new( + &mut OsRng, + test_keys::ADDRESS_1.deref().clone(), + 1.try_into().unwrap(), + )], + }), + memo: None, + }; + + let transaction_view = TransactionView { + anchor: penumbra_tct::Root(Hash::zero()), + binding_sig: Signature::from([0u8; 64]), + body_view: TransactionBodyView { + action_views: plan + .actions + .iter() + .filter_map(|x| { + convert_action(&Cache::with_known_assets(), &test_keys::FULL_VIEWING_KEY, x) + }) + .collect(), + transaction_parameters: plan.transaction_parameters.clone(), + detection_data: None, + memo_view: None, + }, + }; + + let transaction_summary = TransactionView::summary(&transaction_view); + + println!("transaction_summary: {:?}", transaction_summary); + + // assert_eq!(transaction_summary.effects.len(), 2); + } } From 254423276a9de6abe1065ac3e4b6a9e0ac66a0df Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 10 Dec 2024 20:15:55 -0800 Subject: [PATCH 23/30] remove log statements --- crates/core/transaction/src/view.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index e6fdad6bc3..7ec60c0d84 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -136,8 +136,6 @@ impl TransactionView { all_proto_values.extend(proto_values); } - println!("all_proto_values: {:?}", all_proto_values); - let combined_proto = ProtoBalance { values: all_proto_values, }; @@ -920,8 +918,6 @@ mod test { let transaction_summary = TransactionView::summary(&transaction_view); - println!("transaction_summary: {:?}", transaction_summary); - - // assert_eq!(transaction_summary.effects.len(), 2); + assert_eq!(transaction_summary.effects.len(), 2); } } From ee48a485834f4e65ec31bb4a44b474b1e06f8371 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 13 Dec 2024 06:12:14 -0800 Subject: [PATCH 24/30] simplify accumulate effects helper --- crates/core/transaction/src/view.rs | 65 ++++++----------------------- 1 file changed, 13 insertions(+), 52 deletions(-) diff --git a/crates/core/transaction/src/view.rs b/crates/core/transaction/src/view.rs index 7ec60c0d84..14cbe2c915 100644 --- a/crates/core/transaction/src/view.rs +++ b/crates/core/transaction/src/view.rs @@ -3,11 +3,9 @@ use decaf377_rdsa::{Binding, Signature}; use penumbra_asset::{Balance, Value}; use penumbra_dex::{swap::SwapView, swap_claim::SwapClaimView}; use penumbra_keys::AddressView; -use penumbra_proto::core::asset::v1::Balance as ProtoBalance; use penumbra_proto::{core::transaction::v1 as pbt, DomainType}; use penumbra_shielded_pool::{OutputView, SpendView}; use serde::{Deserialize, Serialize}; -use std::collections::BTreeSet; pub mod action_view; mod transaction_perspective; @@ -102,55 +100,18 @@ impl TransactionView { /// Acts as a higher-order translator that summarizes a TransactionSummary by consolidating /// effects for each unique address. - fn accumulate_summary(summary: &TransactionSummary) -> TransactionSummary { - let mut transaction_summary = TransactionSummary::default(); - - // Get list of unique addresses - let addresses = summary - .effects - .iter() - .map(|e| &e.address) - .collect::>(); - - for address in &addresses { - // Get unique asset IDs for this address - let asset_ids = summary - .effects - .iter() - .filter(|effect| effect.address == **address) - .flat_map(|effect| effect.balance.balance.keys()) - .collect::>(); - - // For each unique asset, collect all proto values - let mut all_proto_values = Vec::new(); - - for asset_id in asset_ids { - let proto_values = summary - .effects - .iter() - .filter(|effect| effect.address == **address) - .filter(|effect| effect.balance.balance.contains_key(asset_id)) - .flat_map(|effect| ProtoBalance::from(effect.balance.clone()).values) - .collect::>(); - - all_proto_values.extend(proto_values); - } - - let combined_proto = ProtoBalance { - values: all_proto_values, - }; - - // Single conversion to domain type - let combined_balance: Balance = - combined_proto.try_into().expect("Domain type conversion"); - - transaction_summary.effects.push(TransactionEffect { - address: (*address).clone(), - balance: combined_balance, - }); + fn accumulate_effects(summary: TransactionSummary) -> TransactionSummary { + use std::collections::BTreeMap; + let mut keyed_effects: BTreeMap = BTreeMap::new(); + for effect in summary.effects { + *keyed_effects.entry(effect.address).or_default() += effect.balance; + } + TransactionSummary { + effects: keyed_effects + .into_iter() + .map(|(address, balance)| TransactionEffect { address, balance }) + .collect(), } - - transaction_summary } /// Produces a TransactionSummary, iterating through each visible action and collecting the effects of the transaction. @@ -262,9 +223,9 @@ impl TransactionView { } } - let mut summary = TransactionSummary { effects }; + let summary = TransactionSummary { effects }; - Self::accumulate_summary(&mut summary) + Self::accumulate_effects(summary) } } From d59033ecbb5d11fa4df40c0195bbcd76d4fb139c Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 13 Dec 2024 11:00:34 -0800 Subject: [PATCH 25/30] proto(Balance): handle zero cases in fallible conversion --- crates/core/asset/src/balance.rs | 61 ++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index 9fc7a0c016..ecdea31da7 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -101,17 +101,20 @@ impl TryFrom for Balance { *existing_amount = NonZeroU128::new(diff) .ok_or_else(|| anyhow::anyhow!("Reduce required amount"))?; } + Some(0) => { + entry.remove(); + } _ => { *entry.get_mut() = Imbalance::Provided( NonZeroU128::new(amount.get() - existing_amount.get()) .ok_or_else(|| { anyhow::anyhow!( - "Convert required amount to provided amount" + "Convert required to provided amount" ) })?, ); } - }; + } } (Imbalance::Provided(existing_amount), true) => { match existing_amount.get().checked_sub(amount.get()) { @@ -119,12 +122,15 @@ impl TryFrom for Balance { *existing_amount = NonZeroU128::new(diff) .ok_or_else(|| anyhow::anyhow!("Reduce provided amount"))?; } + Some(0) => { + entry.remove(); + } _ => { *entry.get_mut() = Imbalance::Required( NonZeroU128::new(amount.get() - existing_amount.get()) .ok_or_else(|| { anyhow::anyhow!( - "Convert provided amount to required amount" + "Convert provided to required amount" ) })?, ); @@ -910,6 +916,55 @@ mod test { assert!(Balance::try_from(proto_balance).is_err()); } + /// Implement fallible conversion (protobuf to domain type) for cases where [-x UM, +x UM] + /// [+x UM, -x UM]. + #[test] + fn try_from_fallible_conversion_failure_zero_invariant() { + let proto_balance_0 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: true, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: false, + }, + ], + }; + + let proto_balance_1 = pb::Balance { + values: vec![ + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: false, + }, + pb::balance::SignedValue { + value: Some(pb::Value { + asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), + amount: Some(Amount::from(100u128).into()), + }), + negated: true, + }, + ], + }; + + let balance_0 = Balance::try_from(proto_balance_0).expect("fallible conversion"); + let balance_1 = Balance::try_from(proto_balance_1).expect("fallible conversion"); + + assert!(balance_0.balance.get(&STAKING_TOKEN_ASSET_ID).is_none()); + assert!(balance_1.balance.get(&STAKING_TOKEN_ASSET_ID).is_none()); + } + /// Implement infallible conversion (domain type to protobuf). #[test] fn from_infallible_conversion() { From 02b18d088e4a1cfe09395e6c61822babbd47370e Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 13 Dec 2024 11:01:33 -0800 Subject: [PATCH 26/30] serialization: explicitely define ordering --- crates/core/keys/src/address/view.rs | 95 +++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/crates/core/keys/src/address/view.rs b/crates/core/keys/src/address/view.rs index ad8a1ab7c6..45003fc044 100644 --- a/crates/core/keys/src/address/view.rs +++ b/crates/core/keys/src/address/view.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use serde::{Deserialize, Serialize}; use penumbra_proto::{penumbra::core::keys::v1 as pb, DomainType}; @@ -11,7 +13,7 @@ use super::Address; /// /// This type allows working with addresses and address indexes without knowing /// the corresponding FVK. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(try_from = "pb::AddressView", into = "pb::AddressView")] pub enum AddressView { Opaque { @@ -99,6 +101,97 @@ impl TryFrom for AddressView { } } +// Canonical ordering for serialization +impl PartialOrd for AddressView { + fn partial_cmp(&self, other: &Self) -> Option { + // Opaque < Decoded + match (self, other) { + (AddressView::Opaque { address: a1 }, AddressView::Opaque { address: a2 }) => { + a1.partial_cmp(a2) + } + ( + AddressView::Decoded { + address: a1, + index: i1, + wallet_id: w1, + }, + AddressView::Decoded { + address: a2, + index: i2, + wallet_id: w2, + }, + ) => match a1.partial_cmp(a2) { + Some(Ordering::Equal) => match i1.partial_cmp(i2) { + Some(Ordering::Equal) => w1.partial_cmp(w2), + ord => ord, + }, + ord => ord, + }, + ( + AddressView::Opaque { address: _ }, + AddressView::Decoded { + address: _, + index: _, + wallet_id: _, + }, + ) => Some(Ordering::Less), + ( + AddressView::Decoded { + address: _, + index: _, + wallet_id: _, + }, + AddressView::Opaque { address: _ }, + ) => Some(Ordering::Greater), + } + } +} + +impl Ord for AddressView { + fn cmp(&self, other: &Self) -> Ordering { + // Opaque < Decoded + match (self, other) { + (AddressView::Opaque { address: a1 }, AddressView::Opaque { address: a2 }) => { + a1.cmp(a2) + } + ( + AddressView::Decoded { + address: a1, + index: i1, + wallet_id: w1, + }, + AddressView::Decoded { + address: a2, + index: i2, + wallet_id: w2, + }, + ) => match a1.cmp(a2) { + Ordering::Equal => match i1.cmp(i2) { + Ordering::Equal => w1.cmp(w2), + ord => ord, + }, + ord => ord, + }, + ( + AddressView::Opaque { address: _ }, + AddressView::Decoded { + address: _, + index: _, + wallet_id: _, + }, + ) => Ordering::Less, + ( + AddressView::Decoded { + address: _, + index: _, + wallet_id: _, + }, + AddressView::Opaque { address: _ }, + ) => Ordering::Greater, + } + } +} + #[cfg(test)] mod tests { use rand_core::OsRng; From a37c0460df64df8f3749582320d6a3842e662071 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 13 Dec 2024 11:25:31 -0800 Subject: [PATCH 27/30] testing: add proptest and filter zero amounts in fallible conversion --- crates/core/asset/src/balance.rs | 41 +++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index ecdea31da7..f5013f1d83 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -61,8 +61,10 @@ impl TryFrom for Balance { .value .ok_or_else(|| anyhow::anyhow!("missing value"))?; let value: Value = proto_value.try_into()?; - let amount = NonZeroU128::new(value.amount.into()) - .ok_or_else(|| anyhow::anyhow!("amount is zero"))?; + let amount = match NonZeroU128::new(value.amount.into()) { + Some(amount) => amount, + None => continue, + }; // The 'negated' flag in SignedValue determines the imbalance type: // true = Required, false = Provided @@ -539,7 +541,10 @@ impl std::ops::Add for BalanceVar { #[cfg(test)] mod test { - use crate::{asset::Metadata, STAKING_TOKEN_ASSET_ID}; + use crate::{ + asset::{self, Metadata}, + STAKING_TOKEN_ASSET_ID, + }; use ark_ff::Zero; use decaf377::Fr; use once_cell::sync::Lazy; @@ -1023,4 +1028,34 @@ mod test { Some(proto_amount) ); } + + fn test_balance_serialization_round_tripping_example( + parts: Vec<(u8, i64)>, + ) -> anyhow::Result<()> { + let proto = pb::Balance { + values: parts + .into_iter() + .map(|(asset, amount)| pb::balance::SignedValue { + value: Some(pb::Value { + amount: Some(Amount::from(amount.abs() as u64).into()), + asset_id: Some(asset::Id::try_from([asset; 32]).unwrap().into()), + }), + negated: amount < 0, + }) + .collect(), + }; + let p_to_d = Balance::try_from(proto)?; + let p_to_d_to_p = pb::Balance::from(p_to_d.clone()); + let p_to_d_to_p_to_d = Balance::try_from(p_to_d_to_p)?; + assert_eq!(p_to_d, p_to_d_to_p_to_d); + Ok(()) + } + + proptest! { + #[test] + fn test_balance_serialization_roundtripping(parts in proptest::collection::vec(((0u8..16u8), any::()), 0..100)) { + // To get better errors + assert!(matches!(test_balance_serialization_round_tripping_example(parts), Ok(_))); + } + } } From f34601f4011e71f283e3d8a902c6c5dbb3b75bd9 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Fri, 13 Dec 2024 12:18:24 -0800 Subject: [PATCH 28/30] Simplify balance conversion This makes the newly added proptests pass --- crates/core/asset/src/balance.rs | 183 +++++-------------------------- 1 file changed, 28 insertions(+), 155 deletions(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index f5013f1d83..db2aa0482e 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use ark_r1cs_std::prelude::*; use ark_r1cs_std::uint8::UInt8; use ark_relations::r1cs::SynthesisError; @@ -25,7 +26,7 @@ mod imbalance; mod iter; use commitment::VALUE_BLINDING_GENERATOR; use decaf377::{r1cs::ElementVar, Fq, Fr}; -use imbalance::Imbalance; +use imbalance::{Imbalance, Sign}; use self::commitment::BalanceCommitmentVar; use penumbra_proto::{penumbra::core::asset::v1 as pb, DomainType}; @@ -39,6 +40,23 @@ pub struct Balance { pub balance: BTreeMap>, } +impl Balance { + fn from_signed_value(negated: bool, value: Value) -> Option { + let non_zero = NonZeroU128::try_from(value.amount.value()).ok()?; + Some(Self { + negated: false, + balance: BTreeMap::from([( + value.asset_id, + if negated { + Imbalance::Required(non_zero) + } else { + Imbalance::Provided(non_zero) + }, + )]), + }) + } +} + impl DomainType for Balance { type Proto = pb::Balance; } @@ -53,111 +71,15 @@ impl DomainType for Balance { impl TryFrom for Balance { type Error = anyhow::Error; - fn try_from(v: pb::Balance) -> Result { - let mut balance_map = BTreeMap::new(); - - for signed_value in v.values { - let proto_value = signed_value - .value - .ok_or_else(|| anyhow::anyhow!("missing value"))?; - let value: Value = proto_value.try_into()?; - let amount = match NonZeroU128::new(value.amount.into()) { - Some(amount) => amount, - None => continue, - }; - - // The 'negated' flag in SignedValue determines the imbalance type: - // true = Required, false = Provided - - match balance_map.entry(value.asset_id) { - // First entry for this asset ID in BTreeMap - std::collections::btree_map::Entry::Vacant(entry) => { - let imbalance = if signed_value.negated { - Imbalance::Required(amount) - } else { - Imbalance::Provided(amount) - }; - entry.insert(imbalance); - } - // Subsequent entries for this asset ID in BTreeMap - // - // 1. Asset ID has a Required imbalance - accumulate another required amount - // 2. Asset ID has a Required imbalance - accumulate another provided amount - // 3. Asset ID has a Provided imbalance - accumulate another required amount - // 4. Asset ID has a Provided imbalance - accumulate another provided amount - std::collections::btree_map::Entry::Occupied(mut entry) => { - let existing = entry.get_mut(); - match (existing, signed_value.negated) { - (Imbalance::Required(existing_amount), true) => { - *existing_amount = NonZeroU128::new( - existing_amount - .get() - .checked_add(amount.get()) - .ok_or_else(|| anyhow::anyhow!("Combining required amounts"))?, - ) - .ok_or_else(|| anyhow::anyhow!("Combining required amounts"))?; - } - (Imbalance::Required(existing_amount), false) => { - match existing_amount.get().checked_sub(amount.get()) { - Some(diff) if diff > 0 => { - *existing_amount = NonZeroU128::new(diff) - .ok_or_else(|| anyhow::anyhow!("Reduce required amount"))?; - } - Some(0) => { - entry.remove(); - } - _ => { - *entry.get_mut() = Imbalance::Provided( - NonZeroU128::new(amount.get() - existing_amount.get()) - .ok_or_else(|| { - anyhow::anyhow!( - "Convert required to provided amount" - ) - })?, - ); - } - } - } - (Imbalance::Provided(existing_amount), true) => { - match existing_amount.get().checked_sub(amount.get()) { - Some(diff) if diff > 0 => { - *existing_amount = NonZeroU128::new(diff) - .ok_or_else(|| anyhow::anyhow!("Reduce provided amount"))?; - } - Some(0) => { - entry.remove(); - } - _ => { - *entry.get_mut() = Imbalance::Required( - NonZeroU128::new(amount.get() - existing_amount.get()) - .ok_or_else(|| { - anyhow::anyhow!( - "Convert provided to required amount" - ) - })?, - ); - } - }; - } - (Imbalance::Provided(existing_amount), false) => { - *existing_amount = NonZeroU128::new( - existing_amount - .get() - .checked_add(amount.get()) - .ok_or_else(|| anyhow::anyhow!("Combining provided amounts"))?, - ) - .ok_or_else(|| anyhow::anyhow!("Combining provided amounts"))?; - } - } - } + fn try_from(balance: pb::Balance) -> Result { + let mut out = Self::default(); + for v in balance.values { + let value = v.value.ok_or_else(|| anyhow!("missing value"))?; + if let Some(b) = Balance::from_signed_value(v.negated, Value::try_from(value)?) { + out += b; } } - - // Normalize the `Balance`. - Ok(Self { - negated: false, - balance: balance_map, - }) + Ok(out) } } @@ -169,7 +91,7 @@ impl From for pb::Balance { .map(|(id, imbalance)| { // Decompose imbalance into it sign and magnitude, and convert // magnitude into raw amount and determine negation based on the sign. - let (_sign, magnitude) = imbalance.into_inner(); + let (sign, magnitude) = if v.negated { -imbalance } else { imbalance }.into_inner(); let amount = u128::from(magnitude); pb::balance::SignedValue { @@ -177,7 +99,7 @@ impl From for pb::Balance { asset_id: Some(id.into()), amount: Some(Amount::from(amount).into()), }), - negated: v.negated, + negated: matches!(sign, Sign::Required), } }) .collect(); @@ -921,55 +843,6 @@ mod test { assert!(Balance::try_from(proto_balance).is_err()); } - /// Implement fallible conversion (protobuf to domain type) for cases where [-x UM, +x UM] - /// [+x UM, -x UM]. - #[test] - fn try_from_fallible_conversion_failure_zero_invariant() { - let proto_balance_0 = pb::Balance { - values: vec![ - pb::balance::SignedValue { - value: Some(pb::Value { - asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), - amount: Some(Amount::from(100u128).into()), - }), - negated: true, - }, - pb::balance::SignedValue { - value: Some(pb::Value { - asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), - amount: Some(Amount::from(100u128).into()), - }), - negated: false, - }, - ], - }; - - let proto_balance_1 = pb::Balance { - values: vec![ - pb::balance::SignedValue { - value: Some(pb::Value { - asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), - amount: Some(Amount::from(100u128).into()), - }), - negated: false, - }, - pb::balance::SignedValue { - value: Some(pb::Value { - asset_id: Some((*STAKING_TOKEN_ASSET_ID).into()), - amount: Some(Amount::from(100u128).into()), - }), - negated: true, - }, - ], - }; - - let balance_0 = Balance::try_from(proto_balance_0).expect("fallible conversion"); - let balance_1 = Balance::try_from(proto_balance_1).expect("fallible conversion"); - - assert!(balance_0.balance.get(&STAKING_TOKEN_ASSET_ID).is_none()); - assert!(balance_1.balance.get(&STAKING_TOKEN_ASSET_ID).is_none()); - } - /// Implement infallible conversion (domain type to protobuf). #[test] fn from_infallible_conversion() { From 74aa056b6c395c17c475c81d26e732a13852ad47 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Fri, 13 Dec 2024 12:35:52 -0800 Subject: [PATCH 29/30] Simplify manual PartialOrd for AdressView This matches the derived implementation, but doesn't change under refactoring. --- crates/core/keys/src/address/view.rs | 37 ++++++---------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/crates/core/keys/src/address/view.rs b/crates/core/keys/src/address/view.rs index 45003fc044..932d80368a 100644 --- a/crates/core/keys/src/address/view.rs +++ b/crates/core/keys/src/address/view.rs @@ -103,46 +103,25 @@ impl TryFrom for AddressView { // Canonical ordering for serialization impl PartialOrd for AddressView { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { + use AddressView::*; // Opaque < Decoded match (self, other) { - (AddressView::Opaque { address: a1 }, AddressView::Opaque { address: a2 }) => { - a1.partial_cmp(a2) - } + (Opaque { .. }, Decoded { .. }) => Some(Ordering::Less), + (Decoded { .. }, Opaque { .. }) => Some(Ordering::Greater), + (Opaque { address: a1 }, Opaque { address: a2 }) => a1.partial_cmp(a2), ( - AddressView::Decoded { + Decoded { address: a1, index: i1, wallet_id: w1, }, - AddressView::Decoded { + Decoded { address: a2, index: i2, wallet_id: w2, }, - ) => match a1.partial_cmp(a2) { - Some(Ordering::Equal) => match i1.partial_cmp(i2) { - Some(Ordering::Equal) => w1.partial_cmp(w2), - ord => ord, - }, - ord => ord, - }, - ( - AddressView::Opaque { address: _ }, - AddressView::Decoded { - address: _, - index: _, - wallet_id: _, - }, - ) => Some(Ordering::Less), - ( - AddressView::Decoded { - address: _, - index: _, - wallet_id: _, - }, - AddressView::Opaque { address: _ }, - ) => Some(Ordering::Greater), + ) => (a1, i1, w1).partial_cmp(&(a2, i2, w2)), } } } From 28c8e70f0d2c3627143ce00afe7dc40e6fce3925 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Fri, 13 Dec 2024 17:25:28 -0500 Subject: [PATCH 30/30] core(balance): fix typo Signed-off-by: Erwan Or --- crates/core/asset/src/balance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/asset/src/balance.rs b/crates/core/asset/src/balance.rs index db2aa0482e..8b8e290dba 100644 --- a/crates/core/asset/src/balance.rs +++ b/crates/core/asset/src/balance.rs @@ -62,7 +62,7 @@ impl DomainType for Balance { } /// Serialization should normalize the `Balance`, where the top-level -/// negated field is exlcuded during serialization. Rather, the +/// negated field is excluded during serialization. Rather, the /// sign information is captured in the `SignedValue` pairs. /// /// Since the underlying BTreeMap can't hold multiple imbalances for