Skip to content

Commit 807b151

Browse files
ETCM-12351 dolos governed map data source (#1117)
Co-authored-by: ladamesny <[email protected]>
1 parent fec56a3 commit 807b151

File tree

4 files changed

+149
-28
lines changed

4 files changed

+149
-28
lines changed

demo/node/src/data_sources.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,9 @@ pub async fn create_dolos_data_sources(
134134
dolos_client.clone(),
135135
),
136136
),
137-
governed_map: Arc::new(
138-
partner_chains_db_sync_data_sources::GovernedMapDataSourceCachedImpl::new(
139-
pool.clone(),
140-
metrics_opt.clone(),
141-
GOVERNED_MAP_CACHE_SIZE,
142-
block_dbsync.clone(),
143-
)
144-
.await?,
145-
),
137+
governed_map: Arc::new(partner_chains_dolos_data_sources::GovernedMapDataSourceImpl::new(
138+
dolos_client.clone(),
139+
)),
146140
bridge: Arc::new(
147141
partner_chains_db_sync_data_sources::CachedTokenBridgeDataSourceImpl::new(
148142
pool,

dev/local-environment/setup.sh

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,11 @@ choose_deployment_option() {
324324
echo "1) Include only Cardano testnet"
325325
echo "2) Include Cardano testnet with Ogmios"
326326
echo "3) Include Cardano testnet, Ogmios, DB-Sync and Postgres"
327-
echo "4) Deploy a single Partner Chains node with network_mode: "host" for external connections (adjust partner-chains-external-node.txt before running this script)"
327+
echo "4) Deploy a single Partner Chains node with network_mode: \"host\" for external connections (adjust partner-chains-external-node.txt before running this script)"
328328
echo "5) Deploy a 3 node Partner Chain network using wizard"
329-
echo "6) Include Cardano testnet, Ogmios, and Dolos"
330-
read -p "Enter your choice (1/2/3/4/5/6): " deployment_option
329+
echo "6) Include Cardano testnet, Ogmios, DB-Sync, Postgres, Dolos and Partner Chains nodes with Dolos"
330+
echo "7) Include Cardano testnet, Ogmios, Dolos (NO DB-Sync/Postgres) and Partner Chains nodes with Dolos"
331+
read -p "Enter your choice (1/2/3/4/5/6/7): " deployment_option
331332
else
332333
deployment_option=0
333334
fi
@@ -391,6 +392,14 @@ create_docker_compose() {
391392
cat ./modules/partner-chains-nodes-dolos.txt >> docker-compose.yml
392393
cat ./modules/partner-chains-setup.txt >> docker-compose.yml
393394
;;
395+
7)
396+
echo -e "Including Cardano testnet, Ogmios, Dolos (no DB-Sync/Postgres) and Partner Chains nodes with Dolos.\n"
397+
cat ./modules/cardano.txt >> docker-compose.yml
398+
cat ./modules/ogmios.txt >> docker-compose.yml
399+
cat ./modules/dolos.txt >> docker-compose.yml
400+
cat ./modules/partner-chains-nodes-dolos.txt >> docker-compose.yml
401+
cat ./modules/partner-chains-setup.txt >> docker-compose.yml
402+
;;
394403
0)
395404
echo -e "Including all services.\n"
396405
cat ./modules/cardano.txt >> docker-compose.yml
@@ -427,11 +436,11 @@ parse_arguments() {
427436
shift
428437
;;
429438
-d|--deployment-option)
430-
if [[ -n "$2" && "$2" =~ ^[1-6]$ ]]; then
439+
if [[ -n "$2" && "$2" =~ ^[1-7]$ ]]; then
431440
deployment_option="$2"
432441
shift 2
433442
else
434-
echo "Error: Invalid deployment option '$2'. Valid options are 1, 2, 3, 4, 5 or 6."
443+
echo "Error: Invalid deployment option '$2'. Valid options are 1 to 7."
435444
exit 1
436445
fi
437446
;;
@@ -462,7 +471,7 @@ parse_arguments() {
462471
echo "Usage: $0 [OPTION]..."
463472
echo "Initialize and configure the Docker environment."
464473
echo " -n, --non-interactive Run with no interactive prompts and accept sensible default configuration settings."
465-
echo " -d, --deployment-option Specify one of the custom deployment options (1, 2, 3, or 4)."
474+
echo " -d, --deployment-option Specify one of the custom deployment options (1 to 7)."
466475
echo " -p, --postgres-password Set a specific password for PostgreSQL (overrides automatic generation)."
467476
echo " -i, --node-image Specify a custom Partner Chains Node image."
468477
echo " -t, --tests Include tests container."
Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,142 @@
1-
use crate::Result;
1+
use crate::{
2+
Result,
3+
client::{MiniBFClient, api::MiniBFApi},
4+
};
25
use async_trait::async_trait;
6+
use cardano_serialization_lib::PlutusData;
7+
use partner_chains_plutus_data::governed_map::GovernedMapDatum;
38
use sidechain_domain::byte_string::ByteString;
49
use sidechain_domain::*;
510
use sp_governed_map::{GovernedMapDataSource, MainChainScriptsV1};
11+
use std::collections::BTreeMap;
612

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+
}
922

1023
#[async_trait]
1124
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+
1281
async fn get_mapping_changes(
1382
&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,
1786
) -> 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)
19118
}
119+
}
20120

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+
},
27141
}
28142
}

toolkit/data-sources/dolos/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ mod bridge;
2828
#[cfg(feature = "bridge")]
2929
pub use bridge::TokenBridgeDataSourceImpl;
3030

31+
#[cfg(feature = "block-source")]
3132
mod block;
33+
#[cfg(feature = "block-source")]
3234
pub use block::BlockDataSourceImpl;
35+
36+
#[cfg(feature = "block-source")]
3337
use sidechain_domain::mainchain_epoch::MainchainEpochConfig;
3438

3539
use crate::client::MiniBFClient;

0 commit comments

Comments
 (0)