Skip to content

Commit 7e1d573

Browse files
authored
add: ETCM-12255 bridge data source (#983)
1 parent c6e7622 commit 7e1d573

File tree

21 files changed

+1318
-27
lines changed

21 files changed

+1318
-27
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ partner-chains-db-sync-data-sources = { workspace = true, features = [
101101
"mc-hash",
102102
"sidechain-rpc",
103103
"block-participation",
104+
"bridge",
104105
] }
105106
partner-chains-mock-data-sources = { workspace = true, features = [
106107
"block-source",

demo/node/src/data_sources.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use pallet_sidechain_rpc::SidechainRpcDataSource;
33
use partner_chains_db_sync_data_sources::{
44
BlockDataSourceImpl, CandidatesDataSourceImpl, GovernedMapDataSourceCachedImpl,
55
McFollowerMetrics, McHashDataSourceImpl, NativeTokenManagementDataSourceImpl,
6-
SidechainRpcDataSourceImpl, StakeDistributionDataSourceImpl,
6+
SidechainRpcDataSourceImpl, StakeDistributionDataSourceImpl, TokenBridgeDataSourceImpl,
77
};
88
use partner_chains_demo_runtime::AccountId;
99
use partner_chains_mock_data_sources::{
@@ -151,6 +151,6 @@ pub async fn create_cached_db_sync_data_sources(
151151
)
152152
.await?,
153153
),
154-
bridge: Arc::new(TokenBridgeDataSourceMock::new()),
154+
bridge: Arc::new(TokenBridgeDataSourceImpl::new(pool, metrics_opt)),
155155
})
156156
}

toolkit/bridge/pallet/src/benchmarking.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use super::*;
22
use frame_benchmarking::v2::*;
3-
use frame_support::BoundedVec;
4-
use frame_support::assert_ok;
5-
use frame_support::traits::Get;
3+
use frame_support::{BoundedVec, assert_ok, traits::Get};
64
use frame_system::RawOrigin;
5+
use sidechain_domain::McBlockNumber;
76
use sp_partner_chains_bridge::*;
87

98
/// Helper trait for injecting mock values for use in benchmarks
@@ -26,7 +25,7 @@ impl<T: crate::Config> BenchmarkHelper<T> for () {
2625
}
2726

2827
fn data_checkpoint() -> BridgeDataCheckpoint {
29-
Default::default()
28+
BridgeDataCheckpoint::Block(McBlockNumber(0))
3029
}
3130
}
3231

toolkit/bridge/pallet/src/tests.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ fn main_chain_scripts() -> MainChainScripts {
2424
MainChainScripts {
2525
token_policy_id: PolicyId([1; 28]),
2626
token_asset_name: AssetName(bounded_vec![2;8]),
27-
illiquid_supply_validator_address: MainchainAddress::from_str("validator address").unwrap(),
27+
illiquid_circulation_supply_validator_address: MainchainAddress::from_str(
28+
"validator address",
29+
)
30+
.unwrap(),
2831
}
2932
}
3033

3134
fn data_checkpoint() -> BridgeDataCheckpoint {
32-
BridgeDataCheckpoint(UtxoId::new([1; 32], 3))
35+
BridgeDataCheckpoint::Utxo(UtxoId::new([1; 32], 3))
3336
}
3437

3538
mod set_main_chain_scripts {

toolkit/bridge/primitives/src/lib.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use alloc::vec::*;
88
use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
99
use scale_info::TypeInfo;
1010
use serde::{Deserialize, Serialize};
11-
use sidechain_domain::{AssetName, MainchainAddress, McBlockHash, PolicyId, UtxoId};
11+
use sidechain_domain::{AssetName, MainchainAddress, McBlockHash, McBlockNumber, PolicyId, UtxoId};
1212
use sp_inherents::*;
1313

1414
#[cfg(feature = "std")]
@@ -37,7 +37,7 @@ pub struct MainChainScripts {
3737
/// Address of the illiquid supply validator.
3838
///
3939
/// All tokens sent to that address are effectively locked and considered "sent" to the Partner Chain.
40-
pub illiquid_supply_validator_address: MainchainAddress,
40+
pub illiquid_circulation_supply_validator_address: MainchainAddress,
4141
}
4242

4343
/// Type containing all information needed to process a single transfer incoming from
@@ -115,20 +115,21 @@ pub enum TokenBridgeInherentDataProvider<RecipientAddress> {
115115
},
116116
}
117117

