Skip to content

Commit 30a5585

Browse files
authored
Merge pull request #140 from input-output-hk/whankinsiv/multiassets-in-utxo-deltas
feat: add native asset decoding to tx_unpacker and store balances in utxo_state
2 parents 8951137 + 40a3d9a commit 30a5585

File tree

10 files changed

+366
-145
lines changed

10 files changed

+366
-145
lines changed

common/src/types.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use serde_with::{hex::Hex, serde_as};
1616
use std::cmp::Ordering;
1717
use std::collections::{HashMap, HashSet};
1818
use std::fmt::{Display, Formatter};
19+
use std::ops::Neg;
1920

2021
/// Protocol era
2122
#[derive(
@@ -126,7 +127,7 @@ pub struct AddressDelta {
126127
pub address: Address,
127128

128129
/// Balance change
129-
pub delta: i64,
130+
pub delta: ValueDelta,
130131
}
131132

132133
/// Stake balance change
@@ -139,6 +140,88 @@ pub struct StakeAddressDelta {
139140
pub delta: i64,
140141
}
141142

143+
/// Value (lovelace + multiasset)
144+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
145+
pub struct Value {
146+
pub lovelace: u64,
147+
pub assets: NativeAssets,
148+
}
149+
150+
impl Value {
151+
pub fn new(lovelace: u64, assets: NativeAssets) -> Self {
152+
Self { lovelace, assets }
153+
}
154+
155+
pub fn coin(&self) -> u64 {
156+
self.lovelace
157+
}
158+
}
159+
160+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
161+
pub struct ValueDelta {
162+
pub lovelace: i64,
163+
pub assets: Vec<(PolicyId, Vec<NativeAssetDelta>)>,
164+
}
165+
166+
impl ValueDelta {
167+
pub fn new(lovelace: i64, assets: Vec<(PolicyId, Vec<NativeAssetDelta>)>) -> Self {
168+
Self { lovelace, assets }
169+
}
170+
}
171+
172+
impl From<&Value> for ValueDelta {
173+
fn from(v: &Value) -> Self {
174+
ValueDelta {
175+
lovelace: v.lovelace as i64,
176+
assets: v
177+
.assets
178+
.iter()
179+
.map(|(pid, nas)| {
180+
let nas_delta = nas
181+
.iter()
182+
.map(|na| NativeAssetDelta {
183+
name: na.name.clone(),
184+
amount: na.amount as i64,
185+
})
186+
.collect();
187+
(*pid, nas_delta)
188+
})
189+
.collect(),
190+
}
191+
}
192+
}
193+
194+
impl Neg for ValueDelta {
195+
type Output = Self;
196+
197+
fn neg(mut self) -> Self::Output {
198+
self.lovelace = -self.lovelace;
199+
for (_, nas) in &mut self.assets {
200+
for na in nas {
201+
na.amount = -na.amount;
202+
}
203+
}
204+
self
205+
}
206+
}
207+
208+
pub type PolicyId = [u8; 28];
209+
pub type NativeAssets = Vec<(PolicyId, Vec<NativeAsset>)>;
210+
pub type NativeAssetsDelta = Vec<(PolicyId, Vec<NativeAssetDelta>)>;
211+
pub type AssetName = Vec<u8>;
212+
213+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
214+
pub struct NativeAsset {
215+
pub name: AssetName,
216+
pub amount: u64,
217+
}
218+
219+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
220+
pub struct NativeAssetDelta {
221+
pub name: AssetName,
222+
pub amount: i64,
223+
}
224+
142225
/// Transaction output (UTXO)
143226
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
144227
pub struct TxOutput {
@@ -152,7 +235,7 @@ pub struct TxOutput {
152235
pub address: Address,
153236

154237
/// Output value (Lovelace)
155-
pub value: u64,
238+
pub value: Value,
156239
// todo: Implement datum /// Datum (raw)
157240
// !!! pub datum: Vec<u8>,
158241
}

modules/genesis_bootstrapper/src/genesis_bootstrapper.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use acropolis_common::{
66
CardanoMessage, GenesisCompleteMessage, Message, PotDeltasMessage, UTXODeltasMessage,
77
},
88
Address, BlockInfo, BlockStatus, ByronAddress, Era, Lovelace, LovelaceDelta, Pot, PotDelta,
9-
TxOutput, UTXODelta,
9+
TxOutput, UTXODelta, Value,
1010
};
1111
use anyhow::Result;
1212
use caryatid_sdk::{module, Context, Module};
@@ -107,7 +107,7 @@ impl GenesisBootstrapper {
107107
address: Address::Byron(ByronAddress {
108108
payload: address.payload.to_vec(),
109109
}),
110-
value: *amount,
110+
value: Value::new(*amount, Vec::new()),
111111
};
112112

113113
utxo_deltas_message.deltas.push(UTXODelta::Output(tx_output));

