Skip to content

Commit 1df613c

Browse files
authored
Merge pull request #189 from input-output-hk/whankinsiv/asset-transactions
feat: transactions and addresses storage in assets_state
2 parents 07fef0b + ccd7ae6 commit 1df613c

File tree

14 files changed

+880
-221
lines changed

14 files changed

+880
-221
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ structure is highly subject to change:
6060
- [Stake Delta Filter](modules/stake_delta_filter) - filters out stake address changes and handles stake pointer references
6161
- [Epochs State](modules/epochs_state) - track fees blocks minted and epochs history
6262
- [Accounts State](modules/accounts_state) - stake and reward accounts tracker
63+
- [Assets State](modules/assets_state) - tracks native asset supply, metadata, transactions, and addresses
6364

6465
```mermaid
6566
graph LR
@@ -74,19 +75,24 @@ graph LR
7475
DRepState(DRep State)
7576
GovernanceState(Governance State)
7677
StakeDeltaFilter(Stake Delta Filter)
77-
EpochsState(EpochsState)
78+
EpochsState(Epochs State)
7879
AccountsState(Accounts State)
80+
AssetsState(Assets State)
81+
ParametersState(Parameters State)
7982
8083
UpstreamChainFetcher --> BlockUnpacker
8184
MithrilSnapshotFetcher --> BlockUnpacker
8285
BlockUnpacker --> TxUnpacker
8386
GenesisBootstrapper --> UTXOState
8487
TxUnpacker --> UTXOState
88+
TxUnpacker --> AssetsState
8589
TxUnpacker --> EpochsState
8690
TxUnpacker --> AccountsState
87-
TxUnpacker --> SPOState
8891
TxUnpacker --> DRepState
92+
TxUnpacker --> SPOState
8993
TxUnpacker --> GovernanceState
94+
GovernanceState --> ParametersState
95+
TxUnpacker --> ParametersState
9096
UTXOState --> StakeDeltaFilter
9197
StakeDeltaFilter --> AccountsState
9298
UpstreamChainFetcher --> EpochsState
@@ -95,6 +101,7 @@ graph LR
95101
SPOState --> AccountsState
96102
DRepState --> GovernanceState
97103
GovernanceState --> AccountsState
104+
ParametersState --> AccountsState
98105
```
99106

100107
## Messages

common/src/address.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct ByronAddress {
1414
}
1515

