Merge pull request #3 from vincenzopalazzo/macros/accountkey-derivation
common: handle funding transaction
This commit is contained in:
commit
7e5d7588a2
|
@ -7,6 +7,7 @@ use std::io;
|
|||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use json::Value;
|
||||
use lightning_signer::bitcoin as vlsbtc;
|
||||
use lightning_signer::signer::derive::KeyDerive;
|
||||
use lightning_signer::signer::derive::NativeKeyDerive;
|
||||
|
@ -15,16 +16,21 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json as json;
|
||||
|
||||
use clightningrpc_common::client::Client;
|
||||
use clightningrpc_plugin::error;
|
||||
use clightningrpc_plugin::errors::PluginError;
|
||||
use clightningrpc_plugin::{commands::RPCCommand, plugin::Plugin};
|
||||
use clightningrpc_plugin_macros::plugin;
|
||||
use clightningrpc_plugin_macros::{plugin, rpc_method};
|
||||
|
||||
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;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct State {
|
||||
/// The RGB Manager where we ask to do everything
|
||||
|
@ -74,7 +80,9 @@ pub fn build_plugin() -> anyhow::Result<Plugin<State>> {
|
|||
state: State::new(),
|
||||
dynamic: true,
|
||||
notification: [ ],
|
||||
methods: [],
|
||||
methods: [
|
||||
rgb_balance,
|
||||
],
|
||||
hooks: [],
|
||||
};
|
||||
plugin.on_init(on_init);
|
||||
|
@ -83,6 +91,11 @@ pub fn build_plugin() -> anyhow::Result<Plugin<State>> {
|
|||
Ok(plugin)
|
||||
}
|
||||
|
||||
#[rpc_method(rpc_name = "rgbbalances", description = "Return the RGB balance")]
|
||||
pub fn rgb_balance(plugin: &mut Plugin<State>, requet: Value) -> Result<Value, PluginError> {
|
||||
walletrpc::rgb_balance(plugin, requet)
|
||||
}
|
||||
|
||||
fn read_secret(file: fs::File, network: &str) -> anyhow::Result<ExtendedPrivKey> {
|
||||
let buffer = io::BufReader::new(file);
|
||||
let network = vlsbtc::Network::from_str(network)?;
|
||||
|
@ -159,14 +172,20 @@ impl RPCCommand<State> for OnFundingChannelTx {
|
|||
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, body.channel_id)
|
||||
.handle_onfunding_tx(tx, txid, &mut psbt, body.channel_id)
|
||||
.unwrap();
|
||||
let result = OnFundingChannelTxResponse {
|
||||
tx: serialize_hex(&tx),
|
||||
psbt: body.psbt,
|
||||
psbt: psbt.serialize_hex(),
|
||||
};
|
||||
Ok(json::json!({ "result": json::to_value(&result)? }))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
//! RGB Wallet RPC methods
|
||||
//!
|
||||
//! Author: Vincenzo Palazzo <vincenzopalazzo@member.fsf.org>
|
||||
use std::str::FromStr;
|
||||
|
||||
use rgb_common::core::ContractId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json as json;
|
||||
use serde_json::Value;
|
||||
|
||||
use clightningrpc_plugin::error;
|
||||
use clightningrpc_plugin::errors::PluginError;
|
||||
use clightningrpc_plugin::plugin::Plugin;
|
||||
|
||||
use rgb_common::lib::wallet::Balance;
|
||||
// TODO this should be hidden inside the common crate
|
||||
use rgb_common::types::RgbInfo;
|
||||
|
||||
use crate::plugin::State;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct RGBBalanceRequest {
|
||||
asset_id: String,
|
||||
}
|
||||
|
||||
/// Return the balance of an RGB assert
|
||||
pub fn rgb_balance(plugin: &mut Plugin<State>, request: Value) -> Result<Value, PluginError> {
|
||||
let request: RGBBalanceRequest = json::from_value(request).map_err(|err| error!("{err}"))?;
|
||||
let balance = plugin
|
||||
.state
|
||||
.manager()
|
||||
.assert_balance(request.asset_id)
|
||||
.map_err(|err| error!("{err}"));
|
||||
Ok(json::to_value(balance)?)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct RGBFundChannelRequest {
|
||||
peer_id: String,
|
||||
amount_msat: String,
|
||||
asset_id: String,
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
|
||||
// check if the asset id is valit
|
||||
let contract_id = ContractId::from_str(&request.asset_id).map_err(|err| error!("{err}"))?;
|
||||
|
||||
let assert_balance: Balance = plugin
|
||||
.state
|
||||
.call(
|
||||
"rpcbalance",
|
||||
RGBBalanceRequest {
|
||||
asset_id: request.asset_id.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(|err| error!("{err}"))?;
|
||||
|
||||
// FIXME: Check if we are connected with the peer otherwise connect to them
|
||||
|
||||
// FIXME: we need the magic of core lightning here
|
||||
let balance = request
|
||||
.amount_msat
|
||||
.parse::<u64>()
|
||||
.map_err(|err| error!("{err}"))?;
|
||||
|
||||
if balance < assert_balance.spendable {
|
||||
return Err(error!(
|
||||
"Balance avaialbe `{}` is not enough to open a channel of `{}` capacity",
|
||||
assert_balance.spendable, balance
|
||||
));
|
||||
}
|
||||
|
||||
let fundchannel: json::Value = plugin
|
||||
.state
|
||||
.call("fundchannel", json::json!({}))
|
||||
.map_err(|err| error!("{err}"))?;
|
||||
let channel_id = fundchannel["channel_id"].to_string();
|
||||
log::info!("RGB channel id `{channel_id}` created");
|
||||
|
||||
let info = RgbInfo {
|
||||
channel_id,
|
||||
contract_id,
|
||||
local_rgb_amount: balance,
|
||||
// FIXME: Check that we are not opening a dual funding channel with
|
||||
// liquidity ads
|
||||
remote_rgb_amount: 0,
|
||||
};
|
||||
|
||||
plugin
|
||||
.state
|
||||
.manager()
|
||||
.add_rgb_info(&info, true)
|
||||
.map_err(|err| error!("{err}"))?;
|
||||
Ok(json::json!({
|
||||
"info": fundchannel,
|
||||
"rgb_info": info,
|
||||
}))
|
||||
}
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
anyhow = "1.0.71"
|
||||
|
||||
lightning = "0.0.118"
|
||||
bitcoin = "0.30"
|
||||
|
||||
amplify = "=4.5.0"
|
||||
base64 = "0.13.0"
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
//! RGB Wallet mock
|
||||
use std::path::{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 strict_encoding::{FieldName, TypeName};
|
||||
|
||||
use crate::bitcoin::bip32::ChildNumber;
|
||||
use crate::bitcoin::bip32::ExtendedPrivKey;
|
||||
use crate::bitcoin::bip32::ExtendedPubKey;
|
||||
use crate::bitcoin::psbt::PartiallySignedTransaction;
|
||||
use crate::bitcoin::secp256k1::hashes::Hash;
|
||||
use crate::bitcoin::secp256k1::Secp256k1;
|
||||
use crate::bitcoin::Network;
|
||||
use crate::bitcoin30::psbt::PartiallySignedTransaction as RgbPsbt;
|
||||
use crate::core::contract::Operation;
|
||||
use crate::lib::utils::load_rgb_runtime;
|
||||
use crate::lib::wallet::{DatabaseType, Online, Wallet as RgbWallet, WalletData};
|
||||
use crate::lib::BitcoinNetwork;
|
||||
use crate::rgb::persistence::Inventory;
|
||||
use crate::rgb::psbt::opret::OutputOpret;
|
||||
use crate::rgb::psbt::{PsbtDbc, RgbExt, RgbInExt};
|
||||
use crate::rgb_manager::STATIC_BLINDING;
|
||||
use crate::std::contract::GraphSeal;
|
||||
use crate::types;
|
||||
use crate::types::RgbInfo;
|
||||
|
||||
pub struct Wallet {
|
||||
path: String,
|
||||
pub network: BitcoinNetwork,
|
||||
pub wallet: Arc<Mutex<RgbWallet>>,
|
||||
pub online_wallet: Option<Online>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn new(
|
||||
network: &BitcoinNetwork,
|
||||
xprv: ExtendedPrivKey,
|
||||
path: &str,
|
||||
) -> anyhow::Result<Self> {
|
||||
// with rgb library tere is a new function for calculate the account key
|
||||
let account_privkey = Self::derive_account_xprv_from_mnemonic(network.clone(), &xprv)?;
|
||||
let account_xpub = ExtendedPubKey::from_priv(&Secp256k1::new(), &account_privkey);
|
||||
let mut wallet = RgbWallet::new(WalletData {
|
||||
data_dir: path.to_owned(),
|
||||
bitcoin_network: network.clone(),
|
||||
database_type: DatabaseType::Sqlite,
|
||||
max_allocations_per_utxo: 11,
|
||||
pubkey: account_xpub.to_string().to_owned(),
|
||||
mnemonic: None,
|
||||
vanilla_keychain: None,
|
||||
})?;
|
||||
let network = Network::from_str(&network.to_string())?;
|
||||
let url = match network {
|
||||
Network::Bitcoin => "https://mempool.space/api",
|
||||
Network::Testnet => "https://mempool.space/testnet/api",
|
||||
Network::Signet => "https://mempool.space/signet/api",
|
||||
Network::Regtest => "",
|
||||
_ => anyhow::bail!("Network `{network}` not supported"),
|
||||
};
|
||||
let mut online_info = None;
|
||||
if !url.is_empty() {
|
||||
online_info = Some(wallet.go_online(false, url.to_owned())?);
|
||||
}
|
||||
Ok(Self {
|
||||
path: path.to_owned(),
|
||||
wallet: Arc::new(Mutex::new(wallet)),
|
||||
network: BitcoinNetwork::from_str(&network.to_string())?,
|
||||
online_wallet: online_info,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.wallet.lock().unwrap().get_wallet_dir()
|
||||
}
|
||||
|
||||
fn get_coin_type(bitcoin_network: BitcoinNetwork) -> u32 {
|
||||
u32::from(bitcoin_network != BitcoinNetwork::Mainnet)
|
||||
}
|
||||
|
||||
fn derive_account_xprv_from_mnemonic(
|
||||
bitcoin_network: BitcoinNetwork,
|
||||
master_xprv: &ExtendedPrivKey,
|
||||
) -> anyhow::Result<ExtendedPrivKey> {
|
||||
const PURPOSE: u8 = 84;
|
||||
const ACCOUNT: u8 = 0;
|
||||
|
||||
let coin_type = Self::get_coin_type(bitcoin_network);
|
||||
let account_derivation_path = vec![
|
||||
ChildNumber::from_hardened_idx(PURPOSE as u32).unwrap(),
|
||||
ChildNumber::from_hardened_idx(coin_type).unwrap(),
|
||||
ChildNumber::from_hardened_idx(ACCOUNT as u32).unwrap(),
|
||||
];
|
||||
Ok(master_xprv.derive_priv(&Secp256k1::new(), &account_derivation_path)?)
|
||||
}
|
||||
|
||||
/// Given A PSBT we add the rgb information into it
|
||||
pub fn colored_funding(
|
||||
&self,
|
||||
psbt: &mut PartiallySignedTransaction,
|
||||
funding_outpoint: types::OutPoint,
|
||||
commitment_info: &RgbInfo,
|
||||
holder_vout: u32,
|
||||
) -> anyhow::Result<()> {
|
||||
use bp::Outpoint;
|
||||
|
||||
let mut tx = psbt.clone().extract_tx();
|
||||
tx.output.push(TxOut {
|
||||
value: 0,
|
||||
script_pubkey: ScriptBuf::new_op_return(&[1]),
|
||||
});
|
||||
let mut rgb_psbt = RgbPsbt::from_unsigned_tx(tx.clone())?;
|
||||
let mut runtime = load_rgb_runtime(self.path.clone().into(), self.network)?;
|
||||
|
||||
let holder_vout_amount = commitment_info.local_rgb_amount;
|
||||
let counterparty_vout_amount = commitment_info.remote_rgb_amount;
|
||||
let counterparty_vout = holder_vout ^ 1;
|
||||
|
||||
let mut beneficiaries = vec![];
|
||||
let mut asset_transition_builder = runtime
|
||||
.runtime
|
||||
.transition_builder(
|
||||
commitment_info.contract_id,
|
||||
TypeName::try_from("RGB20").unwrap(),
|
||||
None::<&str>,
|
||||
)
|
||||
.map_err(|err| anyhow::anyhow!("{err}"))?;
|
||||
let assignment_id = asset_transition_builder
|
||||
.assignments_type(&FieldName::from("beneficiary"))
|
||||
.ok_or(anyhow::anyhow!(
|
||||
"`None` returned during `asset_transition_builder.assignments_type`"
|
||||
))?;
|
||||
|
||||
if holder_vout_amount > 0 {
|
||||
let holder_seal = BuilderSeal::Revealed(GraphSeal::with_vout(
|
||||
CloseMethod::OpretFirst,
|
||||
holder_vout as u32,
|
||||
STATIC_BLINDING,
|
||||
));
|
||||
beneficiaries.push(holder_seal);
|
||||
asset_transition_builder = asset_transition_builder.add_raw_state(
|
||||
assignment_id,
|
||||
holder_seal,
|
||||
TypedState::Amount(holder_vout_amount),
|
||||
)?;
|
||||
}
|
||||
|
||||
if counterparty_vout_amount > 0 {
|
||||
let counterparty_seal = BuilderSeal::Revealed(GraphSeal::with_vout(
|
||||
CloseMethod::OpretFirst,
|
||||
counterparty_vout as u32,
|
||||
STATIC_BLINDING,
|
||||
));
|
||||
beneficiaries.push(counterparty_seal);
|
||||
asset_transition_builder = asset_transition_builder.add_raw_state(
|
||||
assignment_id,
|
||||
counterparty_seal,
|
||||
TypedState::Amount(counterparty_vout_amount),
|
||||
)?;
|
||||
}
|
||||
|
||||
let prev_outputs = rgb_psbt
|
||||
.unsigned_tx
|
||||
.input
|
||||
.iter()
|
||||
.map(|txin| txin.previous_output)
|
||||
.map(|outpoint| Outpoint::new(outpoint.txid.to_byte_array().into(), outpoint.vout))
|
||||
.collect::<Vec<_>>();
|
||||
for (opout, _state) in runtime
|
||||
.runtime
|
||||
.state_for_outpoints(commitment_info.contract_id, prev_outputs.iter().copied())
|
||||
.map_err(|err| anyhow::anyhow!("{err}"))?
|
||||
{
|
||||
asset_transition_builder = asset_transition_builder.add_input(opout)?;
|
||||
}
|
||||
let transition =
|
||||
asset_transition_builder.complete_transition(commitment_info.contract_id)?;
|
||||
|
||||
let inputs = [Outpoint::new(
|
||||
bp::Txid::from_str(&funding_outpoint.txid.to_string()).unwrap(),
|
||||
funding_outpoint.index as u32,
|
||||
)];
|
||||
for (input, txin) in rgb_psbt.inputs.iter_mut().zip(&rgb_psbt.unsigned_tx.input) {
|
||||
let prevout = txin.previous_output;
|
||||
let outpoint = Outpoint::new(prevout.txid.to_byte_array().into(), prevout.vout);
|
||||
if inputs.contains(&outpoint) {
|
||||
input.set_rgb_consumer(commitment_info.contract_id, transition.id())?;
|
||||
}
|
||||
}
|
||||
rgb_psbt.push_rgb_transition(transition)?;
|
||||
// FIXME: we can comment the code below?
|
||||
// let bundles = rgb_psbt.rgb_bundles().expect("able to get bundles");
|
||||
let (opreturn_index, _) = rgb_psbt
|
||||
.unsigned_tx
|
||||
.output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, o)| o.script_pubkey.is_op_return())
|
||||
.unwrap();
|
||||
let (_, opreturn_output) = rgb_psbt
|
||||
.outputs
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(i, _)| i == &opreturn_index)
|
||||
.unwrap();
|
||||
opreturn_output.set_opret_host()?;
|
||||
rgb_psbt.rgb_bundle_to_lnpbp4().expect("ok");
|
||||
let _ = rgb_psbt.dbc_conclude(CloseMethod::OpretFirst)?;
|
||||
|
||||
*psbt = PartiallySignedTransaction::from_str(&rgb_psbt.to_string()).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
mod comm;
|
||||
mod internal_wallet;
|
||||
mod proxy;
|
||||
mod rgb_manager;
|
||||
mod rgb_storage;
|
||||
pub mod types;
|
||||
|
||||
use lightning as ldk;
|
||||
use reqwest::blocking::Client as BlockingClient;
|
||||
|
||||
pub use anyhow;
|
||||
// Re-exporting RGB dependencies under a single module.
|
||||
pub use bitcoin as bitcoin30;
|
||||
pub use rgb;
|
||||
pub use rgb::interface::rgb20 as asset20;
|
||||
pub use rgb_core as core;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
//! A module for operating an RGB HTTP JSON-RPC proxy
|
||||
use core::str::FromStr;
|
||||
use core::time::Duration;
|
||||
use std::path::Path;
|
||||
|
||||
use amplify::s;
|
||||
use reqwest::blocking::multipart::Form;
|
||||
use reqwest::blocking::multipart::Part;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -13,18 +16,23 @@ const JSON: &str = "application/json";
|
|||
const PROXY_TIMEOUT: u8 = 90;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
pub struct ConsignmentClient {
|
||||
inner: BlockingClient,
|
||||
network: Network,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
impl ConsignmentClient {
|
||||
pub fn new(network: &str) -> anyhow::Result<Self> {
|
||||
let network = Network::from_str(network)?;
|
||||
let inner = BlockingClient::builder()
|
||||
.timeout(Duration::from_secs(PROXY_TIMEOUT as u64))
|
||||
.build()?;
|
||||
Ok(Self { inner, network })
|
||||
Ok(Self {
|
||||
inner,
|
||||
network,
|
||||
url: "".to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_consignment(&self, consignment_id: &str) -> anyhow::Result<JsonRpcResponse<String>> {
|
||||
|
@ -38,7 +46,7 @@ impl Client {
|
|||
};
|
||||
|
||||
// FIXME: add a URL for this
|
||||
let url = "";
|
||||
let url = format!("{}/TODO", self.url);
|
||||
let resp = self
|
||||
.inner
|
||||
.post(format!("{url}"))
|
||||
|
@ -48,6 +56,43 @@ impl Client {
|
|||
.json::<JsonRpcResponse<String>>()?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub fn post_consignment(
|
||||
&self,
|
||||
consignment_path: &Path,
|
||||
recipient_id: String,
|
||||
txid: String,
|
||||
vout: Option<u32>,
|
||||
) -> anyhow::Result<()> {
|
||||
let file_name = consignment_path
|
||||
.file_name()
|
||||
.map(|filename| filename.to_string_lossy().into_owned())
|
||||
.unwrap();
|
||||
let consignment_file = Part::file(consignment_path)?.file_name(file_name);
|
||||
let params = serde_json::json!({
|
||||
"recipient_id": recipient_id,
|
||||
"txid": txid,
|
||||
"vout": vout,
|
||||
});
|
||||
|
||||
let form = Form::new()
|
||||
.text("method", "consignment.post")
|
||||
.text("jsonrpc", "2.0")
|
||||
.text("id", "1")
|
||||
.text("params", serde_json::to_string(¶ms)?)
|
||||
.part("file", consignment_file);
|
||||
|
||||
// FIXME: add a URL for this
|
||||
let url = format!("{}/TODO", self.url);
|
||||
self.inner
|
||||
.post(format!("{url}"))
|
||||
.header(CONTENT_TYPE, JSON)
|
||||
.multipart(form)
|
||||
.send()?
|
||||
.json::<JsonRpcResponse<String>>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON-RPC Error
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
//! RGB Manager
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use bitcoin::bip32::ChildNumber;
|
||||
use bitcoin::bip32::{ExtendedPrivKey, ExtendedPubKey};
|
||||
use bitcoin::secp256k1;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
use bitcoin::Network;
|
||||
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::lib::wallet::{DatabaseType, Wallet, WalletData};
|
||||
use crate::internal_wallet::Wallet;
|
||||
use crate::lib::BitcoinNetwork;
|
||||
use crate::proxy;
|
||||
use crate::rgb_storage as store;
|
||||
use crate::rgb_storage::RGBStorage;
|
||||
use crate::types;
|
||||
use crate::types::RgbInfo;
|
||||
|
||||
/// Static blinding costant (will be removed in the future)
|
||||
/// See https://github.com/RGB-Tools/rust-lightning/blob/80497c4086beea490b56e5b8413b7f6d86f2c042/lightning/src/rgb_utils/mod.rs#L53
|
||||
pub const STATIC_BLINDING: u64 = 777;
|
||||
|
||||
pub struct RGBManager {
|
||||
proxy_client: Arc<proxy::Client>,
|
||||
wallet: Arc<Mutex<Wallet>>,
|
||||
consignment_proxy: Arc<proxy::ConsignmentClient>,
|
||||
storage: Box<dyn store::RGBStorage>,
|
||||
wallet: Arc<Wallet>,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RGBManager {
|
||||
|
@ -25,71 +35,45 @@ impl std::fmt::Debug for RGBManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_coin_type(bitcoin_network: BitcoinNetwork) -> u32 {
|
||||
u32::from(bitcoin_network != BitcoinNetwork::Mainnet)
|
||||
}
|
||||
|
||||
fn derive_account_xprv_from_mnemonic(
|
||||
bitcoin_network: BitcoinNetwork,
|
||||
master_xprv: &ExtendedPrivKey,
|
||||
) -> anyhow::Result<ExtendedPrivKey> {
|
||||
const PURPOSE: u8 = 84;
|
||||
const ACCOUNT: u8 = 0;
|
||||
|
||||
let coin_type = get_coin_type(bitcoin_network);
|
||||
let account_derivation_path = vec![
|
||||
ChildNumber::from_hardened_idx(PURPOSE as u32).unwrap(),
|
||||
ChildNumber::from_hardened_idx(coin_type).unwrap(),
|
||||
ChildNumber::from_hardened_idx(ACCOUNT as u32).unwrap(),
|
||||
];
|
||||
Ok(master_xprv.derive_priv(&Secp256k1::new(), &account_derivation_path)?)
|
||||
}
|
||||
|
||||
impl RGBManager {
|
||||
pub fn init(
|
||||
root_dir: &str,
|
||||
master_xprv: &ExtendedPrivKey,
|
||||
network: &str,
|
||||
) -> anyhow::Result<Self> {
|
||||
let client = proxy::Client::new(network)?;
|
||||
|
||||
let storage = Box::new(store::InMemoryStorage::new()?);
|
||||
let client = proxy::ConsignmentClient::new(network)?;
|
||||
let bitcoin_network = BitcoinNetwork::from_str(network)?;
|
||||
// with rgb library tere is a new function for calculate the account key
|
||||
let account_privkey = derive_account_xprv_from_mnemonic(bitcoin_network, master_xprv)?;
|
||||
let account_xpub = ExtendedPubKey::from_priv(&Secp256k1::new(), &account_privkey);
|
||||
let mut wallet = Wallet::new(WalletData {
|
||||
data_dir: root_dir.to_owned(),
|
||||
bitcoin_network,
|
||||
database_type: DatabaseType::Sqlite,
|
||||
max_allocations_per_utxo: 11,
|
||||
pubkey: account_xpub.to_string().to_owned(),
|
||||
mnemonic: None,
|
||||
vanilla_keychain: None,
|
||||
})?;
|
||||
let network = Network::from_str(network)?;
|
||||
let url = match network {
|
||||
Network::Bitcoin => "https://mempool.space/api",
|
||||
Network::Testnet => "https://mempool.space/testnet/api",
|
||||
Network::Signet => "https://mempool.space/signet/api",
|
||||
Network::Regtest => "",
|
||||
_ => anyhow::bail!("Network `{network}` not supported"),
|
||||
};
|
||||
if !url.is_empty() {
|
||||
let _ = wallet.go_online(false, url.to_owned())?;
|
||||
}
|
||||
let wallet = Wallet::new(&bitcoin_network, *master_xprv, root_dir)?;
|
||||
// FIXME: setting up the correct proxy client URL
|
||||
Ok(Self {
|
||||
proxy_client: Arc::new(client),
|
||||
wallet: Arc::new(Mutex::new(wallet)),
|
||||
consignment_proxy: Arc::new(client),
|
||||
wallet: Arc::new(wallet),
|
||||
path: root_dir.to_owned(),
|
||||
storage,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wallet(&self) -> Arc<Mutex<Wallet>> {
|
||||
pub fn wallet(&self) -> Arc<Wallet> {
|
||||
self.wallet.clone()
|
||||
}
|
||||
|
||||
pub fn proxy_client(&self) -> Arc<proxy::Client> {
|
||||
self.proxy_client.clone()
|
||||
pub fn consignment_proxy(&self) -> Arc<proxy::ConsignmentClient> {
|
||||
self.consignment_proxy.clone()
|
||||
}
|
||||
|
||||
pub fn assert_balance(&self, asset_id: String) -> anyhow::Result<Balance> {
|
||||
let balance = self
|
||||
.wallet
|
||||
.wallet
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_asset_balance(asset_id)?;
|
||||
Ok(balance)
|
||||
}
|
||||
|
||||
pub fn add_rgb_info(&self, info: &RgbInfo, pending: bool) -> anyhow::Result<()> {
|
||||
self.storage.write_rgb_info(&info.channel_id, pending, info)
|
||||
}
|
||||
|
||||
/// Modify the funding transaction before sign it with the node signer.
|
||||
|
@ -97,8 +81,69 @@ impl RGBManager {
|
|||
&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)?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Missing parameters: amount_sat of the funding tx and
|
||||
// the script one
|
||||
//
|
||||
// Maybe it is possible extract from the tx if we know the information from
|
||||
// the tx, but not sure if this is a good usage of cln hooks?
|
||||
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?
|
||||
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
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
//! RGB Storage interface
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
use crate::types::RgbInfo;
|
||||
|
||||
/// A common interface for an RGB Storage
|
||||
pub trait RGBStorage {
|
||||
fn new() -> anyhow::Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn get_rgb_channel_info(&self, channel_id: &str) -> anyhow::Result<RgbInfo>;
|
||||
|
||||
fn get_rgb_channel_info_pending(&self, channel_id: &str) -> anyhow::Result<RgbInfo>;
|
||||
|
||||
fn is_channel_rgb(&self, channel_id: &str, is_pending: bool) -> anyhow::Result<bool>;
|
||||
|
||||
fn write_rgb_info(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
is_pending: bool,
|
||||
info: &RgbInfo,
|
||||
) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub struct InMemoryStorage {
|
||||
inner: RefCell<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl InMemoryStorage {
|
||||
fn derive_channel_db_key(&self, channel_id: &str, is_pending: bool) -> anyhow::Result<String> {
|
||||
return if is_pending {
|
||||
Ok(format!("rgb/pending/channel/{channel_id}"))
|
||||
} else {
|
||||
Ok(format!("rgb/channel/{channel_id}"))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl RGBStorage for InMemoryStorage {
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: RefCell::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_rgb_channel_info(&self, channel_id: &str) -> anyhow::Result<RgbInfo> {
|
||||
let key = self.derive_channel_db_key(channel_id, false)?;
|
||||
let map = self.inner.borrow();
|
||||
let value = map
|
||||
.get(&key)
|
||||
.ok_or(anyhow::anyhow!("rgb channel with key `{key}` is not found"))?;
|
||||
let info: RgbInfo = serde_json::from_str(&value)?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn get_rgb_channel_info_pending(&self, channel_id: &str) -> anyhow::Result<RgbInfo> {
|
||||
let key = self.derive_channel_db_key(channel_id, true)?;
|
||||
let map = self.inner.borrow();
|
||||
let value = map
|
||||
.get(&key)
|
||||
.ok_or(anyhow::anyhow!("rgb channel with key `{key}` is not found"))?;
|
||||
let info: RgbInfo = serde_json::from_str(&value)?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn is_channel_rgb(&self, channel_id: &str, is_pending: bool) -> anyhow::Result<bool> {
|
||||
let key = self.derive_channel_db_key(channel_id, is_pending)?;
|
||||
let map = self.inner.borrow();
|
||||
Ok(map.contains_key(&key))
|
||||
}
|
||||
|
||||
fn write_rgb_info(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
is_pending: bool,
|
||||
info: &RgbInfo,
|
||||
) -> anyhow::Result<()> {
|
||||
let key = self.derive_channel_db_key(channel_id, is_pending)?;
|
||||
// FIXME: we need a lock before production
|
||||
let mut map = self.inner.borrow_mut();
|
||||
map.insert(key, serde_json::to_string(info)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
//! RGB types
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use commit_verify::mpc::MerkleBlock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::bitcoin::Txid;
|
||||
use crate::core::{Anchor, TransitionBundle};
|
||||
use crate::std::contract::ContractId;
|
||||
|
||||
/// RGB channel info
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RgbInfo {
|
||||
/// Channel_id
|
||||
pub channel_id: String,
|
||||
/// 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 override_route_amount: bool,
|
||||
}
|
||||
|
||||
/// RGB transfer info
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TransferInfo {
|
||||
/// Transfer anchor
|
||||
pub anchor: Anchor<MerkleBlock>,
|
||||
/// Transfer bundles
|
||||
pub bundles: BTreeMap<ContractId, TransitionBundle>,
|
||||
/// Transfer contract ID
|
||||
pub contract_id: ContractId,
|
||||
/// Transfer RGB amount
|
||||
pub rgb_amount: u64,
|
||||
}
|
||||
|
||||
/// A reference to a transaction output.
|
||||
///
|
||||
/// Differs from bitcoin::blockdata::transaction::OutPoint as the index is a u16 instead of u32
|
||||
/// due to LN's restrictions on index values. Should reduce (possibly) unsafe conversions this way.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
pub struct OutPoint {
|
||||
/// The referenced transaction's txid.
|
||||
pub txid: Txid,
|
||||
/// The index of the referenced output in its transaction's vout.
|
||||
pub index: u16,
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
//! RGB Wallet mock
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::bitcoin::bip32::ExtendedPrivKey;
|
||||
use crate::bitcoin::bip32::ExtendedPubKey;
|
||||
use crate::bitcoin::psbt::PartiallySignedTransaction;
|
||||
use crate::bitcoin::secp256k1::Secp256k1;
|
||||
use crate::bitcoin::Network;
|
||||
use crate::lib::wallet::{DatabaseType, Online, Wallet as RgbWallet, WalletData};
|
||||
use crate::lib::BitcoinNetwork;
|
||||
use crate::types::RgbInfo;
|
||||
|
||||
pub struct Wallet {
|
||||
wallet: Arc<Mutex<RgbWallet>>,
|
||||
online_wallet: Option<Online>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn new(
|
||||
network: &BitcoinNetwork,
|
||||
xprv: ExtendedPrivKey,
|
||||
path: &str,
|
||||
) -> anyhow::Result<Self> {
|
||||
// with rgb library tere is a new function for calculate the account key
|
||||
let account_privkey = Self::derive_account_xprv_from_mnemonic(network.clone(), xprv)?;
|
||||
let account_xpub = ExtendedPubKey::from_priv(&Secp256k1::new(), &account_privkey);
|
||||
let mut wallet = RgbWallet::new(WalletData {
|
||||
data_dir: path.to_owned(),
|
||||
bitcoin_network: network.clone(),
|
||||
database_type: DatabaseType::Sqlite,
|
||||
max_allocations_per_utxo: 11,
|
||||
pubkey: account_xpub.to_string().to_owned(),
|
||||
mnemonic: None,
|
||||
vanilla_keychain: None,
|
||||
})?;
|
||||
let network = Network::from_str(network)?;
|
||||
let url = match network {
|
||||
Network::Bitcoin => "https://mempool.space/api",
|
||||
Network::Testnet => "https://mempool.space/testnet/api",
|
||||
Network::Signet => "https://mempool.space/signet/api",
|
||||
Network::Regtest => "",
|
||||
_ => anyhow::bail!("Network `{network}` not supported"),
|
||||
};
|
||||
let mut online_info = None;
|
||||
if !url.is_empty() {
|
||||
online_info = Some(wallet.go_online(false, url.to_owned())?);
|
||||
}
|
||||
Ok(Self {
|
||||
wallet: Arc::new(Mutex::new(wallet)),
|
||||
online_wallet: online_info,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_coin_type(bitcoin_network: BitcoinNetwork) -> u32 {
|
||||
u32::from(bitcoin_network != BitcoinNetwork::Mainnet)
|
||||
}
|
||||
|
||||
fn derive_account_xprv_from_mnemonic(
|
||||
bitcoin_network: BitcoinNetwork,
|
||||
master_xprv: &ExtendedPrivKey,
|
||||
) -> anyhow::Result<ExtendedPrivKey> {
|
||||
const PURPOSE: u8 = 84;
|
||||
const ACCOUNT: u8 = 0;
|
||||
|
||||
let coin_type = Self::get_coin_type(bitcoin_network);
|
||||
let account_derivation_path = vec![
|
||||
ChildNumber::from_hardened_idx(PURPOSE as u32).unwrap(),
|
||||
ChildNumber::from_hardened_idx(coin_type).unwrap(),
|
||||
ChildNumber::from_hardened_idx(ACCOUNT as u32).unwrap(),
|
||||
];
|
||||
Ok(master_xprv.derive_priv(&Secp256k1::new(), &account_derivation_path)?)
|
||||
}
|
||||
|
||||
/// Given A PSBT we add the rgb information into it
|
||||
pub fn add_rgb_ouput(
|
||||
&self,
|
||||
psbt: &mut PartiallySignedTransaction,
|
||||
commitment_info: &RgbInfo,
|
||||
) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue