-
Notifications
You must be signed in to change notification settings - Fork 296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TransactionView: summary of transaction effects #4943
base: main
Are you sure you want to change the base?
Changes from all commits
9935b89
30138f8
7174118
06bf641
9add44d
abd9a29
8c12d8e
8382e6a
ff86c9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Id, Imbalance<NonZeroU128>>, | ||
} | ||
|
||
/* Protobuf impls */ | ||
impl DomainType for Balance { | ||
type Proto = pb::Balance; | ||
} | ||
|
||
impl TryFrom<pb::Balance> for Balance { | ||
type Error = anyhow::Error; | ||
|
||
fn try_from(v: pb::Balance) -> Result<Self, Self::Error> { | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the modeling suggested by henry here: https://github.com/penumbra-zone/penumbra/pull/4943/files#r1855950445 takes care of that |
||
balance_map.insert(value.asset_id, imbalance); | ||
} | ||
|
||
Ok(Self { | ||
negated: v.negated, | ||
balance: balance_map, | ||
}) | ||
} | ||
} | ||
|
||
impl From<Balance> for pb::Balance { | ||
fn from(_v: Balance) -> Self { | ||
todo!() // todo: implement fallible conversion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment: need to implement fallible conversion (proto to domain type) |
||
} | ||
} | ||
|
||
impl Debug for Balance { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("Balance") | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,14 +7,15 @@ 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}, | ||||||||||||
swap::Swap, | ||||||||||||
}; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. penumbra/crates/core/transaction/src/transaction.rs Lines 735 to 739 in ff86c9a
comment: does this lead to infinite recursion? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup. good catch |
||||||||||||
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<MemoCiphertext>, | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// Represents a transaction summary containing multiple effects. | ||||||||||||
#[derive(Clone, Default, Serialize, Deserialize)] | ||||||||||||
#[serde(try_from = "pbt::TransactionSummary", into = "pbt::TransactionSummary")] | ||||||||||||
pub struct TransactionSummary { | ||||||||||||
pub effects: Vec<Effects>, | ||||||||||||
} | ||||||||||||
|
||||||||||||
/// 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 address: AddressView, | ||||||||||||
pub 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<TransactionSummary> for pbt::TransactionSummary { | ||||||||||||
fn from(pbt: TransactionSummary) -> Self { | ||||||||||||
pbt::TransactionSummary { | ||||||||||||
effects: pbt.effects.into_iter().map(Into::into).collect(), | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl TryFrom<pbt::TransactionSummary> for TransactionSummary { | ||||||||||||
type Error = anyhow::Error; | ||||||||||||
|
||||||||||||
fn try_from(pbt: pbt::TransactionSummary) -> Result<Self, Self::Error> { | ||||||||||||
let effects = pbt | ||||||||||||
.effects | ||||||||||||
.into_iter() | ||||||||||||
.map(TryInto::try_into) | ||||||||||||
.collect::<Result<Vec<_>, _>>()?; | ||||||||||||
|
||||||||||||
Ok(Self { effects }) | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl DomainType for Effects { | ||||||||||||
type Proto = pbt::Effects; | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl From<Effects> for pbt::Effects { | ||||||||||||
fn from(effect: Effects) -> Self { | ||||||||||||
pbt::Effects { | ||||||||||||
address: Some(effect.address.into()), | ||||||||||||
balance: Some(effect.balance.into()), | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
impl TryFrom<pbt::Effects> for Effects { | ||||||||||||
type Error = anyhow::Error; | ||||||||||||
|
||||||||||||
fn try_from(pbt: pbt::Effects) -> Result<Self, Self::Error> { | ||||||||||||
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; | ||||||||||||
} | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Item = &ActionView> { | ||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment: Iterates through the visible action views and adds the contributing balances to the transaction's effects. Sanity check the required and provided balances are correct. |
||
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!(), | ||
Comment on lines
+134
to
+135
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment: need to implement other action views |
||
_ => {} | ||
} | ||
} | ||
|
||
TransactionSummary { effects } | ||
} | ||
} | ||
|
||
impl DomainType for TransactionView { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment: confirmed the previous derived serde policy wasn't used.