diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml index ed182292d65..6a77d0dee01 100644 --- a/lightning-invoice/Cargo.toml +++ b/lightning-invoice/Cargo.toml @@ -29,7 +29,10 @@ hashbrown = { version = "0.8", optional = true } bitcoin = { version = "0.29.0", default-features = false } # RGB and related -rgb-std = { version = "=0.11.0-beta.5", default-features = false } +rgb-lib = { version = "0.3.0-alpha.3", features = [ + "electrum", + "esplora", +] } [dev-dependencies] lightning = { version = "0.0.118", path = "../lightning", default-features = false, features = ["_test_utils"] } diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs index 67f2e235e9e..aad6140ac02 100644 --- a/lightning-invoice/src/de.rs +++ b/lightning-invoice/src/de.rs @@ -13,7 +13,7 @@ use bitcoin::{PubkeyHash, ScriptHash}; use bitcoin::util::address::WitnessVersion; use bitcoin_hashes::Hash; use bitcoin_hashes::sha256; -use rgbstd::contract::ContractId; +use rgb_lib::ContractId; use crate::prelude::*; use lightning::ln::PaymentSecret; use lightning::routing::gossip::RoutingFees; diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs index 344abb6f0b7..5bd2c242135 100644 --- a/lightning-invoice/src/lib.rs +++ b/lightning-invoice/src/lib.rs @@ -51,7 +51,7 @@ use bitcoin_hashes::{Hash, sha256}; use lightning::ln::features::Bolt11InvoiceFeatures; use lightning::util::invoice::construct_invoice_preimage; -use rgbstd::contract::ContractId; +use rgb_lib::ContractId; use secp256k1::PublicKey; use secp256k1::{Message, Secp256k1}; use secp256k1::ecdsa::RecoverableSignature; diff --git a/lightning-invoice/src/utils.rs b/lightning-invoice/src/utils.rs index ad9c06a19d4..44928c6209f 100644 --- a/lightning-invoice/src/utils.rs +++ b/lightning-invoice/src/utils.rs @@ -15,7 +15,7 @@ use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey}; use lightning::routing::gossip::RoutingFees; use lightning::routing::router::{RouteHint, RouteHintHop, Router}; use lightning::util::logger::Logger; -use rgbstd::contract::ContractId; +use rgb_lib::ContractId; use secp256k1::PublicKey; use core::ops::Deref; use core::time::Duration; diff --git a/lightning/Cargo.toml b/lightning/Cargo.toml index c77ee72f0ff..54418a9c01b 100644 --- a/lightning/Cargo.toml +++ b/lightning/Cargo.toml @@ -40,8 +40,11 @@ grind_signatures = [] default = ["std", "grind_signatures"] [dependencies] -bitcoin_30 = { package = "bitcoin", version = "0.30.0", default-features = false, features = ["secp-recovery", "base64"] } -bitcoin = { package = "bitcoin", version = "0.29.0", default-features = false, features = ["base64", "secp-recovery", "serde"] } +bitcoin = { package = "bitcoin", version = "0.29.0", default-features = false, features = [ + "base64", + "secp-recovery", + "serde", +] } hashbrown = { version = "0.8", optional = true } regex = { version = "1.5.6", optional = true } @@ -50,31 +53,20 @@ backtrace = { version = "0.3", optional = true } core2 = { version = "0.3.0", optional = true, default-features = false } # RGB and related -amplify = { version = "=4.6.0", default-features = false } -base64 = "0.13.0" -bp-core = { version = "=0.11.0-beta.5", default-features = false } -commit_verify = { version = "=0.11.0-beta.5", default-features = false } futures = "0.3" hex = "0.4" -reqwest = { version = "0.11", default-features = false, features = ["json", "blocking"] } -rgb-core = { version = "=0.11.0-beta.5", default-features = false, features = [ - "stl", -] } -rgb-invoice = { version = "=0.11.0-beta.5", default-features = false } -rgb-lib = { version = "0.3.0-alpha.2", features = [ +rgb-lib = { version = "0.3.0-alpha.3", features = [ "electrum", "esplora", ] } -rgb-psbt = { version = "=0.11.0-beta.5", default-features = false } -rgb-std = { version = "=0.11.0-beta.5", default-features = false } -rgb-runtime = { version = "=0.11.0-beta.5", default-features = false, features = [ - "electrum", - "serde", +serde = { version = "^1.0", features = [ + "derive", ] } -serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -strict_encoding = { version = "=2.7.0-beta.1", default-features = false } -tokio = { version = "1.14.1", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.14.1", features = [ + "macros", + "rt-multi-thread", +] } [dev-dependencies] hex = "0.4" diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 6df31c844ef..2a6a210b726 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -23,7 +23,7 @@ use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1,ecdsa::Signature}; use bitcoin::secp256k1; -use invoice::RgbTransport; +use rgb_lib::RgbTransport; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a2ab1c50f1b..4022848f0b6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -30,7 +30,7 @@ use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; use bitcoin::{LockTime, secp256k1, Sequence}; -use invoice::RgbTransport; +use rgb_lib::RgbTransport; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs}; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 60620b36681..01bd5151200 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,9 +31,7 @@ use bitcoin::{secp256k1, Witness}; use bitcoin::blockdata::script::Script; use bitcoin::hash_types::Txid; -use invoice::RgbTransport; - -use rgbstd::contract::ContractId; +use rgb_lib::{ContractId, RgbTransport}; use crate::blinded_path::payment::ReceiveTlvs; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; diff --git a/lightning/src/rgb_utils/mod.rs b/lightning/src/rgb_utils/mod.rs index 9bb4de9fb37..2917a5c6272 100644 --- a/lightning/src/rgb_utils/mod.rs +++ b/lightning/src/rgb_utils/mod.rs @@ -1,823 +1,732 @@ //! A module to provide RGB functionality -pub mod proxy; - use crate::chain::transaction::OutPoint; -use crate::ln::features::ChannelTypeFeatures; -use crate::ln::{PaymentHash, ChannelId}; -use crate::ln::chan_utils::{BuiltCommitmentTransaction, ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment, get_counterparty_payment_script}; +use crate::ln::chan_utils::{ + get_counterparty_payment_script, BuiltCommitmentTransaction, ClosingTransaction, + CommitmentTransaction, HTLCOutputInCommitment, +}; +use crate::ln::channel::{ChannelContext, ChannelError}; use crate::ln::channelmanager::{ChannelDetails, MsgHandleErrInternal}; -use crate::ln::channel::{ChannelError, ChannelContext}; +use crate::ln::features::ChannelTypeFeatures; +use crate::ln::{ChannelId, PaymentHash}; use crate::sign::SignerProvider; -use amplify::none; -use bitcoin::{TxOut, Script}; use bitcoin::blockdata::transaction::Transaction; use bitcoin::hashes::hex::ToHex; -use bitcoin::psbt::PartiallySignedTransaction; +use bitcoin::psbt::Psbt; use bitcoin::secp256k1::PublicKey; -use bitcoin_30::psbt::PartiallySignedTransaction as BitcoinPsbt; -use bitcoin_30::hashes::Hash; -use bp::Outpoint as RgbOutpoint; -use bp::seals::txout::blind::BlindSeal; -use bp::seals::txout::{CloseMethod, ExplicitSeal, TxPtr}; -use psbt::{Psbt as RgbPsbt, RgbPsbt as RgbPsbtTrait}; -use rgb_lib::wallet::Outpoint; -use rgb_rt::electrum::Resolver; -use rgb::validation::Validity; -use rgb::{Assign, BlindingFactor, Layer1, Operation, XChain, XOutputSeal}; -use rgb_lib::{BitcoinNetwork, AssetSchema}; -use rgb_lib::utils::{load_rgb_runtime, RgbRuntime, RgbInExt, RgbPsbtExt, RgbOutExt}; +use bitcoin::TxOut; +use rgb_lib::{ + bitcoin::psbt::Psbt as RgbLibPsbt, + wallet::{rust_only::ColoringInfo, AssetIface, DatabaseType, Outpoint, WalletData}, + BitcoinNetwork, ContractId, Error as RgbLibError, Fascia, FileContent, RgbTransfer, + RgbTransport, Wallet, +}; use serde::{Deserialize, Serialize}; -use rgbstd::Txid as RgbTxid; -use rgbstd::containers::{BuilderSeal, CloseMethodSet, Fascia, FileContent, Transfer as RgbTransfer}; -use rgbstd::contract::{ContractId, GraphSeal}; -use rgbstd::persistence::Inventory; -use invoice::RgbTransport; -use strict_encoding::{TypeName, FieldName}; +use tokio::runtime::Handle; use core::ops::Deref; -use std::collections::HashSet; +use std::collections::HashMap; use std::fs; -use std::convert::TryFrom; -use std::path::{PathBuf, Path}; +use std::path::{Path, PathBuf}; use std::str::FromStr; - -use self::proxy::get_consignment; - /// Static blinding costant (will be removed in the future) pub const STATIC_BLINDING: u64 = 777; /// Name of the file containing the bitcoin network pub const BITCOIN_NETWORK_FNAME: &str = "bitcoin_network"; /// Name of the file containing the electrum URL -pub const ELECTRUM_URL_FNAME: &str = "electrum_url"; +pub const INDEXER_URL_FNAME: &str = "indexer_url"; /// Name of the file containing the wallet fingerprint pub const WALLET_FINGERPRINT_FNAME: &str = "wallet_fingerprint"; +/// Name of the file containing the wallet account xPub +pub const WALLET_ACCOUNT_XPUB_FNAME: &str = "wallet_account_xpub"; const INBOUND_EXT: &str = "inbound"; const OUTBOUND_EXT: &str = "outbound"; /// RGB channel info #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RgbInfo { - /// Channel contract ID - pub contract_id: ContractId, - /// Channel RGB local amount - pub local_rgb_amount: u64, - /// Channel RGB remote amount - pub remote_rgb_amount: u64, + /// Channel contract ID + pub contract_id: ContractId, + /// Channel RGB local amount + pub local_rgb_amount: u64, + /// Channel RGB remote amount + pub remote_rgb_amount: u64, } /// RGB payment info #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RgbPaymentInfo { - /// RGB contract ID - pub contract_id: ContractId, - /// RGB payment amount - pub amount: u64, - /// RGB local amount - pub local_rgb_amount: u64, - /// RGB remote amount - pub remote_rgb_amount: u64, - /// Whether the RGB amount in route should be overridden - pub swap_payment: bool, - /// Whether the payment is inbound - pub inbound: bool, + /// RGB contract ID + pub contract_id: ContractId, + /// RGB payment amount + pub amount: u64, + /// RGB local amount + pub local_rgb_amount: u64, + /// RGB remote amount + pub remote_rgb_amount: u64, + /// Whether the RGB amount in route should be overridden + pub swap_payment: bool, + /// Whether the payment is inbound + pub inbound: bool, } /// RGB transfer info #[derive(Debug, Clone, Deserialize, Serialize)] pub struct TransferInfo { - /// Transfer fascia - pub fascia: Fascia, - /// Transfer contract ID - pub contract_id: ContractId, - /// Transfer RGB amount - pub rgb_amount: u64, + /// Transfer fascia + pub fascia: Fascia, + /// Transfer contract ID + pub contract_id: ContractId, + /// Transfer RGB amount + pub rgb_amount: u64, } -fn _get_resolver(ldk_data_dir: &Path) -> Resolver { - let electrum_url = fs::read_to_string(ldk_data_dir.parent().unwrap().join(ELECTRUM_URL_FNAME)).expect("able to read"); - Resolver::new(&electrum_url).expect("able to get resolver") +fn _get_file_in_parent(ldk_data_dir: &Path, fname: &str) -> PathBuf { + ldk_data_dir.parent().unwrap().join(fname) +} + +fn _read_file_in_parent(ldk_data_dir: &Path, fname: &str) -> String { + fs::read_to_string(_get_file_in_parent(ldk_data_dir, fname)).unwrap() } fn _get_rgb_wallet_dir(ldk_data_dir: &Path) -> PathBuf { - let wallet_fingerprint = fs::read_to_string(ldk_data_dir.parent().unwrap().join(WALLET_FINGERPRINT_FNAME)).expect("able to read"); - ldk_data_dir.parent().unwrap().join(wallet_fingerprint) + let fingerprint = _read_file_in_parent(ldk_data_dir, WALLET_FINGERPRINT_FNAME); + _get_file_in_parent(ldk_data_dir, &fingerprint) } -fn _testnet(ldk_data_dir: &Path) -> bool { - let bitcoin_network_str = fs::read_to_string(ldk_data_dir.parent().unwrap().join(BITCOIN_NETWORK_FNAME)).expect("able to read"); - let bitcoin_network = BitcoinNetwork::from_str(&bitcoin_network_str).unwrap(); - !matches!(bitcoin_network, BitcoinNetwork::Mainnet) +fn _get_bitcoin_network(ldk_data_dir: &Path) -> BitcoinNetwork { + let bitcoin_network = _read_file_in_parent(ldk_data_dir, BITCOIN_NETWORK_FNAME); + BitcoinNetwork::from_str(&bitcoin_network).unwrap() } -/// Get an instance of the RGB runtime -pub fn get_rgb_runtime(ldk_data_dir: &Path) -> RgbRuntime { - load_rgb_runtime(_get_rgb_wallet_dir(ldk_data_dir)).expect("RGB runtime should be available") +fn _get_account_xpub(ldk_data_dir: &Path) -> String { + _read_file_in_parent(ldk_data_dir, WALLET_ACCOUNT_XPUB_FNAME) } -/// Read TransferInfo file -pub fn read_rgb_transfer_info(path: &Path) -> TransferInfo { - let serialized_info = fs::read_to_string(path).expect("able to read transfer info file"); - serde_json::from_str(&serialized_info).expect("valid transfer info") +fn _get_indexer_url(ldk_data_dir: &Path) -> String { + _read_file_in_parent(ldk_data_dir, INDEXER_URL_FNAME) } -/// Whether a transfer is colored -pub fn is_transfer_colored(path: &str) -> bool { - PathBuf::from(path).exists() +fn _new_rgb_wallet(data_dir: String, bitcoin_network: BitcoinNetwork, pubkey: String) -> Wallet { + Wallet::new(WalletData { + data_dir, + bitcoin_network, + database_type: DatabaseType::Sqlite, + max_allocations_per_utxo: 1, + pubkey, + mnemonic: None, + vanilla_keychain: None, + }) + .expect("valid rgb-lib wallet") +} + +fn _get_wallet_data(ldk_data_dir: &Path) -> (String, BitcoinNetwork, String) { + let data_dir = ldk_data_dir.parent().unwrap().to_string_lossy().to_string(); + let bitcoin_network = _get_bitcoin_network(ldk_data_dir); + let pubkey = _get_account_xpub(ldk_data_dir); + (data_dir, bitcoin_network, pubkey) +} + +async fn _get_rgb_wallet(ldk_data_dir: &Path) -> Wallet { + let (data_dir, bitcoin_network, pubkey) = _get_wallet_data(ldk_data_dir); + tokio::task::spawn_blocking(move || _new_rgb_wallet(data_dir, bitcoin_network, pubkey)) + .await + .unwrap() +} + +async fn _accept_transfer( + ldk_data_dir: &Path, + funding_txid: String, + consignment_endpoint: RgbTransport, +) -> Result<(RgbTransfer, u64), RgbLibError> { + let (data_dir, bitcoin_network, pubkey) = _get_wallet_data(ldk_data_dir); + let indexer_url = _get_indexer_url(ldk_data_dir); + tokio::task::spawn_blocking(move || { + let mut wallet = _new_rgb_wallet(data_dir, bitcoin_network, pubkey); + wallet.go_online(true, indexer_url).unwrap(); + wallet.accept_transfer( + funding_txid.clone(), + 0, + consignment_endpoint, + STATIC_BLINDING, + true, + ) + }) + .await + .unwrap() +} + +/// Read TransferInfo file +pub fn read_rgb_transfer_info(path: &Path) -> TransferInfo { + let serialized_info = fs::read_to_string(path).expect("able to read transfer info file"); + serde_json::from_str(&serialized_info).expect("valid transfer info") } /// Write TransferInfo file pub fn write_rgb_transfer_info(path: &PathBuf, info: &TransferInfo) { - let serialized_info = serde_json::to_string(&info).expect("valid transfer info"); - fs::write(path, serialized_info).expect("able to write transfer info file") + let serialized_info = serde_json::to_string(&info).expect("valid transfer info"); + fs::write(path, serialized_info).expect("able to write transfer info file") } -fn counterparty_output_index(outputs: &[TxOut], channel_type_features: &ChannelTypeFeatures, payment_key: &PublicKey) -> Option { - let counterparty_payment_script = get_counterparty_payment_script(channel_type_features, payment_key); - outputs.iter().enumerate() - .find(|(_, out)| out.script_pubkey == counterparty_payment_script) - .map(|(idx, _)| idx) +fn _counterparty_output_index( + outputs: &[TxOut], + channel_type_features: &ChannelTypeFeatures, + payment_key: &PublicKey, +) -> Option { + let counterparty_payment_script = + get_counterparty_payment_script(channel_type_features, payment_key); + outputs + .iter() + .enumerate() + .find(|(_, out)| out.script_pubkey == counterparty_payment_script) + .map(|(idx, _)| idx) } /// Color commitment transaction -pub(crate) fn color_commitment(channel_context: &ChannelContext, commitment_tx: &mut CommitmentTransaction, counterparty: bool) -> Result<(), ChannelError> where ::Target: SignerProvider { - let channel_id = &channel_context.channel_id; - let funding_outpoint = channel_context.channel_transaction_parameters.funding_outpoint.unwrap(); - let ldk_data_dir = channel_context.ldk_data_dir.as_path(); - - let mut transaction = commitment_tx.clone().built.transaction; - transaction.output.push(TxOut { value: 0, script_pubkey: Script::new_op_return(&[]) }); - let psbt = PartiallySignedTransaction::from_unsigned_tx(transaction.clone()).expect("valid transaction"); - let mut psbt = BitcoinPsbt::from_str(&psbt.to_string()).unwrap(); - - let runtime = get_rgb_runtime(ldk_data_dir); - - let mut static_blinding_32_bytes: [u8; 32] = [0; 32]; - static_blinding_32_bytes[0..8].copy_from_slice(&STATIC_BLINDING.to_le_bytes()); - let static_blinding_factor = BlindingFactor::try_from(static_blinding_32_bytes).unwrap(); - - let (rgb_info, _) = get_rgb_channel_info_pending(channel_id, ldk_data_dir); - - let chan_id = channel_id.to_hex(); - let mut rgb_offered_htlc = 0; - let mut rgb_received_htlc = 0; - let mut last_rgb_payment_info = None; - let mut asset_transition_builder = runtime.stock.transition_builder(rgb_info.contract_id, TypeName::try_from("RGB20").unwrap(), None::<&str>).expect("ok"); - let assignment_id = asset_transition_builder - .assignments_type(&FieldName::from("assetOwner")).expect("valid assignment"); - - for htlc in commitment_tx.htlcs() { - if htlc.amount_rgb.unwrap_or(0) == 0 { +pub(crate) fn color_commitment( + channel_context: &ChannelContext, + commitment_transaction: &mut CommitmentTransaction, + counterparty: bool, +) -> Result<(), ChannelError> +where + ::Target: SignerProvider, +{ + let channel_id = &channel_context.channel_id; + let funding_outpoint = channel_context + .channel_transaction_parameters + .funding_outpoint + .unwrap(); + let ldk_data_dir = channel_context.ldk_data_dir.as_path(); + + let commitment_tx = commitment_transaction.clone().built.transaction; + + let (rgb_info, _) = get_rgb_channel_info_pending(channel_id, ldk_data_dir); + + let chan_id = channel_id.to_hex(); + let mut rgb_offered_htlc = 0; + let mut rgb_received_htlc = 0; + let mut last_rgb_payment_info = None; + let mut output_map = HashMap::new(); + + for htlc in commitment_transaction.htlcs() { + if htlc.amount_rgb.unwrap_or(0) == 0 { continue; } - let htlc_amount_rgb = htlc.amount_rgb.expect("this HTLC has RGB assets"); - - let htlc_vout = htlc.transaction_output_index.unwrap(); - - let inbound = htlc.offered == counterparty; - - let htlc_payment_hash = hex::encode(htlc.payment_hash.0); - let htlc_proxy_id = format!("{chan_id}{htlc_payment_hash}"); - let mut rgb_payment_info_proxy_id_path = ldk_data_dir.join(htlc_proxy_id); - let rgb_payment_info_path = ldk_data_dir.join(htlc_payment_hash); - let mut rgb_payment_info_path = rgb_payment_info_path.clone(); - if inbound { - rgb_payment_info_proxy_id_path.set_extension(INBOUND_EXT); - rgb_payment_info_path.set_extension(INBOUND_EXT); - } else { - rgb_payment_info_proxy_id_path.set_extension(OUTBOUND_EXT); - rgb_payment_info_path.set_extension(OUTBOUND_EXT); - } - let rgb_payment_info_tmp_path = append_pending_extension(&rgb_payment_info_path); - - if rgb_payment_info_tmp_path.exists() { - let mut rgb_payment_info = parse_rgb_payment_info(&rgb_payment_info_tmp_path); - rgb_payment_info.local_rgb_amount = rgb_info.local_rgb_amount; - rgb_payment_info.remote_rgb_amount = rgb_info.remote_rgb_amount; - let serialized_info = serde_json::to_string(&rgb_payment_info).expect("valid rgb payment info"); - fs::write(&rgb_payment_info_proxy_id_path, serialized_info).expect("able to write rgb payment info file"); - fs::remove_file(rgb_payment_info_tmp_path).expect("able to remove file"); - } - - let rgb_payment_info = if rgb_payment_info_proxy_id_path.exists() { - parse_rgb_payment_info(&rgb_payment_info_proxy_id_path) - } else { - let rgb_payment_info = RgbPaymentInfo { - contract_id: rgb_info.contract_id, - amount: htlc_amount_rgb, - local_rgb_amount: rgb_info.local_rgb_amount, - remote_rgb_amount: rgb_info.remote_rgb_amount, - swap_payment: true, - inbound, - }; - let serialized_info = serde_json::to_string(&rgb_payment_info).expect("valid rgb payment info"); - fs::write(rgb_payment_info_proxy_id_path, serialized_info.clone()).expect("able to write rgb payment info file"); - fs::write(rgb_payment_info_path, serialized_info).expect("able to write rgb payment info file"); - rgb_payment_info - }; - - if inbound { - rgb_received_htlc += rgb_payment_info.amount - } else { - rgb_offered_htlc += rgb_payment_info.amount - }; - - if rgb_payment_info.amount > 0 { - let graph_seal = GraphSeal::with_blinded_vout( - CloseMethod::OpretFirst, - htlc_vout, - STATIC_BLINDING, - ); - let htlc_seal = BuilderSeal::Revealed(XChain::with(Layer1::Bitcoin, graph_seal)); - - asset_transition_builder = asset_transition_builder - .add_fungible_state_raw(assignment_id, htlc_seal, rgb_payment_info.amount, static_blinding_factor).expect("ok"); - } - - last_rgb_payment_info = Some(rgb_payment_info); - } - - let (local_amt, remote_amt) = if let Some(last_rgb_payment_info) = last_rgb_payment_info { - (last_rgb_payment_info.local_rgb_amount - rgb_offered_htlc, last_rgb_payment_info.remote_rgb_amount - rgb_received_htlc) - } else { - (rgb_info.local_rgb_amount, rgb_info.remote_rgb_amount) - }; - let (vout_p2wpkh_amt, vout_p2wsh_amt) = if counterparty { - (local_amt, remote_amt) - } else { - (remote_amt, local_amt) - }; - - let payment_point = if counterparty { - channel_context.get_holder_pubkeys().payment_point - } else { - channel_context.get_counterparty_pubkeys().payment_point - }; - let vout_p2wpkh = counterparty_output_index( - &transaction.output, - &channel_context.channel_type, - &payment_point - ).unwrap() as u32; - let vout_p2wsh = commitment_tx.trust().revokeable_output_index().unwrap() as u32; - - if vout_p2wpkh_amt > 0 { - let graph_seal = GraphSeal::with_blinded_vout(CloseMethod::OpretFirst, vout_p2wpkh, STATIC_BLINDING); - let seal_p2wpkh = BuilderSeal::Revealed(XChain::with(Layer1::Bitcoin, graph_seal)); - - asset_transition_builder = asset_transition_builder - .add_fungible_state_raw(assignment_id, seal_p2wpkh, vout_p2wpkh_amt, static_blinding_factor).expect("ok"); - } - if vout_p2wsh_amt > 0 { - let graph_seal = GraphSeal::with_blinded_vout(CloseMethod::OpretFirst, vout_p2wsh, STATIC_BLINDING); - let seal_p2wsh = BuilderSeal::Revealed(XChain::with(Layer1::Bitcoin, graph_seal)); - asset_transition_builder = asset_transition_builder - .add_fungible_state_raw(assignment_id, seal_p2wsh, vout_p2wsh_amt, static_blinding_factor).expect("ok"); - } - - let prev_outputs = psbt - .unsigned_tx - .input - .iter() - .map(|txin| txin.previous_output) - .map(|outpoint| { - XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, Outpoint::from(outpoint).into()), - ) - }) - .collect::>(); - for ((opout, _), state) in runtime.stock.state_for_outpoints(rgb_info.contract_id, prev_outputs.iter().copied()).expect("ok") { - asset_transition_builder = asset_transition_builder.add_input(opout, state).expect("valid input"); - } - let transition = asset_transition_builder - .complete_transition().expect("should complete transition"); - - let (opreturn_index, _) = psbt - .unsigned_tx - .output - .iter() - .enumerate() - .find(|(_, o)| o.script_pubkey.is_op_return()) - .expect("psbt should have an op_return output"); - let (_, opreturn_output) = psbt - .outputs - .iter_mut() - .enumerate() - .find(|(i, _)| i == &opreturn_index) - .unwrap(); - opreturn_output.set_opret_host(); - opreturn_output.set_mpc_entropy(STATIC_BLINDING); - - let inputs: &[XOutputSeal; 1] = &[ - XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, RgbOutpoint::new(RgbTxid::from_str(&funding_outpoint.txid.to_string()).unwrap(), funding_outpoint.index as u32)) - ) - ]; - for (input, txin) in psbt.inputs.iter_mut().zip(&psbt.unsigned_tx.input) { - let prevout = txin.previous_output; - let outpoint = RgbOutpoint::new(prevout.txid.to_byte_array().into(), prevout.vout); - let output = XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, outpoint), - ); - if inputs.contains(&output) { - input.set_rgb_consumer(rgb_info.contract_id, transition.id()).expect("ok"); - } - } - psbt.push_rgb_transition(transition, CloseMethodSet::OpretFirst).expect("ok"); - - let mut rgb_psbt = RgbPsbt::from_str(&psbt.to_string()).unwrap(); - rgb_psbt.complete_construction(); - let fascia = rgb_psbt.rgb_commit().unwrap(); - - let psbt = PartiallySignedTransaction::from_str(&rgb_psbt.to_string()).unwrap(); - let modified_tx = psbt.extract_tx(); - let txid = modified_tx.txid(); - commitment_tx.built = BuiltCommitmentTransaction { - transaction: modified_tx, - txid, - }; + let htlc_amount_rgb = htlc.amount_rgb.expect("this HTLC has RGB assets"); + + let htlc_vout = htlc.transaction_output_index.unwrap(); + + let inbound = htlc.offered == counterparty; + + let htlc_payment_hash = hex::encode(htlc.payment_hash.0); + let htlc_proxy_id = format!("{chan_id}{htlc_payment_hash}"); + let mut rgb_payment_info_proxy_id_path = ldk_data_dir.join(htlc_proxy_id); + let rgb_payment_info_path = ldk_data_dir.join(htlc_payment_hash); + let mut rgb_payment_info_path = rgb_payment_info_path.clone(); + if inbound { + rgb_payment_info_proxy_id_path.set_extension(INBOUND_EXT); + rgb_payment_info_path.set_extension(INBOUND_EXT); + } else { + rgb_payment_info_proxy_id_path.set_extension(OUTBOUND_EXT); + rgb_payment_info_path.set_extension(OUTBOUND_EXT); + } + let rgb_payment_info_tmp_path = _append_pending_extension(&rgb_payment_info_path); + + if rgb_payment_info_tmp_path.exists() { + let mut rgb_payment_info = parse_rgb_payment_info(&rgb_payment_info_tmp_path); + rgb_payment_info.local_rgb_amount = rgb_info.local_rgb_amount; + rgb_payment_info.remote_rgb_amount = rgb_info.remote_rgb_amount; + let serialized_info = + serde_json::to_string(&rgb_payment_info).expect("valid rgb payment info"); + fs::write(&rgb_payment_info_proxy_id_path, serialized_info) + .expect("able to write rgb payment info file"); + fs::remove_file(rgb_payment_info_tmp_path).expect("able to remove file"); + } + + let rgb_payment_info = if rgb_payment_info_proxy_id_path.exists() { + parse_rgb_payment_info(&rgb_payment_info_proxy_id_path) + } else { + let rgb_payment_info = RgbPaymentInfo { + contract_id: rgb_info.contract_id, + amount: htlc_amount_rgb, + local_rgb_amount: rgb_info.local_rgb_amount, + remote_rgb_amount: rgb_info.remote_rgb_amount, + swap_payment: true, + inbound, + }; + let serialized_info = + serde_json::to_string(&rgb_payment_info).expect("valid rgb payment info"); + fs::write(rgb_payment_info_proxy_id_path, serialized_info.clone()) + .expect("able to write rgb payment info file"); + fs::write(rgb_payment_info_path, serialized_info) + .expect("able to write rgb payment info file"); + rgb_payment_info + }; + + if inbound { + rgb_received_htlc += rgb_payment_info.amount + } else { + rgb_offered_htlc += rgb_payment_info.amount + }; + + output_map.insert(htlc_vout, rgb_payment_info.amount); + + last_rgb_payment_info = Some(rgb_payment_info); + } + + let (local_amt, remote_amt) = if let Some(last_rgb_payment_info) = last_rgb_payment_info { + ( + last_rgb_payment_info.local_rgb_amount - rgb_offered_htlc, + last_rgb_payment_info.remote_rgb_amount - rgb_received_htlc, + ) + } else { + (rgb_info.local_rgb_amount, rgb_info.remote_rgb_amount) + }; + let (vout_p2wpkh_amt, vout_p2wsh_amt) = if counterparty { + (local_amt, remote_amt) + } else { + (remote_amt, local_amt) + }; + + let payment_point = if counterparty { + channel_context.get_holder_pubkeys().payment_point + } else { + channel_context.get_counterparty_pubkeys().payment_point + }; + let vout_p2wpkh = _counterparty_output_index( + &commitment_tx.output, + &channel_context.channel_type, + &payment_point, + ) + .unwrap() as u32; + let vout_p2wsh = commitment_transaction + .trust() + .revokeable_output_index() + .unwrap() as u32; + + output_map.insert(vout_p2wpkh, vout_p2wpkh_amt); + output_map.insert(vout_p2wsh, vout_p2wsh_amt); + + let coloring_info = ColoringInfo { + contract_id: rgb_info.contract_id, + iface: AssetIface::RGB20, + input_outpoint: Outpoint { + txid: funding_outpoint.txid.to_string(), + vout: funding_outpoint.index as u32, + }, + output_map, + static_blinding: STATIC_BLINDING, + }; + let psbt = Psbt::from_unsigned_tx(commitment_tx.clone()).unwrap(); + let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()).unwrap(); + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir)); + let (fascia, _) = wallet.color_psbt(&mut psbt, coloring_info).unwrap(); + let psbt = Psbt::from_str(&psbt.to_string()).unwrap(); + let modified_tx = psbt.extract_tx(); + + let txid = modified_tx.txid(); + commitment_transaction.built = BuiltCommitmentTransaction { + transaction: modified_tx, + txid, + }; // save RGB transfer data to disk - if counterparty { - let transfer_info = TransferInfo { - fascia, - contract_id: rgb_info.contract_id, - rgb_amount: vout_p2wpkh_amt + rgb_offered_htlc, - }; - let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); - write_rgb_transfer_info(&transfer_info_path, &transfer_info); - } else { - let transfer_info = TransferInfo { - fascia, - contract_id: rgb_info.contract_id, - rgb_amount: vout_p2wsh_amt + rgb_received_htlc, - }; - let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); - write_rgb_transfer_info(&transfer_info_path, &transfer_info); - } - - Ok(()) + if counterparty { + let transfer_info = TransferInfo { + fascia, + contract_id: rgb_info.contract_id, + rgb_amount: vout_p2wpkh_amt + rgb_offered_htlc, + }; + let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); + write_rgb_transfer_info(&transfer_info_path, &transfer_info); + } else { + let transfer_info = TransferInfo { + fascia, + contract_id: rgb_info.contract_id, + rgb_amount: vout_p2wsh_amt + rgb_received_htlc, + }; + let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); + write_rgb_transfer_info(&transfer_info_path, &transfer_info); + } + + Ok(()) } /// Color HTLC transaction -pub(crate) fn color_htlc(htlc_tx: &mut Transaction, htlc: &HTLCOutputInCommitment, ldk_data_dir: &Path) -> Result<(), ChannelError> { - if htlc.amount_rgb.unwrap_or(0) == 0 { - return Ok(()) - } - let htlc_amount_rgb = htlc.amount_rgb.expect("this HTLC has RGB assets"); - - htlc_tx.output.push(TxOut { value: 0, script_pubkey: Script::new_op_return(&[]) }); - let psbt = PartiallySignedTransaction::from_unsigned_tx(htlc_tx.clone()).expect("valid transaction"); - let mut psbt = BitcoinPsbt::from_str(&psbt.to_string()).unwrap(); - - let runtime = get_rgb_runtime(ldk_data_dir); - - let mut static_blinding_32_bytes: [u8; 32] = [0; 32]; - static_blinding_32_bytes[0..8].copy_from_slice(&STATIC_BLINDING.to_le_bytes()); - let static_blinding_factor = BlindingFactor::try_from(static_blinding_32_bytes).unwrap(); - - let consignment_htlc_outpoint = htlc_tx.input.first().unwrap().previous_output; - let commitment_txid = consignment_htlc_outpoint.txid; - - let transfer_info_path = ldk_data_dir.join(format!("{}_transfer_info", commitment_txid)); - let transfer_info = read_rgb_transfer_info(&transfer_info_path); - let contract_id = transfer_info.contract_id; - - let mut asset_transition_builder = runtime.stock.transition_builder(contract_id, TypeName::try_from("RGB20").unwrap(), None::<&str>).expect("ok"); - let assignment_id = asset_transition_builder - .assignments_type(&FieldName::from("assetOwner")).expect("valid assignment"); - - let seal_vout = 0; - let graph_seal = GraphSeal::with_blinded_vout(CloseMethod::OpretFirst, seal_vout, STATIC_BLINDING); - let seal = BuilderSeal::Revealed(XChain::with(Layer1::Bitcoin, graph_seal)); - asset_transition_builder = asset_transition_builder - .add_fungible_state_raw(assignment_id, seal, htlc_amount_rgb, static_blinding_factor).expect("ok"); - - let prev_outputs = psbt - .unsigned_tx - .input - .iter() - .map(|txin| txin.previous_output) - .map(|outpoint| { - XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, Outpoint::from(outpoint).into()), - ) - }) - .collect::>(); - for ((opout, _), state) in runtime.stock.state_for_outpoints(contract_id, prev_outputs.iter().copied()).expect("ok") { - asset_transition_builder = asset_transition_builder.add_input(opout, state).expect("valid input"); - } - let transition = asset_transition_builder - .complete_transition().expect("should complete transition"); - - let (opreturn_index, _) = psbt - .unsigned_tx - .output - .iter() - .enumerate() - .find(|(_, o)| o.script_pubkey.is_op_return()) - .expect("psbt should have an op_return output"); - let (_, opreturn_output) = psbt - .outputs - .iter_mut() - .enumerate() - .find(|(i, _)| i == &opreturn_index) - .unwrap(); - opreturn_output.set_opret_host(); - opreturn_output.set_mpc_entropy(STATIC_BLINDING); - - let inputs: &[XOutputSeal; 1] = &[ - XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, RgbOutpoint::new(RgbTxid::from_str(&commitment_txid.to_string()).unwrap(), htlc_tx.input.first().unwrap().previous_output.vout)) - ) - ]; - for (input, txin) in psbt.inputs.iter_mut().zip(&psbt.unsigned_tx.input) { - let prevout = txin.previous_output; - let outpoint = RgbOutpoint::new(prevout.txid.to_byte_array().into(), prevout.vout); - let output = XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, outpoint), - ); - if inputs.contains(&output) { - input.set_rgb_consumer(contract_id, transition.id()).expect("ok"); - } - } - psbt.push_rgb_transition(transition, CloseMethodSet::OpretFirst).expect("ok"); - - let mut rgb_psbt = RgbPsbt::from_str(&psbt.to_string()).unwrap(); - rgb_psbt.complete_construction(); - let fascia = rgb_psbt.rgb_commit().unwrap(); - - let psbt = PartiallySignedTransaction::from_str(&rgb_psbt.to_string()).unwrap(); - let modified_tx = psbt.extract_tx(); - let modified_txid = &modified_tx.txid(); - *htlc_tx = modified_tx; - - // save RGB transfer data to disk - let transfer_info = TransferInfo { - fascia, - contract_id, - rgb_amount: htlc_amount_rgb, - }; - let transfer_info_path = ldk_data_dir.join(format!("{modified_txid}_transfer_info")); - write_rgb_transfer_info(&transfer_info_path, &transfer_info); - - Ok(()) +pub(crate) fn color_htlc( + htlc_tx: &mut Transaction, + htlc: &HTLCOutputInCommitment, + ldk_data_dir: &Path, +) -> Result<(), ChannelError> { + if htlc.amount_rgb.unwrap_or(0) == 0 { + return Ok(()); + } + let htlc_amount_rgb = htlc.amount_rgb.expect("this HTLC has RGB assets"); + + let consignment_htlc_outpoint = htlc_tx.input.first().unwrap().previous_output; + let commitment_txid = consignment_htlc_outpoint.txid.to_string(); + + let transfer_info_path = ldk_data_dir.join(format!("{commitment_txid}_transfer_info")); + let transfer_info = read_rgb_transfer_info(&transfer_info_path); + let contract_id = transfer_info.contract_id; + + let coloring_info = ColoringInfo { + contract_id, + iface: AssetIface::RGB20, + input_outpoint: Outpoint { + txid: commitment_txid, + vout: consignment_htlc_outpoint.vout, + }, + output_map: HashMap::from([(0, htlc_amount_rgb)]), + static_blinding: STATIC_BLINDING, + }; + let psbt = Psbt::from_unsigned_tx(htlc_tx.clone()).unwrap(); + let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()).unwrap(); + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir)); + let (fascia, _) = wallet.color_psbt(&mut psbt, coloring_info).unwrap(); + let psbt = Psbt::from_str(&psbt.to_string()).unwrap(); + let modified_tx = psbt.extract_tx(); + let txid = &modified_tx.txid(); + + // save RGB transfer data to disk + let transfer_info = TransferInfo { + fascia, + contract_id, + rgb_amount: htlc_amount_rgb, + }; + let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); + write_rgb_transfer_info(&transfer_info_path, &transfer_info); + + Ok(()) } /// Color closing transaction -pub(crate) fn color_closing(channel_id: &ChannelId, funding_outpoint: &OutPoint, closing_tx: &mut ClosingTransaction, ldk_data_dir: &Path) -> Result<(), ChannelError> { - let mut transaction = closing_tx.clone().built; - transaction.output.push(TxOut { value: 0, script_pubkey: Script::new_op_return(&[]) }); - let psbt = PartiallySignedTransaction::from_unsigned_tx(transaction.clone()).expect("valid transaction"); - let mut psbt = BitcoinPsbt::from_str(&psbt.to_string()).unwrap(); - - let runtime = get_rgb_runtime(ldk_data_dir); - - let mut static_blinding_32_bytes: [u8; 32] = [0; 32]; - static_blinding_32_bytes[0..8].copy_from_slice(&STATIC_BLINDING.to_le_bytes()); - let static_blinding_factor = BlindingFactor::try_from(static_blinding_32_bytes).unwrap(); - - let (rgb_info, _) = get_rgb_channel_info_pending(channel_id, ldk_data_dir); - - let holder_vout = transaction.output.iter().position(|o| o.script_pubkey == closing_tx.to_holder_script).unwrap(); - let counterparty_vout = holder_vout ^ 1; - - let holder_vout_amount = rgb_info.local_rgb_amount; - let counterparty_vout_amount = rgb_info.remote_rgb_amount; - - let mut asset_transition_builder = runtime.stock.transition_builder(rgb_info.contract_id, TypeName::try_from("RGB20").unwrap(), None::<&str>).expect("ok"); - let assignment_id = asset_transition_builder - .assignments_type(&FieldName::from("assetOwner")).expect("valid assignment"); - - if holder_vout_amount > 0 { - let graph_seal = GraphSeal::with_blinded_vout(CloseMethod::OpretFirst, holder_vout as u32, STATIC_BLINDING); - let holder_seal = BuilderSeal::Revealed(XChain::with(Layer1::Bitcoin, graph_seal)); - asset_transition_builder = asset_transition_builder - .add_fungible_state_raw(assignment_id, holder_seal, holder_vout_amount, static_blinding_factor).expect("ok"); - } - if counterparty_vout_amount > 0 { - let graph_seal = GraphSeal::with_blinded_vout(CloseMethod::OpretFirst, counterparty_vout as u32, STATIC_BLINDING); - let counterparty_seal = BuilderSeal::Revealed(XChain::with(Layer1::Bitcoin, graph_seal)); - asset_transition_builder = asset_transition_builder - .add_fungible_state_raw(assignment_id, counterparty_seal, counterparty_vout_amount, static_blinding_factor).expect("ok"); - } - - let prev_outputs = psbt - .unsigned_tx - .input - .iter() - .map(|txin| txin.previous_output) - .map(|outpoint| { - XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, Outpoint::from(outpoint).into()), - ) - }) - .collect::>(); - for ((opout, _), state) in runtime.stock.state_for_outpoints(rgb_info.contract_id, prev_outputs.iter().copied()).expect("ok") { - asset_transition_builder = asset_transition_builder.add_input(opout, state).expect("valid input"); - } - let transition = asset_transition_builder - .complete_transition().expect("should complete transition"); - - let (opreturn_index, _) = psbt - .unsigned_tx - .output - .iter() - .enumerate() - .find(|(_, o)| o.script_pubkey.is_op_return()) - .expect("psbt should have an op_return output"); - let (_, opreturn_output) = psbt - .outputs - .iter_mut() - .enumerate() - .find(|(i, _)| i == &opreturn_index) - .unwrap(); - opreturn_output.set_opret_host(); - opreturn_output.set_mpc_entropy(STATIC_BLINDING); - - let inputs: &[XOutputSeal; 1] = &[ - XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, RgbOutpoint::new(RgbTxid::from_str(&funding_outpoint.txid.to_string()).unwrap(), funding_outpoint.index as u32)) - ) - ]; - for (input, txin) in psbt.inputs.iter_mut().zip(&psbt.unsigned_tx.input) { - let prevout = txin.previous_output; - let outpoint = RgbOutpoint::new(prevout.txid.to_byte_array().into(), prevout.vout); - let output = XChain::with( - Layer1::Bitcoin, - ExplicitSeal::new(CloseMethod::OpretFirst, outpoint), - ); - if inputs.contains(&output) { - input.set_rgb_consumer(rgb_info.contract_id, transition.id()).expect("ok"); - } - } - psbt.push_rgb_transition(transition, CloseMethodSet::OpretFirst).expect("ok"); - - let mut rgb_psbt = RgbPsbt::from_str(&psbt.to_string()).unwrap(); - rgb_psbt.complete_construction(); - let fascia = rgb_psbt.rgb_commit().unwrap(); - - let psbt = PartiallySignedTransaction::from_str(&rgb_psbt.to_string()).unwrap(); - let modified_tx = psbt.extract_tx(); - let txid = modified_tx.txid(); - closing_tx.built = modified_tx; - - // save RGB transfer data to disk - let transfer_info = TransferInfo { - fascia, - contract_id: rgb_info.contract_id, - rgb_amount: holder_vout_amount, - }; - let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); - write_rgb_transfer_info(&transfer_info_path, &transfer_info); - - Ok(()) +pub(crate) fn color_closing( + channel_id: &ChannelId, + funding_outpoint: &OutPoint, + closing_transaction: &mut ClosingTransaction, + ldk_data_dir: &Path, +) -> Result<(), ChannelError> { + let closing_tx = closing_transaction.clone().built; + + let (rgb_info, _) = get_rgb_channel_info_pending(channel_id, ldk_data_dir); + + let holder_vout = closing_tx + .output + .iter() + .position(|o| o.script_pubkey == closing_transaction.to_holder_script) + .unwrap(); + let counterparty_vout = holder_vout ^ 1; + + let holder_vout_amount = rgb_info.local_rgb_amount; + let counterparty_vout_amount = rgb_info.remote_rgb_amount; + + let coloring_info = ColoringInfo { + contract_id: rgb_info.contract_id, + iface: AssetIface::RGB20, + input_outpoint: Outpoint { + txid: funding_outpoint.txid.to_string(), + vout: funding_outpoint.index as u32, + }, + output_map: HashMap::from([ + (holder_vout as u32, holder_vout_amount), + (counterparty_vout as u32, counterparty_vout_amount), + ]), + static_blinding: STATIC_BLINDING, + }; + let psbt = Psbt::from_unsigned_tx(closing_tx.clone()).unwrap(); + let mut psbt = RgbLibPsbt::from_str(&psbt.to_string()).unwrap(); + let handle = Handle::current(); + let _ = handle.enter(); + let wallet = futures::executor::block_on(_get_rgb_wallet(ldk_data_dir)); + let (fascia, _) = wallet.color_psbt(&mut psbt, coloring_info).unwrap(); + let psbt = Psbt::from_str(&psbt.to_string()).unwrap(); + let modified_tx = psbt.extract_tx(); + + let txid = &modified_tx.txid(); + closing_transaction.built = modified_tx; + + // save RGB transfer data to disk + let transfer_info = TransferInfo { + fascia, + contract_id: rgb_info.contract_id, + rgb_amount: holder_vout_amount, + }; + let transfer_info_path = ldk_data_dir.join(format!("{txid}_transfer_info")); + write_rgb_transfer_info(&transfer_info_path, &transfer_info); + + Ok(()) } /// Get RgbPaymentInfo file path -pub fn get_rgb_payment_info_path(payment_hash: &PaymentHash, ldk_data_dir: &Path, inbound: bool) -> PathBuf { - let mut path = ldk_data_dir.join(hex::encode(payment_hash.0)); - path.set_extension(if inbound { INBOUND_EXT } else { OUTBOUND_EXT }); - path +pub fn get_rgb_payment_info_path( + payment_hash: &PaymentHash, + ldk_data_dir: &Path, + inbound: bool, +) -> PathBuf { + let mut path = ldk_data_dir.join(hex::encode(payment_hash.0)); + path.set_extension(if inbound { INBOUND_EXT } else { OUTBOUND_EXT }); + path } /// Parse RgbPaymentInfo pub fn parse_rgb_payment_info(rgb_payment_info_path: &PathBuf) -> RgbPaymentInfo { - let serialized_info = fs::read_to_string(rgb_payment_info_path).expect("valid rgb payment info"); - serde_json::from_str(&serialized_info).expect("valid rgb info file") + let serialized_info = + fs::read_to_string(rgb_payment_info_path).expect("valid rgb payment info"); + serde_json::from_str(&serialized_info).expect("valid rgb info file") } /// Get RgbInfo file path pub fn get_rgb_channel_info_path(channel_id: &str, ldk_data_dir: &Path, pending: bool) -> PathBuf { - let mut info_file_path = ldk_data_dir.join(channel_id); - if pending { - info_file_path.set_extension("pending"); - } - info_file_path + let mut info_file_path = ldk_data_dir.join(channel_id); + if pending { + info_file_path.set_extension("pending"); + } + info_file_path } /// Get RgbInfo file -pub(crate) fn get_rgb_channel_info(channel_id: &str, ldk_data_dir: &Path, pending: bool) -> (RgbInfo, PathBuf) { - let info_file_path = get_rgb_channel_info_path(channel_id, ldk_data_dir, pending); - let info = parse_rgb_channel_info(&info_file_path); - (info, info_file_path) +pub(crate) fn get_rgb_channel_info( + channel_id: &str, + ldk_data_dir: &Path, + pending: bool, +) -> (RgbInfo, PathBuf) { + let info_file_path = get_rgb_channel_info_path(channel_id, ldk_data_dir, pending); + let info = parse_rgb_channel_info(&info_file_path); + (info, info_file_path) } /// Get pending RgbInfo file -pub fn get_rgb_channel_info_pending(channel_id: &ChannelId, ldk_data_dir: &Path) -> (RgbInfo, PathBuf) { - get_rgb_channel_info(&channel_id.to_hex(), ldk_data_dir, true) +pub fn get_rgb_channel_info_pending( + channel_id: &ChannelId, + ldk_data_dir: &Path, +) -> (RgbInfo, PathBuf) { + get_rgb_channel_info(&channel_id.to_hex(), ldk_data_dir, true) } /// Parse RgbInfo pub fn parse_rgb_channel_info(rgb_channel_info_path: &PathBuf) -> RgbInfo { - let serialized_info = fs::read_to_string(&rgb_channel_info_path).expect("valid rgb info file"); - serde_json::from_str(&serialized_info).expect("valid rgb info file") + let serialized_info = fs::read_to_string(&rgb_channel_info_path).expect("valid rgb info file"); + serde_json::from_str(&serialized_info).expect("valid rgb info file") } /// Whether the channel data for a channel exist pub fn is_channel_rgb(channel_id: &ChannelId, ldk_data_dir: &Path) -> bool { - get_rgb_channel_info_path(&channel_id.to_hex(), ldk_data_dir, false).exists() + get_rgb_channel_info_path(&channel_id.to_hex(), ldk_data_dir, false).exists() } /// Write RgbInfo file pub fn write_rgb_channel_info(path: &PathBuf, rgb_info: &RgbInfo) { - let serialized_info = serde_json::to_string(&rgb_info).expect("valid rgb info"); - fs::write(path, serialized_info).expect("able to write") + let serialized_info = serde_json::to_string(&rgb_info).expect("valid rgb info"); + fs::write(path, serialized_info).expect("able to write") } -fn append_pending_extension(path: &PathBuf) -> PathBuf { - let mut new_path = path.clone(); - new_path.set_extension(format!("{}_pending", new_path.extension().unwrap().to_string_lossy())); - new_path +fn _append_pending_extension(path: &PathBuf) -> PathBuf { + let mut new_path = path.clone(); + new_path.set_extension(format!( + "{}_pending", + new_path.extension().unwrap().to_string_lossy() + )); + new_path } /// Write RGB payment info to file -pub fn write_rgb_payment_info_file(ldk_data_dir: &Path, payment_hash: &PaymentHash, contract_id: ContractId, amount_rgb: u64, swap_payment: bool, inbound: bool) { - let rgb_payment_info_path = get_rgb_payment_info_path(payment_hash, ldk_data_dir, inbound); - let rgb_payment_info_tmp_path = append_pending_extension(&rgb_payment_info_path); - let rgb_payment_info = RgbPaymentInfo { - contract_id, - amount: amount_rgb, - local_rgb_amount: 0, - remote_rgb_amount: 0, - swap_payment, - inbound, - }; - let serialized_info = serde_json::to_string(&rgb_payment_info).expect("valid rgb payment info"); - std::fs::write(rgb_payment_info_path, serialized_info.clone()).expect("able to write rgb payment info file"); - std::fs::write(rgb_payment_info_tmp_path, serialized_info).expect("able to write rgb payment info tmp file"); +pub fn write_rgb_payment_info_file( + ldk_data_dir: &Path, + payment_hash: &PaymentHash, + contract_id: ContractId, + amount_rgb: u64, + swap_payment: bool, + inbound: bool, +) { + let rgb_payment_info_path = get_rgb_payment_info_path(payment_hash, ldk_data_dir, inbound); + let rgb_payment_info_tmp_path = _append_pending_extension(&rgb_payment_info_path); + let rgb_payment_info = RgbPaymentInfo { + contract_id, + amount: amount_rgb, + local_rgb_amount: 0, + remote_rgb_amount: 0, + swap_payment, + inbound, + }; + let serialized_info = serde_json::to_string(&rgb_payment_info).expect("valid rgb payment info"); + std::fs::write(rgb_payment_info_path, serialized_info.clone()) + .expect("able to write rgb payment info file"); + std::fs::write(rgb_payment_info_tmp_path, serialized_info) + .expect("able to write rgb payment info tmp file"); } /// Rename RGB files from temporary to final channel ID -pub(crate) fn rename_rgb_files(channel_id: &ChannelId, temporary_channel_id: &ChannelId, ldk_data_dir: &Path) { - let temp_chan_id = temporary_channel_id.to_hex(); - let chan_id = channel_id.to_hex(); - - fs::rename( - get_rgb_channel_info_path(&temp_chan_id, ldk_data_dir, false), - get_rgb_channel_info_path(&chan_id, ldk_data_dir, false), - ).expect("rename ok"); - fs::rename( - get_rgb_channel_info_path(&temp_chan_id, ldk_data_dir, true), - get_rgb_channel_info_path(&chan_id, ldk_data_dir, true), - ).expect("rename ok"); - - let funding_consignment_tmp = ldk_data_dir.join(format!("consignment_{}", temp_chan_id)); - if funding_consignment_tmp.exists() { - let funding_consignment = ldk_data_dir.join(format!("consignment_{}", chan_id)); - fs::rename(funding_consignment_tmp, funding_consignment).expect("rename ok"); - } +pub(crate) fn rename_rgb_files( + channel_id: &ChannelId, + temporary_channel_id: &ChannelId, + ldk_data_dir: &Path, +) { + let temp_chan_id = temporary_channel_id.to_hex(); + let chan_id = channel_id.to_hex(); + + fs::rename( + get_rgb_channel_info_path(&temp_chan_id, ldk_data_dir, false), + get_rgb_channel_info_path(&chan_id, ldk_data_dir, false), + ) + .expect("rename ok"); + fs::rename( + get_rgb_channel_info_path(&temp_chan_id, ldk_data_dir, true), + get_rgb_channel_info_path(&chan_id, ldk_data_dir, true), + ) + .expect("rename ok"); + + let funding_consignment_tmp = ldk_data_dir.join(format!("consignment_{}", temp_chan_id)); + if funding_consignment_tmp.exists() { + let funding_consignment = ldk_data_dir.join(format!("consignment_{}", chan_id)); + fs::rename(funding_consignment_tmp, funding_consignment).expect("rename ok"); + } } /// Handle funding on the receiver side -pub(crate) fn handle_funding(temporary_channel_id: &ChannelId, funding_txid: String, ldk_data_dir: &Path, consignment_endpoint: RgbTransport) -> Result<(), MsgHandleErrInternal> { - let consignment_endpoint_str = format!("{consignment_endpoint}"); - let proxy_url = if consignment_endpoint_str.starts_with("rpc:") { - let (_, host) = consignment_endpoint_str.split_once(':').unwrap(); - format!("http:{host}") - } else if consignment_endpoint_str.starts_with("rpcs:") { - let (_, host) = consignment_endpoint_str.split_once(':').unwrap(); - format!("https:{host}") - } else { - panic!("impossible"); - }; - - let consignment_res = get_consignment(&proxy_url, funding_txid.clone()); - if consignment_res.is_err() || consignment_res.as_ref().unwrap().result.as_ref().is_none() { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Failed to find RGB consignment".to_owned(), *temporary_channel_id)); - } - let consignment_res = consignment_res.expect("successful get_consignment proxy call").result.expect("result"); - let consignment_bytes = base64::decode(consignment_res.consignment).expect("valid consignment"); - let consignment_path = ldk_data_dir.join(format!("consignment_{}", funding_txid)); - fs::write(consignment_path, consignment_bytes.clone()).expect("unable to write file"); - let consignment_path = ldk_data_dir.join(format!("consignment_{}", temporary_channel_id.to_hex())); - fs::write(consignment_path.clone(), consignment_bytes).expect("unable to write file"); - let consignment = RgbTransfer::load_file(consignment_path).expect("successful consignment load"); - - - let schema_id = consignment.schema_id().to_string(); - match AssetSchema::from_schema_id(schema_id) { - Ok(AssetSchema::Nia) => {} - _ => return Err(MsgHandleErrInternal::send_err_msg_no_close("Unsupported RGB schema".to_owned(), *temporary_channel_id)) - } - - let mut runtime = get_rgb_runtime(ldk_data_dir); - let mut resolver = _get_resolver(ldk_data_dir); - - let testnet = _testnet(ldk_data_dir); - - let blind_seal = BlindSeal::with_blinding(CloseMethod::OpretFirst, TxPtr::WitnessTx, 0, STATIC_BLINDING); - let graph_seal = GraphSeal::from(blind_seal); - let funding_seal = XChain::with(Layer1::Bitcoin, graph_seal); - runtime.stock.store_seal_secret(funding_seal).expect("valid seal"); - - let validated_transfer = match consignment.clone().validate(&mut resolver, testnet) { - Ok(consignment) => consignment, - Err(consignment) => consignment, - }; - let validation_status = validated_transfer.clone().into_validation_status().unwrap(); - let validity = validation_status.validity(); - if ![Validity::Valid, Validity::UnminedTerminals, Validity::UnresolvedTransactions].contains(&validity) { - return Err(MsgHandleErrInternal::send_err_msg_no_close("Invalid RGB consignment for funding".to_owned(), *temporary_channel_id)); - } - - let mut minimal_contract = consignment.clone().into_contract(); - minimal_contract.bundles = none!(); - minimal_contract.terminals = none!(); - let minimal_contract_validated = match minimal_contract.validate(&mut resolver, testnet) { - Ok(consignment) => consignment, - Err(consignment) => consignment, - }; - runtime.stock - .import_contract(minimal_contract_validated, &mut resolver) - .expect("failure importing issued contract"); - - let contract_id = consignment.contract_id(); - - let mut remote_rgb_amount = 0; - for anchored_bundle in &validated_transfer.bundles { - if anchored_bundle.anchor.witness_id_unchecked().to_string() != format!("bc:{funding_txid}") { - continue; - } - 'outer: for transition in anchored_bundle.bundle.known_transitions.values() { - for assignment in transition.assignments.values() { - for fungible_assignment in assignment.as_fungible() { - if let Assign::Revealed { seal, state, .. } = fungible_assignment { - let blind_seal = seal.as_reduced_unsafe(); - if blind_seal.txid == TxPtr::WitnessTx && blind_seal.vout.into_u32() == 0 { - remote_rgb_amount = state.value.as_u64(); - break 'outer; - } - }; - } - } - } - }; - let _status = runtime.stock.accept_transfer(validated_transfer, &mut resolver, true).expect("valid transfer"); - - let rgb_info = RgbInfo { - contract_id, - local_rgb_amount: 0, - remote_rgb_amount, - }; - let temporary_channel_id_str = temporary_channel_id.to_hex(); - write_rgb_channel_info(&get_rgb_channel_info_path(&temporary_channel_id_str, &ldk_data_dir, true), &rgb_info); - write_rgb_channel_info(&get_rgb_channel_info_path(&temporary_channel_id_str, &ldk_data_dir, false), &rgb_info); - - Ok(()) +pub(crate) fn handle_funding( + temporary_channel_id: &ChannelId, + funding_txid: String, + ldk_data_dir: &Path, + consignment_endpoint: RgbTransport, +) -> Result<(), MsgHandleErrInternal> { + let handle = Handle::current(); + let _ = handle.enter(); + let accept_res = futures::executor::block_on(_accept_transfer( + ldk_data_dir, + funding_txid.clone(), + consignment_endpoint, + )); + let (consignment, remote_rgb_amount) = match accept_res { + Ok(res) => res, + Err(RgbLibError::InvalidConsignment) => { + return Err(MsgHandleErrInternal::send_err_msg_no_close( + "Invalid RGB consignment for funding".to_owned(), + *temporary_channel_id, + )) + } + Err(RgbLibError::NoConsignment) => { + return Err(MsgHandleErrInternal::send_err_msg_no_close( + "Failed to find RGB consignment".to_owned(), + *temporary_channel_id, + )) + } + Err(RgbLibError::UnknownRgbSchema { schema_id }) => { + return Err(MsgHandleErrInternal::send_err_msg_no_close( + format!("Unsupported RGB schema: {schema_id}"), + *temporary_channel_id, + )) + } + Err(e) => { + return Err(MsgHandleErrInternal::send_err_msg_no_close( + format!("Unexpected error: {e}"), + *temporary_channel_id, + )) + } + }; + + let consignment_path = ldk_data_dir.join(format!("consignment_{}", funding_txid)); + consignment + .save_file(&consignment_path) + .expect("unable to write file"); + let consignment_path = + ldk_data_dir.join(format!("consignment_{}", temporary_channel_id.to_hex())); + consignment + .save_file(&consignment_path) + .expect("unable to write file"); + + let contract_id = consignment.contract_id(); + + let rgb_info = RgbInfo { + contract_id, + local_rgb_amount: 0, + remote_rgb_amount, + }; + let temporary_channel_id_str = temporary_channel_id.to_hex(); + write_rgb_channel_info( + &get_rgb_channel_info_path(&temporary_channel_id_str, &ldk_data_dir, true), + &rgb_info, + ); + write_rgb_channel_info( + &get_rgb_channel_info_path(&temporary_channel_id_str, &ldk_data_dir, false), + &rgb_info, + ); + + Ok(()) } /// Update RGB channel amount -pub fn update_rgb_channel_amount(channel_id: &str, rgb_offered_htlc: u64, rgb_received_htlc: u64, ldk_data_dir: &Path, pending: bool) { - let (mut rgb_info, info_file_path) = get_rgb_channel_info(channel_id, ldk_data_dir, pending); - - if rgb_offered_htlc > rgb_received_htlc { - let spent = rgb_offered_htlc - rgb_received_htlc; - rgb_info.local_rgb_amount -= spent; - rgb_info.remote_rgb_amount += spent; - } else { - let received = rgb_received_htlc - rgb_offered_htlc; - rgb_info.local_rgb_amount += received; - rgb_info.remote_rgb_amount -= received; - } - - write_rgb_channel_info(&info_file_path, &rgb_info) +pub fn update_rgb_channel_amount( + channel_id: &str, + rgb_offered_htlc: u64, + rgb_received_htlc: u64, + ldk_data_dir: &Path, + pending: bool, +) { + let (mut rgb_info, info_file_path) = get_rgb_channel_info(channel_id, ldk_data_dir, pending); + + if rgb_offered_htlc > rgb_received_htlc { + let spent = rgb_offered_htlc - rgb_received_htlc; + rgb_info.local_rgb_amount -= spent; + rgb_info.remote_rgb_amount += spent; + } else { + let received = rgb_received_htlc - rgb_offered_htlc; + rgb_info.local_rgb_amount += received; + rgb_info.remote_rgb_amount -= received; + } + + write_rgb_channel_info(&info_file_path, &rgb_info) } /// Update pending RGB channel amount -pub(crate) fn update_rgb_channel_amount_pending(channel_id: &ChannelId, rgb_offered_htlc: u64, rgb_received_htlc: u64, ldk_data_dir: &Path) { - update_rgb_channel_amount(&channel_id.to_hex(), rgb_offered_htlc, rgb_received_htlc, ldk_data_dir, true) +pub(crate) fn update_rgb_channel_amount_pending( + channel_id: &ChannelId, + rgb_offered_htlc: u64, + rgb_received_htlc: u64, + ldk_data_dir: &Path, +) { + update_rgb_channel_amount( + &channel_id.to_hex(), + rgb_offered_htlc, + rgb_received_htlc, + ldk_data_dir, + true, + ) } /// Whether the payment is colored pub(crate) fn is_payment_rgb(ldk_data_dir: &Path, payment_hash: &PaymentHash) -> bool { - get_rgb_payment_info_path(payment_hash, ldk_data_dir, false).exists() || - get_rgb_payment_info_path(payment_hash, ldk_data_dir, true).exists() + get_rgb_payment_info_path(payment_hash, ldk_data_dir, false).exists() + || get_rgb_payment_info_path(payment_hash, ldk_data_dir, true).exists() } /// Filter first_hops for contract_id -pub(crate) fn filter_first_hops(ldk_data_dir: &Path, payment_hash: &PaymentHash, first_hops: &mut Vec<&ChannelDetails>) -> ContractId { - let rgb_payment_info_path = get_rgb_payment_info_path(payment_hash, ldk_data_dir, false); - let serialized_info = fs::read_to_string(rgb_payment_info_path).expect("valid rgb payment info file"); - let rgb_payment_info: RgbPaymentInfo = serde_json::from_str(&serialized_info).expect("valid rgb payment info file"); - let contract_id = rgb_payment_info.contract_id; - first_hops.retain(|h| { - let info_file_path = ldk_data_dir.join(h.channel_id.to_hex()); - if !info_file_path.exists() { - return false - } - let serialized_info = fs::read_to_string(info_file_path).expect("valid rgb info file"); - let rgb_info: RgbInfo = serde_json::from_str(&serialized_info).expect("valid rgb info file"); - rgb_info.contract_id == contract_id - }); - contract_id +pub(crate) fn filter_first_hops( + ldk_data_dir: &Path, + payment_hash: &PaymentHash, + first_hops: &mut Vec<&ChannelDetails>, +) -> ContractId { + let rgb_payment_info_path = get_rgb_payment_info_path(payment_hash, ldk_data_dir, false); + let serialized_info = + fs::read_to_string(rgb_payment_info_path).expect("valid rgb payment info file"); + let rgb_payment_info: RgbPaymentInfo = + serde_json::from_str(&serialized_info).expect("valid rgb payment info file"); + let contract_id = rgb_payment_info.contract_id; + first_hops.retain(|h| { + let info_file_path = ldk_data_dir.join(h.channel_id.to_hex()); + if !info_file_path.exists() { + return false; + } + let serialized_info = fs::read_to_string(info_file_path).expect("valid rgb info file"); + let rgb_info: RgbInfo = + serde_json::from_str(&serialized_info).expect("valid rgb info file"); + rgb_info.contract_id == contract_id + }); + contract_id } diff --git a/lightning/src/rgb_utils/proxy.rs b/lightning/src/rgb_utils/proxy.rs deleted file mode 100644 index 00960f7246f..00000000000 --- a/lightning/src/rgb_utils/proxy.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! A module for operating an RGB HTTP JSON-RPC proxy - -use amplify::s; -use reqwest::blocking::Client as BlockingClient; -use reqwest::header::CONTENT_TYPE; -use serde::{Deserialize, Serialize}; -use tokio::task; - -use core::time::Duration; - -const JSON: &str = "application/json"; -const PROXY_TIMEOUT: u8 = 90; - -/// JSON-RPC Error -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct JsonRpcError { - pub(crate) code: i64, - message: String, -} - -/// JSON-RPC request -#[derive(Debug, Deserialize, Serialize)] -pub struct JsonRpcRequest