modules/stake_delta_filter/src/utils.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,13 @@ impl Tracker {
277277
.map(|a| a.to_string())
278278
.unwrap_or(Ok("(none)".to_owned()))
279279
.unwrap_or("(???)".to_owned());
280-
delta += event.address_delta.delta;
280+
delta += event.address_delta.delta.lovelace;
281281

282282
chunk.push(format!(
283283
" blk {}, {}: {} ({:?}) => {} ({:?})",
284284
event.block.number,
285285
src_addr,
286-
event.address_delta.delta,
286+
event.address_delta.delta.lovelace,
287287
event.address_delta.address,
288288
dst_addr,
289289
event.stake_address
@@ -388,7 +388,7 @@ pub fn process_message(
388388

389389
let stake_delta = StakeAddressDelta {
390390
address: stake_address,
391-
delta: d.delta,
391+
delta: d.delta.lovelace,
392392
};
393393
result.deltas.push(stake_delta);
394394
}
@@ -402,15 +402,15 @@ mod test {
402402
use acropolis_common::{
403403
messages::AddressDeltasMessage, Address, AddressDelta, BlockInfo, BlockStatus,
404404
ByronAddress, Era, ShelleyAddress, ShelleyAddressDelegationPart, ShelleyAddressPaymentPart,
405-
ShelleyAddressPointer, StakeAddress, StakeAddressPayload,
405+
ShelleyAddressPointer, StakeAddress, StakeAddressPayload, ValueDelta,
406406
};
407407
use bech32::{Bech32, Hrp};
408408

409409
fn parse_addr(s: &str) -> Result<AddressDelta> {
410410
let a = pallas_addresses::Address::from_bech32(s)?;
411411
Ok(AddressDelta {
412412
address: map_address(&a)?,
413-
delta: 1,
413+
delta: ValueDelta::new(1, Vec::new()),
414414
})
415415
}
416416

modules/tx_unpacker/src/map_parameters.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use pallas::ledger::{
88
ProtocolVersion as PallasProtocolVersion, Relay as PallasRelay, ScriptHash,
99
StakeCredential as PallasStakeCredential,
1010
},
11-
traverse::MultiEraCert,
11+
traverse::{MultiEraCert, MultiEraPolicyAssets, MultiEraValue},
1212
*,
1313
};
1414

@@ -855,3 +855,59 @@ pub fn map_all_governance_voting_procedures(
855855

856856
Ok(procs)
857857
}
858+
859+
pub fn map_value(pallas_value: &MultiEraValue) -> Value {
860+
let lovelace = pallas_value.coin();
861+
let pallas_assets = pallas_value.assets();
862+
863+
let mut assets: NativeAssets = Vec::new();
864+
865+
for policy_group in pallas_assets {
866+
match policy_group {
867+
MultiEraPolicyAssets::AlonzoCompatibleOutput(policy, kvps) => {
868+
match policy.as_ref().try_into() {
869+
Ok(policy_id) => {
870+
let native_assets = kvps
871+
.iter()
872+
.map(|(name, amt)| NativeAsset {
873+
name: name.to_vec(),
874+
amount: *amt,
875+
})
876+
.collect();
877+
878+
assets.push((policy_id, native_assets));
879+
}
880+
Err(_) => {
881+
tracing::error!(
882+
"Invalid policy id length: expected 28 bytes, got {}",
883+
policy.len()
884+
);
885+
continue;
886+
}
887+
}
888+
}
889+
MultiEraPolicyAssets::ConwayOutput(policy, kvps) => match policy.as_ref().try_into() {
890+
Ok(policy_id) => {
891+
let native_assets = kvps
892+
.iter()
893+
.map(|(name, amt)| NativeAsset {
894+
name: name.to_vec(),
895+
amount: u64::from(*amt),
896+
})
897+
.collect();
898+
899+
assets.push((policy_id, native_assets));
900+
}
901+
Err(_) => {
902+
tracing::error!(
903+
"Invalid policy id length: expected 28 bytes, got {}",
904+
policy.len()
905+
);
906+
continue;
907+
}
908+
},
909+
_ => {}
910+
}
911+
}
912+
Value::new(lovelace, assets)
913+
}

modules/tx_unpacker/src/tx_unpacker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl TxUnpacker {
193193
tx_hash: *tx.hash(),
194194
index: index as u64,
195195
address: address,
196-
value: output.value().coin(),
196+
value: map_parameters::map_value(&output.value())
197197
// !!! datum
198198
};
199199

modules/utxo_state/src/address_delta_publisher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Address delta publisher for the UTXO state Acropolis module
22
use acropolis_common::{
33
messages::{AddressDeltasMessage, CardanoMessage, Message},
4-
Address, AddressDelta, BlockInfo,
4+
Address, AddressDelta, BlockInfo, ValueDelta,
55
};
66
use async_trait::async_trait;
77
use caryatid_sdk::Context;
@@ -44,7 +44,7 @@ impl AddressDeltaObserver for AddressDeltaPublisher {
4444
}
4545

4646
/// Observe an address delta and publish messages
47-
async fn observe_delta(&self, address: &Address, delta: i64) {
47+
async fn observe_delta(&self, address: &Address, delta: ValueDelta) {
4848
// Accumulate the delta
4949
self.deltas.lock().await.push(AddressDelta {
5050
address: address.clone(),

modules/utxo_state/src/fake_immutable_utxo_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Fake store for immutable UTXOs
22
33
use crate::state::{ImmutableUTXOStore, UTXOKey, UTXOValue};
4-
use acropolis_common::Address;
4+
use acropolis_common::{Address, Value};
55
use anyhow::Result;
66
use async_trait::async_trait;
77
use config::Config;
@@ -51,7 +51,7 @@ impl ImmutableUTXOStore for FakeImmutableUTXOStore {
5151
async fn lookup_utxo(&self, _key: &UTXOKey) -> Result<Option<UTXOValue>> {
5252
Ok(Some(UTXOValue {
5353
address: Address::None,
54-
value: 42,
54+
value: Value::new(42, Vec::new()),
5555
}))
5656
}
5757

modules/utxo_state/src/rest.rs

Lines changed: 0 additions & 95 deletions
This file was deleted.

0 commit comments

Comments
 (0)