118-
/// Pointer to last data processed
118+
/// Value specifying the point in time up to which bridge transfers have been processed
119+
///
120+
/// This type is an enum wrapping either a block number or a utxo to handle both a case
121+
/// where all transfers up to a block have been handled and a case where there were more
122+
/// transfers than the limit allows and observability needs to pick up after the last
123+
/// utxo that could be observed
119124
#[derive(
120-
Default,
121-
Clone,
122-
Debug,
123-
Encode,
124-
Decode,
125-
DecodeWithMemTracking,
126-
TypeInfo,
127-
PartialEq,
128-
Eq,
129-
MaxEncodedLen,
125+
Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, PartialEq, Eq, MaxEncodedLen,
130126
)]
131-
pub struct BridgeDataCheckpoint(pub UtxoId);
127+
pub enum BridgeDataCheckpoint {
128+
/// Last transfer utxo that has been processed
129+
Utxo(UtxoId),
130+
/// Cardano block up to which data has been processed
131+
Block(McBlockNumber),
132+
}
132133

133134
/// Interface for data sources that can be used by [TokenBridgeInherentDataProvider]
134135
#[cfg(feature = "std")]

toolkit/data-sources/db-sync/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ partner-chains-plutus-data = { workspace = true }
5050
sp-block-participation = { workspace = true, features = [
5151
"std",
5252
], optional = true }
53+
sp-partner-chains-bridge = { workspace = true, optional = true, features = ["std"] }
5354

