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::NativeKeyDerive;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use serde_json as json;
use clightningrpc_common::client::Client;
@ -21,13 +21,9 @@ use clightningrpc_plugin::errors::PluginError;
use clightningrpc_plugin::{commands::RPCCommand, plugin::Plugin};
use clightningrpc_plugin_macros::{plugin, rpc_method};
use rgb_common::anyhow;
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::{anyhow, bitcoin};
mod walletrpc;
@ -88,7 +84,8 @@ pub fn build_plugin() -> anyhow::Result<Plugin<State>> {
};
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)
}
@ -142,59 +139,3 @@ fn on_init(plugin: &mut Plugin<State>) -> json::Value {
plugin.state.rgb_manager = Some(Arc::new(manager));
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::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 crate::plugin::State;
@ -41,6 +44,14 @@ pub struct RGBFundChannelRequest {
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
pub fn fund_rgb_channel(plugin: &mut Plugin<State>, request: Value) -> Result<Value, PluginError> {
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
.call(
"fundchannel",
"fundchannel_start",
json::json!({
"id": request.peer_id,
"amount": balance.to_string(),
}),
)
.map_err(|err| error!("{err}"))?;
let channel_id = fundchannel["channel_id"].to_string();
log::info!("RGB channel id `{channel_id}` created");
let Ok(scriptpubkey) = bitcoin30::ScriptBuf::from_hex(&fundchannel.scriptpubkey) else {
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 {
channel_id,
channel_id: "<channel_id>".to_owned(),
contract_id,
local_rgb_amount: balance,
// 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()
.add_rgb_info(&info, true)
.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!({
"info": fundchannel,
"rgb_info": info,

View File

@ -1,13 +1,11 @@
//! RGB Wallet mock
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use bp::seals::txout::CloseMethod;
use rgb_lib::ScriptBuf;
use rgbstd::containers::BuilderSeal;
use rgbstd::interface::TypedState;
use rgbwallet::bitcoin::{TxOut, Txid};
use rgb_lib::wallet::{AssetNIA, BtcBalance, ReceiveData, Recipient};
use strict_encoding::{FieldName, TypeName};
use crate::bitcoin::bip32::ChildNumber;
@ -99,6 +97,107 @@ impl Wallet {
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
pub fn colored_funding(
&self,

View File

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