|
1 | | -use crate::Result; |
| 1 | +use crate::{ |
| 2 | + Result, |
| 3 | + client::{MiniBFClient, api::MiniBFApi}, |
| 4 | +}; |
2 | 5 | use async_trait::async_trait; |
| 6 | +use cardano_serialization_lib::PlutusData; |
| 7 | +use partner_chains_plutus_data::governed_map::GovernedMapDatum; |
3 | 8 | use sidechain_domain::byte_string::ByteString; |
4 | 9 | use sidechain_domain::*; |
5 | 10 | use sp_governed_map::{GovernedMapDataSource, MainChainScriptsV1}; |
| 11 | +use std::collections::BTreeMap; |
6 | 12 |
|
7 | | -#[derive(Debug, Default)] |
8 | | -pub struct GovernedMapDataSourceImpl {} |
| 13 | +pub struct GovernedMapDataSourceImpl { |
| 14 | + client: MiniBFClient, |
| 15 | +} |
| 16 | + |
| 17 | +impl GovernedMapDataSourceImpl { |
| 18 | + pub fn new(client: MiniBFClient) -> Self { |
| 19 | + Self { client } |
| 20 | + } |
| 21 | +} |
9 | 22 |
|
10 | 23 | #[async_trait] |
11 | 24 | impl GovernedMapDataSource for GovernedMapDataSourceImpl { |
| 25 | + async fn get_state_at_block( |
| 26 | + &self, |
| 27 | + mc_block: McBlockHash, |
| 28 | + main_chain_scripts: MainChainScriptsV1, |
| 29 | + ) -> Result<BTreeMap<String, ByteString>> { |
| 30 | + // Get the block to ensure it exists and get its number |
| 31 | + let block = self.client.blocks_by_id(mc_block.clone()).await?; |
| 32 | + let block_number = |
| 33 | + McBlockNumber(block.height.unwrap_or_default().try_into().unwrap_or(0u32)); |
| 34 | + |
| 35 | + // Get all UTXOs at the governed map validator address |
| 36 | + let utxos = self |
| 37 | + .client |
| 38 | + .addresses_utxos(main_chain_scripts.validator_address.clone()) |
| 39 | + .await |
| 40 | + .unwrap_or_default(); |
| 41 | + |
| 42 | + // Filter UTXOs that: |
| 43 | + // 1. Contain the governed map asset |
| 44 | + // 2. Were created before or at the target block |
| 45 | + let asset_unit = format_asset_unit(&main_chain_scripts.asset_policy_id); |
| 46 | + let mut mappings = BTreeMap::new(); |
| 47 | + |
| 48 | + for utxo in utxos { |
| 49 | + // Check if this UTXO was created before or at target block |
| 50 | + let tx_hash = match McTxHash::decode_hex(&utxo.tx_hash) { |
| 51 | + Ok(hash) => hash, |
| 52 | + Err(e) => { |
| 53 | + log::warn!("Failed to decode tx_hash '{}': {}", utxo.tx_hash, e); |
| 54 | + continue; |
| 55 | + }, |
| 56 | + }; |
| 57 | + let tx = self.client.transaction_by_hash(tx_hash).await?; |
| 58 | + let utxo_block_height = tx.block_height as u32; |
| 59 | + |
| 60 | + if utxo_block_height > block_number.0 { |
| 61 | + continue; |
| 62 | + } |
| 63 | + |
| 64 | + // Check if UTXO contains the governed map asset |
| 65 | + let has_asset = utxo.amount.iter().any(|a| a.unit == asset_unit); |
| 66 | + if !has_asset { |
| 67 | + continue; |
| 68 | + } |
| 69 | + |
| 70 | + // Parse the datum |
| 71 | + if let Some(datum_hex) = &utxo.inline_datum { |
| 72 | + if let Some((key, value)) = parse_governed_map_datum(datum_hex) { |
| 73 | + mappings.insert(key, value); |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + Ok(mappings) |
| 79 | + } |
| 80 | + |
12 | 81 | async fn get_mapping_changes( |
13 | 82 | &self, |
14 | | - _since_mc_block: Option<McBlockHash>, |
15 | | - _up_to_mc_block: McBlockHash, |
16 | | - _scripts: MainChainScriptsV1, |
| 83 | + since_mc_block: Option<McBlockHash>, |
| 84 | + up_to_mc_block: McBlockHash, |
| 85 | + scripts: MainChainScriptsV1, |
17 | 86 | ) -> Result<Vec<(String, Option<ByteString>)>> { |
18 | | - Err("not implemented".into()) |
| 87 | + // Get current state at up_to_mc_block |
| 88 | + let current_mappings = self.get_state_at_block(up_to_mc_block, scripts.clone()).await?; |
| 89 | + |
| 90 | + // If no since_mc_block, return all current mappings as additions |
| 91 | + let Some(since_mc_block) = since_mc_block else { |
| 92 | + let changes = |
| 93 | + current_mappings.into_iter().map(|(key, value)| (key, Some(value))).collect(); |
| 94 | + return Ok(changes); |
| 95 | + }; |
| 96 | + |
| 97 | + // Get previous state at since_mc_block |
| 98 | + let previous_mappings = self.get_state_at_block(since_mc_block, scripts).await?; |
| 99 | + |
| 100 | + // Calculate changes |
| 101 | + let mut changes = Vec::new(); |
| 102 | + |
| 103 | + // Find additions and modifications |
| 104 | + for (key, value) in current_mappings.iter() { |
| 105 | + if previous_mappings.get(key) != Some(value) { |
| 106 | + changes.push((key.clone(), Some(value.clone()))); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // Find deletions |
| 111 | + for key in previous_mappings.keys() { |
| 112 | + if !current_mappings.contains_key(key) { |
| 113 | + changes.push((key.clone(), None)); |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + Ok(changes) |
19 | 118 | } |
| 119 | +} |
20 | 120 |
|
21 | | - async fn get_state_at_block( |
22 | | - &self, |
23 | | - _mc_block: McBlockHash, |
24 | | - _main_chain_scripts: MainChainScriptsV1, |
25 | | - ) -> Result<BTreeMap<String, ByteString>> { |
26 | | - Err("not implemented".into()) |
| 121 | +fn format_asset_unit(policy_id: &PolicyId) -> String { |
| 122 | + // Asset unit format in blockfrost is policy_id + asset_name (hex) |
| 123 | + // For empty asset names, it's just the policy_id without "0x" prefix |
| 124 | + policy_id.to_hex_string()[2..].to_string() |
| 125 | +} |
| 126 | + |
| 127 | +/// Helper function to parse GovernedMapDatum from hex-encoded PlutusData |
| 128 | +fn parse_governed_map_datum(datum_hex: &str) -> Option<(String, ByteString)> { |
| 129 | + match PlutusData::from_hex(datum_hex) { |
| 130 | + Ok(plutus_data) => match GovernedMapDatum::try_from(plutus_data) { |
| 131 | + Ok(GovernedMapDatum { key, value }) => Some((key, value)), |
| 132 | + Err(err) => { |
| 133 | + log::warn!("Failed to parse GovernedMapDatum: {}", err); |
| 134 | + None |
| 135 | + }, |
| 136 | + }, |
| 137 | + Err(err) => { |
| 138 | + log::warn!("Failed to parse PlutusData from hex: {}", err); |
| 139 | + None |
| 140 | + }, |
27 | 141 | } |
28 | 142 | } |
0 commit comments