Merge pull request #3 from vincenzopalazzo/macros/accountkey-derivation

common: handle funding transaction
This commit is contained in:
Vincenzo Palazzo 2024-03-16 14:27:08 +01:00 committed by GitHub
commit 7e5d7588a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 730 additions and 67 deletions

View File

@ -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)? }))
}

View File

@ -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,
}))
}

View File

@ -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"

View File

@ -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(())
}
}

View File

@ -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;

View File

@ -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(&params)?)
.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

View File

@ -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(())
}
}

View File

@ -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(())
}
}

62
rgb-common/src/types.rs Normal file
View File

@ -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,
}

82
rgb-common/src/wallet.rs Normal file
View File

@ -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!()
}
}