cln: reworking channel creation

Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
This commit is contained in:
Vincenzo Palazzo 2024-04-10 19:45:29 +02:00
parent 38d3bc7e4e
commit fcf46e17b7
4 changed files with 205 additions and 123 deletions

View File

@ -12,7 +12,7 @@ use lightning_signer::bitcoin as vlsbtc;
use lightning_signer::signer::derive::KeyDerive; use lightning_signer::signer::derive::KeyDerive;
use lightning_signer::signer::derive::NativeKeyDerive; use lightning_signer::signer::derive::NativeKeyDerive;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::Serialize;
use serde_json as json; use serde_json as json;
use clightningrpc_common::client::Client; use clightningrpc_common::client::Client;
@ -21,13 +21,9 @@ use clightningrpc_plugin::errors::PluginError;
use clightningrpc_plugin::{commands::RPCCommand, plugin::Plugin}; use clightningrpc_plugin::{commands::RPCCommand, plugin::Plugin};
use clightningrpc_plugin_macros::{plugin, rpc_method}; use clightningrpc_plugin_macros::{plugin, rpc_method};
use rgb_common::anyhow;
use rgb_common::bitcoin::bip32::ExtendedPrivKey; use rgb_common::bitcoin::bip32::ExtendedPrivKey;
use rgb_common::bitcoin::consensus::encode::serialize_hex;
use rgb_common::bitcoin::consensus::Decodable;
use rgb_common::bitcoin::hashes::hex::FromHex;
use rgb_common::bitcoin::psbt::PartiallySignedTransaction;
use rgb_common::RGBManager; use rgb_common::RGBManager;
use rgb_common::{anyhow, bitcoin};
mod walletrpc; mod walletrpc;
@ -88,7 +84,8 @@ pub fn build_plugin() -> anyhow::Result<Plugin<State>> {
}; };
plugin.on_init(on_init); plugin.on_init(on_init);
plugin = plugin.register_hook("onfunding_channel_tx", None, None, OnFundingChannelTx); // FIXME: we disable this because it will create loop
//plugin = plugin.register_hook("rpc_command", None, None, OnRpcCommand);
Ok(plugin) Ok(plugin)
} }
@ -142,59 +139,3 @@ fn on_init(plugin: &mut Plugin<State>) -> json::Value {
plugin.state.rgb_manager = Some(Arc::new(manager)); plugin.state.rgb_manager = Some(Arc::new(manager));
json::json!({}) json::json!({})
} }
#[derive(Clone, Debug)]
struct OnFundingChannelTx;
#[derive(Clone, Debug, Deserialize)]
struct OnFundingChannelTxHook {
onfunding_channel_tx: OnFundingChannelTxBody,
}
#[derive(Clone, Debug, Deserialize)]
struct OnFundingChannelTxBody {
tx: String,
txid: String,
psbt: String,
channel_id: String,
}
#[derive(Clone, Debug, Serialize)]
struct OnFundingChannelTxResponse {
tx: String,
psbt: String,
}
impl RPCCommand<State> for OnFundingChannelTx {
fn call<'c>(
&self,
plugin: &mut Plugin<State>,
body: json::Value,
) -> Result<json::Value, clightningrpc_plugin::errors::PluginError> {
log::info!("Calling hook `onfunding_channel_tx` with `{body}`",);
let body: OnFundingChannelTxHook = json::from_value(body)?;
let body = body.onfunding_channel_tx;
let raw_tx = Vec::from_hex(&body.tx).unwrap();
let tx: bitcoin::Transaction = Decodable::consensus_decode(&mut raw_tx.as_slice()).unwrap();
let txid = bitcoin::Txid::from_str(&body.txid).unwrap();
assert_eq!(txid, tx.txid());
let psbt_from_base64 =
bitcoin::base64::decode(&body.psbt).map_err(|err| error!("{err}"))?;
let mut psbt = PartiallySignedTransaction::deserialize(&psbt_from_base64)
.map_err(|err| error!("{err}"))?;
let tx = plugin
.state
.manager()
.handle_onfunding_tx(tx, txid, &mut psbt, body.channel_id)
.unwrap();
let result = OnFundingChannelTxResponse {
tx: serialize_hex(&tx),
psbt: psbt.serialize_hex(),
};
Ok(json::json!({ "result": json::to_value(&result)? }))
}
}
// FIXME: add an hook that will add rgb onchain address to the wallet.

View File

