Skip to content

Commit 6fe67d0

Browse files
authored
Restore IFA support on non-mainnet networks to match rgb-lib behaviour (#93)
1 parent 3b12a3a commit 6fe67d0

6 files changed

Lines changed: 192 additions & 4 deletions

File tree

src/error.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ pub enum APIError {
361361
#[error("Layer 1 {0} is not supported")]
362362
UnsupportedLayer1(String),
363363

364+
#[error("Asset schema {0} is not supported on this network")]
365+
UnsupportedSchema(String),
366+
364367
#[error("Transport type is not supported")]
365368
UnsupportedTransportType,
366369

@@ -479,6 +482,9 @@ impl From<RgbLibError> for APIError {
479482
APIError::UnsupportedInflation(format!("{asset_schema}"))
480483
}
481484
RgbLibError::UnsupportedLayer1 { layer_1 } => APIError::UnsupportedLayer1(layer_1),
485+
RgbLibError::UnsupportedSchema { asset_schema } => {
486+
APIError::UnsupportedSchema(format!("{asset_schema}"))
487+
}
482488
RgbLibError::UnsupportedTransportType => APIError::UnsupportedTransportType,
483489
_ => {
484490
tracing::debug!("Unexpected rgb-lib error: {error:?}");
@@ -601,6 +607,7 @@ impl IntoResponse for APIError {
601607
| APIError::UnlockedNode
602608
| APIError::UnsupportedInflation(_)
603609
| APIError::UnsupportedLayer1(_)
610+
| APIError::UnsupportedSchema(_)
604611
| APIError::UnsupportedTransportType
605612
| APIError::UnsupportedInExternalSignerMode(_) => {
606613
(StatusCode::FORBIDDEN, self.to_string(), self.name())
@@ -700,3 +707,20 @@ impl IntoResponse for AuthError {
700707
}
701708
}
702709
}
710+
711+
#[cfg(test)]
712+
mod tests {
713+
use super::*;
714+
715+
#[test]
716+
fn unsupported_schema_maps_to_dedicated_error() {
717+
let err = APIError::from(RgbLibError::UnsupportedSchema {
718+
asset_schema: rgb_lib::AssetSchema::Ifa,
719+
});
720+
assert!(matches!(err, APIError::UnsupportedSchema(_)));
721+
assert_eq!(
722+
err.to_string(),
723+
"Asset schema Ifa is not supported on this network"
724+
);
725+
}
726+
}

src/ldk.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,11 +1681,10 @@ async fn handle_ldk_events(
16811681
let channel_rgb_amount = rgb_info.local_rgb_amount;
16821682
let asset_id = rgb_info.contract_id.to_string();
16831683
let assignment = match rgb_info.schema {
1684-
AssetSchema::Nia | AssetSchema::Cfa => {
1684+
AssetSchema::Nia | AssetSchema::Cfa | AssetSchema::Ifa => {
16851685
Assignment::Fungible(channel_rgb_amount)
16861686
}
16871687
AssetSchema::Uda => Assignment::NonFungible,
1688-
AssetSchema::Ifa => todo!(),
16891688
};
16901689
let recipient_id =
16911690
recipient_id_from_script_buf(script_buf, static_state.network);
@@ -3649,6 +3648,15 @@ pub(crate) fn select_chain_backend(
36493648
}
36503649
}
36513650

3651+
// rgb-lib rejects wallets supporting IFA on mainnet
3652+
fn supported_asset_schemas(bitcoin_network: BitcoinNetwork) -> Vec<AssetSchema> {
3653+
let mut schemas = vec![AssetSchema::Nia, AssetSchema::Cfa, AssetSchema::Uda];
3654+
if bitcoin_network != BitcoinNetwork::Mainnet {
3655+
schemas.push(AssetSchema::Ifa);
3656+
}
3657+
schemas
3658+
}
3659+
36523660
pub(crate) async fn start_ldk(
36533661
app_state: Arc<AppState>,
36543662
key_source: NodeKeySource,
@@ -4270,7 +4278,7 @@ pub(crate) async fn start_ldk(
42704278
bitcoin_network,
42714279
database_type: DatabaseType::Sqlite,
42724280
max_allocations_per_utxo: 1,
4273-
supported_schemas: vec![AssetSchema::Nia, AssetSchema::Cfa, AssetSchema::Uda],
4281+
supported_schemas: supported_asset_schemas(bitcoin_network),
42744282
reuse_addresses,
42754283
},
42764284
keys,
@@ -5520,4 +5528,22 @@ mod tests {
55205528
assert_eq!(km.get_peer_storage_key().inner, b);
55215529
assert_eq!(km.get_receive_auth_key().0, c);
55225530
}
5531+
5532+
#[test]
5533+
fn ifa_supported_on_all_networks_but_mainnet() {
5534+
assert!(!supported_asset_schemas(BitcoinNetwork::Mainnet).contains(&AssetSchema::Ifa));
5535+
for network in [
5536+
BitcoinNetwork::Testnet,
5537+
BitcoinNetwork::Testnet4,
5538+
BitcoinNetwork::Signet,
5539+
BitcoinNetwork::SignetCustom,
5540+
BitcoinNetwork::Regtest,
5541+
] {
5542+
let schemas = supported_asset_schemas(network);
5543+
assert!(schemas.contains(&AssetSchema::Ifa));
5544+
assert!(schemas.contains(&AssetSchema::Nia));
5545+
assert!(schemas.contains(&AssetSchema::Cfa));
5546+
assert!(schemas.contains(&AssetSchema::Uda));
5547+
}
5548+
}
55235549
}

src/test/ifa_channel.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use super::*;
2+
3+
const TEST_DIR_BASE: &str = "tmp/ifa_channel/";
4+
5+
#[serial_test::serial]
6+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
7+
#[traced_test]
8+
async fn open_pay_close() {
9+
initialize();
10+
11+
let test_dir_base = format!("{TEST_DIR_BASE}open_pay_close/");
12+
let test_dir_node1 = format!("{test_dir_base}node1");
13+
let test_dir_node2 = format!("{test_dir_base}node2");
14+
let (node1_addr, _) = start_node(&test_dir_node1, NODE1_PEER_PORT, false).await;
15+
let (node2_addr, _) = start_node(&test_dir_node2, NODE2_PEER_PORT, false).await;
16+
17+
fund_and_create_utxos(node1_addr, None).await;
18+
fund_and_create_utxos(node2_addr, None).await;
19+
20+
let asset_id = issue_asset_ifa(node1_addr).await.asset_id;
21+
22+
let node2_pubkey = node_info(node2_addr).await.pubkey;
23+
let channel = open_channel(
24+
node1_addr,
25+
&node2_pubkey,
26+
Some(NODE2_PEER_PORT),
27+
None,
28+
Some(3500000),
29+
Some(600),
30+
Some(&asset_id),
31+
)
32+
.await;
33+
assert_eq!(asset_balance_spendable(node1_addr, &asset_id).await, 400);
34+
35+
let asset_amount = Some(100);
36+
let LNInvoiceResponse { invoice } =
37+
ln_invoice(node2_addr, None, Some(&asset_id), asset_amount, 900).await;
38+
send_payment(node1_addr, invoice.clone()).await;
39+
40+
let decoded = decode_ln_invoice(node1_addr, &invoice).await;
41+
let payment = get_payment(node1_addr, &decoded.payment_hash, PaymentType::Outbound).await;
42+
assert_eq!(payment.asset_id, Some(asset_id.clone()));
43+
assert_eq!(payment.asset_amount, asset_amount);
44+
assert_eq!(payment.status, HTLCStatus::Succeeded);
45+
let payment = get_payment(
46+
node2_addr,
47+
&decoded.payment_hash,
48+
PaymentType::InboundAutoClaim,
49+
)
50+
.await;
51+
assert_eq!(payment.asset_id, Some(asset_id.clone()));
52+
assert_eq!(payment.asset_amount, asset_amount);
53+
assert_eq!(payment.status, HTLCStatus::Succeeded);
54+
55+
close_channel(node1_addr, &channel.channel_id, &node2_pubkey, false).await;
56+
wait_for_balance(node1_addr, &asset_id, 900).await;
57+
wait_for_balance(node2_addr, &asset_id, 100).await;
58+
}
59+
60+
#[serial_test::serial]
61+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
62+
#[traced_test]
63+
async fn virtual_open_pay_close() {
64+
initialize();
65+
66+
let test_dir_base = format!("{TEST_DIR_BASE}virtual_open_pay_close/");
67+
let host_peer_port = next_peer_port();
68+
let client_peer_port = next_peer_port();
69+
70+
let (host_addr, _) = start_node_with_virtual_options(
71+
&format!("{test_dir_base}host_node"),
72+
host_peer_port,
73+
false,
74+
true,
75+
vec![],
76+
)
77+
.await;
78+
let host_pubkey = node_info(host_addr).await.pubkey;
79+
80+
fund_and_create_utxos(host_addr, None).await;
81+
let asset_id = issue_asset_ifa(host_addr).await.asset_id;
82+
83+
let (client_addr, _) = start_node_with_virtual_options(
84+
&format!("{test_dir_base}client_node"),
85+
client_peer_port,
86+
false,
87+
true,
88+
vec![bitcoin::secp256k1::PublicKey::from_str(&host_pubkey).unwrap()],
89+
)
90+
.await;
91+
let client_pubkey = node_info(client_addr).await.pubkey;
92+
93+
let channel = open_virtual_channel(
94+
host_addr,
95+
&client_pubkey,
96+
Some(client_peer_port),
97+
Some(100_000),
98+
Some(0),
99+
Some(600),
100+
Some(&asset_id),
101+
None,
102+
)
103+
.await;
104+
assert_eq!(channel.asset_id.as_deref(), Some(asset_id.as_str()));
105+
106+
let asset_amount = Some(100);
107+
let LNInvoiceResponse { invoice } =
108+
ln_invoice(client_addr, None, Some(&asset_id), asset_amount, 900).await;
109+
let payment = send_payment(host_addr, invoice).await;
110+
assert_eq!(payment.asset_id, Some(asset_id.clone()));
111+
assert_eq!(payment.asset_amount, asset_amount);
112+
assert_eq!(payment.status, HTLCStatus::Succeeded);
113+
wait_for_ln_balance(host_addr, &asset_id, 500).await;
114+
wait_for_ln_balance(client_addr, &asset_id, 100).await;
115+
116+
// virtual close requires the counterparty balance back at zero
117+
let LNInvoiceResponse { invoice } = ln_invoice(
118+
host_addr,
119+
Some(3_000_000),
120+
Some(&asset_id),
121+
asset_amount,
122+
900,
123+
)
124+
.await;
125+
let payment = send_payment(client_addr, invoice).await;
126+
assert_eq!(payment.status, HTLCStatus::Succeeded);
127+
wait_for_ln_balance(host_addr, &asset_id, 600).await;
128+
wait_for_ln_balance(client_addr, &asset_id, 0).await;
129+
130+
close_channel(host_addr, &channel.channel_id, &client_pubkey, false).await;
131+
132+
shutdown(&[host_addr, client_addr]).await;
133+
}

src/test/inflate.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const TEST_DIR_BASE: &str = "tmp/inflate/";
55
#[serial_test::serial]
66
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
77
#[traced_test]
8-
#[ignore = "IFA removed from supported_schemas"]
98
async fn success() {
109
initialize();
1110

src/test/issue.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,26 @@ async fn issue() {
2626

2727
// issue assets
2828
let asset_cfa = issue_asset_cfa(node1_addr, Some(file_path)).await;
29+
let asset_ifa = issue_asset_ifa(node1_addr).await;
2930
let asset_nia = issue_asset_nia(node1_addr).await;
3031
let asset_uda = issue_asset_uda(node1_addr, Some(file_path)).await;
3132

3233
// check /listassets
3334
let assets = list_assets(node1_addr).await;
3435
let assets_cfa = assets.cfa.unwrap();
36+
let assets_ifa = assets.ifa.unwrap();
3537
let assets_nia = assets.nia.unwrap();
3638
let assets_uda = assets.uda.unwrap();
3739
assert_eq!(assets_cfa.len(), 1);
40+
assert_eq!(assets_ifa.len(), 1);
3841
assert_eq!(assets_nia.len(), 1);
3942
assert_eq!(assets_uda.len(), 1);
4043
let cfa_asset = assets_cfa.first().unwrap();
44+
let ifa_asset = assets_ifa.first().unwrap();
4145
let nia_asset = assets_nia.first().unwrap();
4246
let uda_asset = assets_uda.first().unwrap();
4347
assert_eq!(cfa_asset.asset_id, asset_cfa.asset_id);
48+
assert_eq!(ifa_asset.asset_id, asset_ifa.asset_id);
4449
assert_eq!(nia_asset.asset_id, asset_nia.asset_id);
4550
assert_eq!(uda_asset.asset_id, asset_uda.asset_id);
4651

src/test/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2935,6 +2935,7 @@ mod gossip_p2p;
29352935
mod gossip_rgs;
29362936
mod hodl_invoice;
29372937
mod htlc_amount_checks;
2938+
mod ifa_channel;
29382939
mod inflate;
29392940
mod init;
29402941
mod init_electrum;

0 commit comments

Comments
 (0)