{ - method: String, - jsonrpc: String, - id: Option, - params: Option

, -} - -/// JSON-RPC response -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct JsonRpcResponse { - id: Option, - pub(crate) result: Option, - pub(crate) error: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct RecipientIDParam { - recipient_id: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub(crate) struct GetConsignmentResponse { - pub(crate) consignment: String, - pub(crate) txid: String, - pub(crate) vout: Option, -} - -fn get_blocking_client() -> BlockingClient { - BlockingClient::builder() - .timeout(Duration::from_secs(PROXY_TIMEOUT as u64)) - .build() - .expect("valid proxy") -} - -pub(crate) fn get_consignment( - url: &str, - recipient_id: String, -) -> Result, reqwest::Error> { - task::block_in_place(|| { - let body = JsonRpcRequest { - method: s!("consignment.get"), - jsonrpc: s!("2.0"), - id: None, - params: Some(RecipientIDParam { recipient_id }), - }; - get_blocking_client() - .post(url) - .header(CONTENT_TYPE, JSON) - .json(&body) - .send()? - .json::>() - }) -} diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs index 5d27a24094d..d4887d66765 100644 --- a/lightning/src/routing/gossip.rs +++ b/lightning/src/routing/gossip.rs @@ -22,7 +22,7 @@ use bitcoin::hashes::hex::FromHex; use bitcoin::network::constants::Network; -use rgbstd::contract::ContractId; +use rgb_lib::ContractId; use crate::events::{MessageSendEvent, MessageSendEventsProvider}; use crate::ln::ChannelId; diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index b1134608f68..73bbdc10260 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -13,7 +13,7 @@ use bitcoin::secp256k1::PublicKey; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; -use rgbstd::contract::ContractId; +use rgb_lib::ContractId; use crate::blinded_path::{BlindedHop, BlindedPath}; use crate::ln::PaymentHash;