@ -12,7 +12,10 @@ use clightningrpc_plugin::error;
use clightningrpc_plugin::errors::PluginError; use clightningrpc_plugin::errors::PluginError;
use clightningrpc_plugin::plugin::Plugin; use clightningrpc_plugin::plugin::Plugin;
// TODO this should be hidden inside the common crate use rgb_common::bitcoin::psbt::Psbt;
use rgb_common::bitcoin30;
use rgb_common::core::ContractId;
use rgb_common::lib::wallet::Balance;
use rgb_common::types::RgbInfo; use rgb_common::types::RgbInfo;
use crate::plugin::State; use crate::plugin::State;
@ -41,6 +44,14 @@ pub struct RGBFundChannelRequest {
asset_id: String, asset_id: String,
} }
#[derive(Debug, Deserialize, Serialize)]
pub struct FundincStartResponse {
funding_address: String,
scriptpubkey: String,
close_to: String,
channel_type: json::Value,
}
/// Opening a RGB channel /// Opening a RGB channel
pub fn fund_rgb_channel(plugin: &mut Plugin<State>, request: Value) -> Result<Value, PluginError> { pub fn fund_rgb_channel(plugin: &mut Plugin<State>, request: Value) -> Result<Value, PluginError> {
let request: RGBFundChannelRequest = json::from_value(request)?; let request: RGBFundChannelRequest = json::from_value(request)?;
@ -81,21 +92,31 @@ pub fn fund_rgb_channel(plugin: &mut Plugin<State>, request: Value) -> Result<Va
)); ));
} }
let fundchannel: json::Value = plugin let fundchannel: FundincStartResponse = plugin
.state .state
.call( .call(
"fundchannel", "fundchannel_start",
json::json!({ json::json!({
"id": request.peer_id, "id": request.peer_id,
"amount": balance.to_string(), "amount": balance.to_string(),
}), }),
) )
.map_err(|err| error!("{err}"))?; .map_err(|err| error!("{err}"))?;
let channel_id = fundchannel["channel_id"].to_string(); let Ok(scriptpubkey) = bitcoin30::ScriptBuf::from_hex(&fundchannel.scriptpubkey) else {
log::info!("RGB channel id `{channel_id}` created"); let _: json::Value = plugin
.state
.call(
"fundchannel_cancel",
json::json!({
"id": request.peer_id,
}),
)
.map_err(|err| error!("{err}"))?;
return Err(error!("Impossible parse `scriptpubkey`, failing funding"));
};
let info = RgbInfo { let info = RgbInfo {
channel_id, channel_id: "<channel_id>".to_owned(),
contract_id, contract_id,
local_rgb_amount: balance, local_rgb_amount: balance,
// FIXME: Check that we are not opening a dual funding channel with // FIXME: Check that we are not opening a dual funding channel with
@ -108,6 +129,37 @@ pub fn fund_rgb_channel(plugin: &mut Plugin<State>, request: Value) -> Result<Va
.manager() .manager()
.add_rgb_info(&info, true) .add_rgb_info(&info, true)
.map_err(|err| error!("{err}"))?; .map_err(|err| error!("{err}"))?;
let Ok(psbt) =
plugin
.state
.manager()
.build_rgb_funding_transaction(&info, scriptpubkey, 1.1, 6)
else {
let _: json::Value = plugin
.state
.call(
"fundchannel_cancel",
json::json!({
"id": request.peer_id,
}),
)
.map_err(|err| error!("{err}"))?;
return Err(error!(
"Impossible .build_rgb_funding_transaction, failing funding"
));
};
let psbt = psbt.serialize_hex();
let fundchannel: json::Value = plugin
.state
.call(
"fundchannel_complete",
json::json!({
"id": request.peer_id,
"psbt": psbt,
}),
)
.map_err(|err| error!("{err}"))?;
Ok(json::json!({ Ok(json::json!({
"info": fundchannel, "info": fundchannel,
"rgb_info": info, "rgb_info": info,

View File

@ -1,13 +1,11 @@
//! RGB Wallet mock //! RGB Wallet mock
use std::path::{Path, PathBuf}; use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use bp::seals::txout::CloseMethod; use bp::seals::txout::CloseMethod;
use rgb_lib::ScriptBuf; use rgb_lib::wallet::{AssetNIA, BtcBalance, ReceiveData, Recipient};
use rgbstd::containers::BuilderSeal;
use rgbstd::interface::TypedState;
use rgbwallet::bitcoin::{TxOut, Txid};
use strict_encoding::{FieldName, TypeName}; use strict_encoding::{FieldName, TypeName};
use crate::bitcoin::bip32::ChildNumber; use crate::bitcoin::bip32::ChildNumber;
@ -99,6 +97,107 @@ impl Wallet {
Ok(master_xprv.derive_priv(&Secp256k1::new(), &account_derivation_path)?) Ok(master_xprv.derive_priv(&Secp256k1::new(), &account_derivation_path)?)
} }
#[cfg(debug_assertions)]
pub fn issue_asset_nia(
&self,
ticker: String,
name: String,
precision: u8,
amounts: Vec<u64>,
) -> anyhow::Result<AssetNIA> {
let Some(ref online) = self.online_wallet else {
anyhow::bail!("Wallet is not online");
};
let assert = self.wallet.lock().unwrap().issue_asset_nia(
online.clone(),
ticker,
name,
precision,
amounts,
)?;
Ok(assert)
}
pub fn new_addr(&self) -> anyhow::Result<String> {
let addr = self.wallet.lock().unwrap().get_address()?;
Ok(addr)
}
pub fn new_blind_receive(
&self,
asset_id: Option<String>,
transport_endpoints: Vec<String>,
min_confirmations: u8,
) -> anyhow::Result<ReceiveData> {
let blind_receive = self.wallet.lock().unwrap().blind_receive(
asset_id,
None,
None,
transport_endpoints,
min_confirmations,
)?;
Ok(blind_receive)
}
/// Preallocate the UTXO assets on chain for RGB.
pub fn create_utxos<F>(&self, fee_rate: f32, sign_psbt: F) -> anyhow::Result<()>
where
F: FnOnce(String) -> anyhow::Result<String>,
{
// FIXME: Mh, I should know why for this?
const UTXO_SIZE_SAT: u32 = 32000;
let wallet_online = self
.online_wallet
.clone()
.ok_or(anyhow::anyhow!("Wallet not online"))?;
let wallet = self.wallet.lock().unwrap();
let unsigned_psbt = wallet.create_utxos_begin(
wallet_online.clone(),
false,
Some(1),
Some(UTXO_SIZE_SAT),
fee_rate,
)?;
let psbt = sign_psbt(unsigned_psbt)?;
wallet.create_utxos_end(wallet_online, psbt)?;
Ok(())
}
pub fn get_btc_balance(&self) -> anyhow::Result<BtcBalance> {
let wallet = self.wallet.lock().unwrap();
let balance = wallet.get_btc_balance(
self.online_wallet
.clone()
.ok_or(anyhow::anyhow!("wallet is not online"))?,
)?;
Ok(balance)
}
pub fn rgb_funding_complete(
&self,
recipient_map: HashMap<String, Vec<Recipient>>,
fee_rate: f32,
min_conf: u8,
) -> anyhow::Result<bitcoin::psbt::PartiallySignedTransaction> {
let wallet = self.wallet.lock().unwrap();
let online = self
.online_wallet
.as_ref()
.ok_or(anyhow::anyhow!("Wallet not online"))?;
let unsigned_psbt =
wallet.send_begin(online.clone(), recipient_map, true, fee_rate, min_conf)?;
// FIXME: sign the psbt with the main key wallet
Ok(bitcoin::psbt::PartiallySignedTransaction::from_str(
&unsigned_psbt,
)?)
}
/// Given A PSBT we add the rgb information into it /// Given A PSBT we add the rgb information into it
pub fn colored_funding( pub fn colored_funding(
&self, &self,

View File

@ -6,9 +6,7 @@ use bitcoin::bip32::ExtendedPrivKey;
use rgb_lib::wallet::Balance; use rgb_lib::wallet::Balance;
use rgb_lib::wallet::Recipient; use rgb_lib::wallet::Recipient;
use rgb_lib::wallet::RecipientData; use rgb_lib::wallet::RecipientData;
use rgb_lib::ScriptBuf;
use rgbwallet::bitcoin; use rgbwallet::bitcoin;
use rgbwallet::bitcoin::psbt::PartiallySignedTransaction;
use crate::internal_wallet::Wallet; use crate::internal_wallet::Wallet;
use crate::lib::BitcoinNetwork; use crate::lib::BitcoinNetwork;
@ -77,44 +75,36 @@ impl RGBManager {
} }
/// Modify the funding transaction before sign it with the node signer. /// Modify the funding transaction before sign it with the node signer.
pub fn handle_onfunding_tx( pub fn build_rgb_funding_transaction(
&self, &self,
tx: bitcoin::Transaction, rgb_info: &RgbInfo,
txid: bitcoin::Txid, scriptpubkey: bitcoin::ScriptBuf,
psbt: &mut PartiallySignedTransaction, fee_rate: f32,
channel_id: String, min_conf: u8,
) -> anyhow::Result<bitcoin::Transaction> { ) -> anyhow::Result<bitcoin::psbt::PartiallySignedTransaction> {
debug_assert!(tx.txid() == txid); // Step 1: get the rgb info https://github.com/RGB-Tools/rgb-lightning-node/blob/master/src/ldk.rs#L328
debug_assert!(psbt.clone().extract_tx().txid() == txid); //let mut info = self.storage.get_rgb_channel_info_pending(&channel_id)?;
// allow looup by channel and returnt the rgb info //info.channel_id = channel_id;
if self.storage.is_channel_rgb(&channel_id, false)? {
// Step 1: get the rgb info https://github.com/RGB-Tools/rgb-lightning-node/blob/master/src/ldk.rs#L328
let mut info = self.storage.get_rgb_channel_info_pending(&channel_id)?;
info.channel_id = channel_id;
// Step 2: Modify the psbt and start sending with the rgb wallet
let funding_outpoint = types::OutPoint {
txid,
index: 0, /* FIXME: cln should tell this info to us */
};
self.prepare_rgb_tx(&info, funding_outpoint, &tx, psbt)?;
// Step 3: Make the cosignemtn and post it somewhere // Step 2: Modify the psbt and start sending with the rgb wallet
let consignment_path = self let psbt = self.prepare_rgb_tx(&rgb_info, scriptpubkey, fee_rate, min_conf)?;
.wallet() // FIXME: avoid cloning
.path() let txid = psbt.clone().extract_tx().txid();
.join("transfers") // Step 3: Make the cosignemtn and post it somewhere
.join(txid.to_string().clone()) let consignment_path = self
.join(info.contract_id.to_string()) .wallet()
.join("consignment_out"); .path()
self.consignment_proxy().post_consignment( .join("transfers")
&consignment_path, .join(txid.to_string().clone())
txid.to_string(), .join(rgb_info.contract_id.to_string())
txid.to_string(), .join("consignment_out");
Some(0), self.consignment_proxy().post_consignment(
)?; &consignment_path,
return Ok(tx); txid.to_string(),
} txid.to_string(),
Ok(tx) Some(0),
)?;
return Ok(psbt);
} }
// Missing parameters: amount_sat of the funding tx and // Missing parameters: amount_sat of the funding tx and
@ -125,25 +115,25 @@ impl RGBManager {
fn prepare_rgb_tx( fn prepare_rgb_tx(
&self, &self,
info: &types::RgbInfo, info: &types::RgbInfo,
funding_outpoint: types::OutPoint, scriptpubkey: bitcoin::ScriptBuf,
tx: &bitcoin::Transaction, fee_rate: f32,
psb: &mut PartiallySignedTransaction, min_conf: u8,
) -> anyhow::Result<()> { ) -> anyhow::Result<bitcoin::psbt::PartiallySignedTransaction> {
// TODO: this is still needed?
let recipient_map = amplify::map! { let recipient_map = amplify::map! {
info.contract_id.to_string() => vec![Recipient { info.contract_id.to_string() => vec![Recipient {
recipient_data: RecipientData::WitnessData { recipient_data: RecipientData::WitnessData {
script_buf: ScriptBuf::new(), // TODO: get this from the transaction script_buf: scriptpubkey,
amount_sat: 0, // TODO get this from the transaction amount_sat: info.remote_rgb_amount,
blinding: Some(STATIC_BLINDING), blinding: Some(STATIC_BLINDING),
}, },
amount: info.local_rgb_amount, amount: info.local_rgb_amount,
transport_endpoints: vec![self.consignment_proxy.url.clone()] transport_endpoints: vec![self.consignment_proxy.url.clone()]
}] }]
}; };
// FIXME: find the position of the vout;
self.wallet let psbt = self
.colored_funding(psb, funding_outpoint, info, 0)?; .wallet
Ok(()) .rgb_funding_complete(recipient_map, fee_rate, min_conf)?;
Ok(psbt)
} }
} }