5455
[dev-dependencies]
5556
tokio-test = "0.4.3"
@@ -67,3 +68,4 @@ native-token = ["sp-native-token-management"]
6768
mc-hash = ["sidechain-mc-hash", "block-source"]
6869
sidechain-rpc = ["pallet-sidechain-rpc", "block-source"]
6970
block-participation = ["sp-block-participation"]
71+
bridge = ["sp-partner-chains-bridge"]
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//! Db-Sync data source used by the Partner Chain token bridge observability
2+
//!
3+
//! # Assumptions
4+
//!
5+
//! The data source implementation assumes that the utxos found at the illiquid circulating
6+
//! supply address conform to rules that are enforced by the Partner Chains smart contracts.
7+
//!
8+
//! Most importantly, transactions that spend any UTXOs from the ICS can only create at most
9+
//! one new UTXO at the ICS address. Conversely, transactions that create more than one UTXO
10+
//! at the illiquid supply address can only spend UTXOs from outside of it. This guarantees
11+
//! that the observability layer can always correctly identify the number of tokens transfered
12+
//! by calculating the delta of `tokens in the new UTXO` - `tokens in the old ICS UTXOs`.
13+
14+
use crate::McFollowerMetrics;
15+
use crate::db_model::*;
16+
use crate::observed_async_trait;
17+
use partner_chains_plutus_data::bridge::TokenTransferDatum;
18+
use partner_chains_plutus_data::bridge::TokenTransferDatumV1;
19+
use sidechain_domain::McBlockHash;
20+
use sp_partner_chains_bridge::*;
21+
use sqlx::PgPool;
22+
use std::fmt::Debug;
23+
24+
#[cfg(test)]
25+
mod tests;
26+
27+
/// Db-Sync data source serving data for Partner Chains token bridge
28+
pub struct TokenBridgeDataSourceImpl {
29+
/// Postgres connection pool
30+
pool: PgPool,
31+
/// Prometheus metrics client
32+
metrics_opt: Option<McFollowerMetrics>,
33+
/// Configuration used by Db-Sync
34+
db_sync_config: DbSyncConfigurationProvider,
35+
}
36+
37+
impl TokenBridgeDataSourceImpl {
38+
/// Crates a new token bridge data source
39+
pub fn new(pool: PgPool, metrics_opt: Option<McFollowerMetrics>) -> Self {
40+
Self { db_sync_config: DbSyncConfigurationProvider::new(pool.clone()), pool, metrics_opt }
41+
}
42+
}
43+
44+
observed_async_trait!(
45+
impl<RecipientAddress> TokenBridgeDataSource<RecipientAddress> for TokenBridgeDataSourceImpl
46+
where
47+
RecipientAddress: Debug,
48+
RecipientAddress: (for<'a> TryFrom<&'a [u8]>),
49+
{
50+
async fn get_transfers(
51+
&self,
52+
main_chain_scripts: MainChainScripts,
53+
data_checkpoint: BridgeDataCheckpoint,
54+
max_transfers: u32,
55+
current_mc_block_hash: McBlockHash,
56+
) -> Result<
57+
(Vec<BridgeTransferV1<RecipientAddress>>, BridgeDataCheckpoint),
58+
Box<dyn std::error::Error + Send + Sync>,
59+
> {
60+
let asset = Asset {
61+
policy_id: main_chain_scripts.token_policy_id.into(),
62+
asset_name: main_chain_scripts.token_asset_name.into(),
63+
};
64+
65+
let current_mc_block = get_block_by_hash(&self.pool, current_mc_block_hash.clone())
66+
.await?
67+
.ok_or(format!("Could not find block for hash {current_mc_block_hash:?}"))?;
68+
69+
let data_checkpoint = match data_checkpoint {
70+
BridgeDataCheckpoint::Utxo(utxo) => {
71+
let TxBlockInfo { block_number, tx_ix } =
72+
get_block_info_for_utxo(&self.pool, utxo.tx_hash.into()).await?.ok_or(
73+
format!(
74+
"Could not find block info for data checkpoint: {data_checkpoint:?}"
75+
),
76+
)?;
77+
BridgeCheckpoint::Utxo {
78+
block_number: block_number.0,
79+
tx_ix: tx_ix.0,
80+
tx_out_ix: utxo.index.0,
81+
}
82+
},
83+
BridgeDataCheckpoint::Block(checkpoint_block_number) => {
84+
BridgeCheckpoint::Block { number: checkpoint_block_number.0 }
85+
},
86+
};
87+
88+
let utxos = get_bridge_utxos_tx(
89+
self.db_sync_config.get_tx_in_config().await?,
90+
&self.pool,
91+
&main_chain_scripts.illiquid_circulation_supply_validator_address.into(),
92+
asset,
93+
data_checkpoint,
94+
current_mc_block.block_no,
95+
max_transfers,
96+
)
97+
.await?;
98+
99+
let new_checkpoint = match utxos.last() {
100+
None => BridgeDataCheckpoint::Block(current_mc_block.block_no.into()),
101+
Some(_) if (utxos.len() as u32) < max_transfers => {
102+
BridgeDataCheckpoint::Block(current_mc_block.block_no.into())
103+
},
104+
Some(utxo) => BridgeDataCheckpoint::Utxo(utxo.utxo_id()),
105+
};
106+
107+
let transfers = utxos.into_iter().flat_map(utxo_to_transfer).collect();
108+
109+
Ok((transfers, new_checkpoint))
110+
}
111+
}
112+
);
113+
114+
fn utxo_to_transfer<RecipientAddress>(
115+
utxo: BridgeUtxo,
116+
) -> Option<BridgeTransferV1<RecipientAddress>>
117+
where
118+
RecipientAddress: for<'a> TryFrom<&'a [u8]>,
119+
{
120+
let token_delta = (utxo.tokens_out.0 as i128) - (utxo.tokens_in.0 as i128);
121+
122+
if token_delta <= 0 {
123+
return None;
124+
}
125+
126+
let token_amount = token_delta as u64;
127+
128+
let transfer = match TokenTransferDatum::try_from(utxo.datum.0.clone()) {
129+
Ok(TokenTransferDatum::V1(TokenTransferDatumV1::UserTransfer { receiver })) => {
130+
match RecipientAddress::try_from(receiver.0.as_ref()) {
131+
Ok(recipient) => BridgeTransferV1::UserTransfer { token_amount, recipient },
132+
Err(_) => {
133+
BridgeTransferV1::InvalidTransfer { token_amount, utxo_id: utxo.utxo_id() }
134+
},
135+
}
136+
},
137+
Ok(TokenTransferDatum::V1(TokenTransferDatumV1::ReserveTransfer)) => {
138+
BridgeTransferV1::ReserveTransfer { token_amount }
139+
},
140+
Err(_) => BridgeTransferV1::InvalidTransfer { token_amount, utxo_id: utxo.utxo_id() },
141+
};
142+
143+
Some(transfer)
144+
}

0 commit comments

Comments
 (0)