1616
/// Address network identifier
17-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17+
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1818
pub enum AddressNetwork {
1919
/// Mainnet
2020
Main,
@@ -30,7 +30,7 @@ impl Default for AddressNetwork {
3030
}
3131

3232
/// A Shelley-era address - payment part
33-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
33+
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3434
pub enum ShelleyAddressPaymentPart {
3535
/// Payment to a key
3636
PaymentKeyHash(KeyHash),
@@ -59,7 +59,7 @@ pub struct ShelleyAddressPointer {
5959
}
6060

6161
/// A Shelley-era address - delegation part
62-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
62+
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
6363
pub enum ShelleyAddressDelegationPart {
6464
/// No delegation (enterprise addresses)
6565
None,
@@ -81,7 +81,7 @@ impl Default for ShelleyAddressDelegationPart {
8181
}
8282

8383
/// A Shelley-era address
84-
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84+
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
8585
pub struct ShelleyAddress {
8686
/// Network id
8787
pub network: AddressNetwork,

common/src/messages.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ pub struct UTXODeltasMessage {
7373
/// Message encapsulating multiple asset deltas
7474
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7575
pub struct AssetDeltasMessage {
76-
/// Ordered set of deltas
77-
pub deltas: Vec<(TxHash, NativeAssetsDelta)>,
76+
/// Mint and burn deltas per tx
77+
pub deltas: Vec<(TxIdentifier, NativeAssetsDelta)>,
7878

7979
/// CIP 25 metadata blobs (Using 721 label)
8080
pub cip25_metadata_updates: Vec<Vec<u8>>,

common/src/queries/assets.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{
2-
AssetInfoRecord, AssetMintRecord, AssetName, PolicyAsset, PolicyId, ShelleyAddress, TxHash,
2+
AssetAddressEntry, AssetInfoRecord, AssetMintRecord, AssetName, PolicyAsset, PolicyId,
3+
TxIdentifier,
34
};
45

56
pub const DEFAULT_ASSETS_QUERY_TOPIC: (&str, &str) =
@@ -13,8 +14,8 @@ pub const DEFAULT_OFFCHAIN_TOKEN_REGISTRY_URL: (&str, &str) = (
1314
pub type AssetList = Vec<PolicyAsset>;
1415
pub type AssetInfo = (u64, AssetInfoRecord);
1516
pub type AssetHistory = Vec<AssetMintRecord>;
16-
pub type AssetAddresses = Vec<(ShelleyAddress, u64)>;
17-
pub type AssetTransactions = Vec<TxHash>;
17+
pub type AssetAddresses = Vec<AssetAddressEntry>;
18+
pub type AssetTransactions = Vec<TxIdentifier>;
1819
pub type PolicyAssets = Vec<PolicyAsset>;
1920

2021
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]

common/src/types.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
address::{Address, StakeAddress},
77
protocol_params,
88
rational_number::RationalNumber,
9+
ShelleyAddress,
910
};
1011
use anyhow::{anyhow, bail, Error, Result};
1112
use bech32::{Bech32, Hrp};
@@ -284,6 +285,9 @@ pub struct TxOutput {
284285
/// Tx hash
285286
pub tx_hash: TxHash,
286287

288+
/// Tx identifier for compact storage
289+
pub tx_identifier: TxIdentifier,
290+
287291
/// Output index in tx
288292
pub index: u64,
289293

@@ -338,6 +342,31 @@ pub type DataHash = Vec<u8>;
338342
/// Transaction hash
339343
pub type TxHash = [u8; 32];
340344

345+
/// Compact transaction identifier for index states
346+
#[derive(
347+
Clone, Default, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
348+
)]
349+
pub struct TxIdentifier([u8; 6]);
350+
351+
impl TxIdentifier {
352+
pub fn new(block_number: u32, tx_index: u16) -> Self {
353+
let mut buf = [0u8; 6];
354+
buf[..4].copy_from_slice(&block_number.to_be_bytes());
355+
buf[4..].copy_from_slice(&tx_index.to_be_bytes());
356+
Self(buf)
357+
}
358+
359+
/// Retrieve block number from TxIdentifier
360+
pub fn block_number(&self) -> u32 {
361+
u32::from_be_bytes(self.0[..4].try_into().unwrap())
362+
}
363+
364+
/// Retrieve transaction index from TxIdentifier
365+
pub fn tx_index(&self) -> u16 {
366+
u16::from_be_bytes(self.0[4..6].try_into().unwrap())
367+
}
368+
}
369+
341370
/// Block Hash
342371
pub type BlockHash = [u8; 32];
343372

@@ -1620,15 +1649,15 @@ pub enum TxCertificate {
16201649

16211650
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
16221651
pub struct AssetInfoRecord {
1623-
pub initial_mint_tx_hash: TxHash,
1652+
pub initial_mint_tx: TxIdentifier,
16241653
pub mint_or_burn_count: u64,
16251654
pub onchain_metadata: Option<Vec<u8>>,
16261655
pub metadata_standard: Option<AssetMetadataStandard>,
16271656
}
16281657

16291658
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
16301659
pub struct AssetMintRecord {
1631-
pub tx_hash: TxHash,
1660+
pub tx: TxIdentifier,
16321661
pub amount: u64,
16331662
pub burn: bool,
16341663
}
@@ -1649,6 +1678,12 @@ pub struct PolicyAsset {
16491678
pub quantity: u64,
16501679
}
16511680

1681+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1682+
pub struct AssetAddressEntry {
1683+
pub address: ShelleyAddress,
1684+
pub quantity: u64,
1685+
}
1686+
16521687
#[cfg(test)]
16531688
mod tests {
16541689
use super::*;

modules/assets_state/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Acropolis reward state module
1+
# Acropolis assets state module
22

33
[package]
44
name = "acropolis_module_assets_state"

modules/assets_state/src/assets_state.rs

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use crate::{
66
asset_registry::AssetRegistry,
7-
state::{AssetsStorageConfig, State},
7+
state::{AssetsStorageConfig, State, StoreTransactions},
88
};
99
use acropolis_common::{
1010
messages::{CardanoMessage, Message, StateQuery, StateQueryResponse},
@@ -26,12 +26,14 @@ const DEFAULT_ASSET_DELTAS_SUBSCRIBE_TOPIC: (&str, &str) =
2626
("asset-deltas-subscribe-topic", "cardano.asset.deltas");
2727
const DEFAULT_UTXO_DELTAS_SUBSCRIBE_TOPIC: (&str, &str) =
2828
("utxo-deltas-subscribe-topic", "cardano.utxo.deltas");
29+
const DEFAULT_ADDRESS_DELTAS_SUBSCRIBE_TOPIC: (&str, &str) =
30+
("address-deltas-subscribe-topic", "cardano.address.delta");
2931

3032
// Configuration defaults
3133
const DEFAULT_STORE_ASSETS: (&str, bool) = ("store-assets", false);
3234
const DEFAULT_STORE_INFO: (&str, bool) = ("store-info", false);
3335
const DEFAULT_STORE_HISTORY: (&str, bool) = ("store-history", false);
34-
const DEFAULT_STORE_TRANSACTIONS: (&str, bool) = ("store-transactions", false);
36+
const DEFAULT_STORE_TRANSACTIONS: (&str, &str) = ("store-transactions", "none");
3537
const DEFAULT_STORE_ADDRESSES: (&str, bool) = ("store-addresses", false);
3638
const DEFAULT_INDEX_BY_POLICY: (&str, bool) = ("index-by-policy", false);
3739

@@ -48,13 +50,18 @@ impl AssetsState {
4850
history: Arc<Mutex<StateHistory<State>>>,
4951
mut asset_deltas_subscription: Box<dyn Subscription<Message>>,
5052
mut utxo_deltas_subscription: Option<Box<dyn Subscription<Message>>>,
53+
mut address_deltas_subscription: Option<Box<dyn Subscription<Message>>>,
5154
storage_config: AssetsStorageConfig,
5255
registry: Arc<Mutex<AssetRegistry>>,
5356
) -> Result<()> {
5457
if let Some(sub) = utxo_deltas_subscription.as_mut() {
5558
let _ = sub.read().await?;
5659
info!("Consumed initial message from utxo_deltas_subscription");
5760
}
61+
if let Some(sub) = address_deltas_subscription.as_mut() {
62+
let _ = sub.read().await?;
63+
info!("Consumed initial message from address_deltas_subscription");
64+
}
5865
// Main loop of synchronised messages
5966
loop {
6067
// Get current state snapshot
@@ -115,20 +122,58 @@ impl AssetsState {
115122
CardanoMessage::UTXODeltas(utxo_deltas_msg),
116123
)) => {
117124
Self::check_sync(&current_block, block_info, "utxo");
118-
let mut reg = registry.lock().await;
119-
state =
120-
match state.handle_cip68_metadata(&utxo_deltas_msg.deltas, &mut *reg) {
125+
126+
if storage_config.store_info {
127+
let reg = registry.lock().await;
128+
state =
129+
match state.handle_cip68_metadata(&utxo_deltas_msg.deltas, &*reg) {
130+
Ok(new_state) => new_state,
131+
Err(e) => {
132+
error!("CIP-68 metadata handling error: {e:#}");
133+
state
134+
}
135+
};
136+
}
137+
138+
if storage_config.store_transactions.is_enabled() {
139+
let reg = registry.lock().await;
140+
state = match state.handle_transactions(&utxo_deltas_msg.deltas, &*reg)
141+
{
121142
Ok(new_state) => new_state,
122143
Err(e) => {
123-
error!("CIP-68 metadata handling error: {e:#}");
144+
error!("Transactions handling error: {e:#}");
124145
state
125146
}
126147
};
148+
}
127149
}
128150
other => error!("Unexpected message on utxo-deltas subscription: {other:?}"),
129151
}
130152
}
131153

154+
if let Some(sub) = address_deltas_subscription.as_mut() {
155+
let (_, address_msg) = sub.read().await?;
156+
match address_msg.as_ref() {
157+
Message::Cardano((
158+
ref block_info,
159+
CardanoMessage::AddressDeltas(address_deltas_msg),
160+
)) => {
161+
Self::check_sync(&current_block, block_info, "address");
162+
163+
let reg = registry.lock().await;
164+
state = match state.handle_address_deltas(&address_deltas_msg.deltas, &*reg)
165+
{
166+
Ok(new_state) => new_state,
167+
Err(e) => {
168+
error!("Address deltas handling error: {e:#}");
169+
state
170+
}
171+
};
172+
}
173+
other => error!("Unexpected message on address-deltas subscription: {other:?}"),
174+
}
175+
}
176+
132177
// Commit state
133178
{
134179
let mut h = history.lock().await;
@@ -165,12 +210,27 @@ impl AssetsState {
165210
config.get_string(key.0).unwrap_or_else(|_| key.1.to_string())
166211
}
167212

213+
fn get_transactions_flag(config: &Config, key: (&str, &str)) -> StoreTransactions {
214+
let val = get_string_flag(config, key);
215+
match val.as_str() {
216+
"none" => StoreTransactions::None,
217+
"all" => StoreTransactions::All,
218+
s => {
219+
if let Ok(n) = s.parse::<u64>() {
220+
StoreTransactions::Last(n)
221+
} else {
222+
StoreTransactions::None
223+
}
224+
}
225+
}
226+
}
227+
168228
// Get configuration flags and topis
169229
let storage_config = AssetsStorageConfig {
170230
store_assets: get_bool_flag(&config, DEFAULT_STORE_ASSETS),
171231
store_info: get_bool_flag(&config, DEFAULT_STORE_INFO),
172232
store_history: get_bool_flag(&config, DEFAULT_STORE_HISTORY),
173-
store_transactions: get_bool_flag(&config, DEFAULT_STORE_TRANSACTIONS),
233+
store_transactions: get_transactions_flag(&config, DEFAULT_STORE_TRANSACTIONS),
174234
store_addresses: get_bool_flag(&config, DEFAULT_STORE_ADDRESSES),
175235
index_by_policy: get_bool_flag(&config, DEFAULT_INDEX_BY_POLICY),
176236
};
@@ -180,13 +240,22 @@ impl AssetsState {
180240
info!("Creating subscriber on '{asset_deltas_subscribe_topic}'");
181241

182242
let utxo_deltas_subscribe_topic: Option<String> =
183-
if storage_config.store_info || storage_config.store_transactions {
243+
if storage_config.store_info || storage_config.store_transactions.is_enabled() {
184244
let topic = get_string_flag(&config, DEFAULT_UTXO_DELTAS_SUBSCRIBE_TOPIC);
185245
info!("Creating subscriber on '{topic}'");
186246
Some(topic)
187247
} else {
188248
None
189249
};
250+
251+
let address_deltas_subscribe_topic: Option<String> = if storage_config.store_addresses {
252+
let topic = get_string_flag(&config, DEFAULT_ADDRESS_DELTAS_SUBSCRIBE_TOPIC);
253+
info!("Creating subscriber on '{topic}'");
254+
Some(topic)
255+
} else {
256+
None
257+
};
258+
190259
let assets_query_topic = get_string_flag(&config, DEFAULT_ASSETS_QUERY_TOPIC);
191260
info!("Creating asset query handler on '{assets_query_topic}'");
192261

@@ -215,7 +284,10 @@ impl AssetsState {
215284
)));
216285
};
217286

218-
let state = history.lock().await.get_current_state();
287+
let state = {
288+
let h = history.lock().await;
289+
h.get_current_state()
290+
};
219291

220292
let response = match query {
221293
AssetsStateQuery::GetAssetsList => {
@@ -295,7 +367,7 @@ impl AssetsState {
295367
Err(e) => AssetsStateQueryResponse::Error(e.to_string()),
296368
},
297369
None => {
298-
if state.config.store_transactions {
370+
if state.config.store_transactions.is_enabled() {
299371
AssetsStateQueryResponse::NotFound
300372
} else {
301373
AssetsStateQueryResponse::Error(
@@ -354,13 +426,19 @@ impl AssetsState {
354426
} else {
355427
None
356428
};
429+
let address_deltas_sub = if let Some(topic) = &address_deltas_subscribe_topic {
430+
Some(context.subscribe(topic).await?)
431+
} else {
432+
None
433+
};
357434

358435
// Start run task
359436
context.run(async move {
360437
Self::run(
361438
history_run,
362439
asset_deltas_sub,
363440
utxo_deltas_sub,
441+
address_deltas_sub,
364442
storage_config,
365443
registry_run,
366444
)

0 commit comments

Comments
 (0)