Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lightning Network Channels #1133

Merged
merged 42 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e7dc46c
WIP restarting node/read channel_manager from file
shamardy Oct 19, 2021
9e5ab02
WIP: LN Registry for register_tx & register_output
shamardy Oct 21, 2021
bdcd988
update transactions confirmations on node restart
shamardy Oct 23, 2021
e13f593
reconnect to channel peers on node restart
shamardy Oct 25, 2021
3ea3aaf
connect to LN node RPC + WIP lightning ctx
shamardy Oct 27, 2021
846ecee
continued lightning ctx impl for multiple coins
shamardy Oct 28, 2021
cef620d
open_channel RPC
shamardy Oct 28, 2021
36691ce
withdraw to p2wsh addresses + funding tx WIP
shamardy Oct 30, 2021
7d6d9dd
fix zhtlc build
shamardy Oct 30, 2021
4cf3cd9
extract_destinations for p2wsh
shamardy Nov 1, 2021
a0b0751
generate/broadcast funding tx (channel opening tx)
shamardy Nov 3, 2021
9202cf3
update rust-lightning to v0.0.102
shamardy Nov 4, 2021
fce783b
update rust-lightning to v0.0.103
shamardy Nov 4, 2021
dcd8843
fix reconnection to open channels nodes on restart
shamardy Nov 4, 2021
135db9c
save nodes to file fix + order txs to confirm
shamardy Nov 5, 2021
424947a
add fee param to open_channel RPC + refactors
shamardy Nov 9, 2021
fde1c1c
use scripthash_get_history to get tx block height
shamardy Nov 11, 2021
e8d92b8
remove unneeded comments
shamardy Nov 11, 2021
9d621c2
Merge remote-tracking branch 'origin/dev' into ln-channels
shamardy Nov 11, 2021
f1fd177
WIP:Review fixes, ChannelOpenAmount, UtxoTxBuilder
shamardy Nov 15, 2021
5137f90
WIP: Review fixes, use get_tx_fee for funding tx
shamardy Nov 16, 2021
05ad87d
remove WithdrawFee, fix unit test
shamardy Nov 16, 2021
ab911f0
WIP: review fixes, use find_output_spend fn
shamardy Nov 17, 2021
b0a2d50
WIP: review fixes, separate dirs for tickers
shamardy Nov 18, 2021
c0caf10
WIP: Review fixes, request_id fix on restart
shamardy Nov 18, 2021
80fa54f
WIP: review fixes, LightningCoin
shamardy Nov 20, 2021
5d8a1ad
final fixes: move ln_registry, fix fee with max
shamardy Nov 22, 2021
93ef490
Merge remote-tracking branch 'origin/dev' into ln-channels
shamardy Nov 22, 2021
27600e3
second review fixes
shamardy Nov 23, 2021
18a9a4b
WIP: review fixes, OpenChannelRequest fix, others
shamardy Nov 24, 2021
3694e19
Review fixes: enable_l2, enable_lightning
shamardy Nov 25, 2021
8ae7ecd
remove lightning from lp_coininit
shamardy Nov 26, 2021
31aa280
Merge remote-tracking branch 'origin/dev' into ln-channels
shamardy Nov 26, 2021
7ff4b28
use virtual bytes to calculate tx fees
shamardy Nov 29, 2021
3429e11
WIP: more review fixes
shamardy Nov 30, 2021
c35da25
WIP: review fixes, remove block_on
shamardy Dec 1, 2021
e8948a4
WIP: review fixes, impl EventHandler trait
shamardy Dec 1, 2021
adc12a8
WIP: add validation methods to L2ActivationOps
shamardy Dec 1, 2021
794d4b1
Review fixes, add unsigned_funding_txs mutex
shamardy Dec 2, 2021
1c8f6ae
review fixes: NO_SUCH_TRANSACTION_ERROR
shamardy Dec 6, 2021
6ab6a97
use 'code': -5 to check for tx unconfirmation
shamardy Dec 8, 2021
4996e6f
add get_merkle method, use tx index to confirm it
shamardy Dec 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions mm2src/coins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jsonrpc-core = "8.0.1"
keys = { path = "../mm2_bitcoin/keys" }
lazy_static = "1.4"
libc = "0.2"
lightning = "0.0.101"
lightning = "0.0.103"
metrics = "0.12"
mocktopus = "0.7.0"
num-traits = "0.2"
Expand Down Expand Up @@ -78,9 +78,9 @@ web-sys = { version = "0.3.4", features = ["console", "Headers", "Request", "Req

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dirs = { version = "1" }
lightning-background-processor = "0.0.101"
lightning-persister = "0.0.101"
lightning-net-tokio = "0.0.101"
lightning-background-processor = "0.0.103"
lightning-persister = "0.0.103"
lightning-net-tokio = "0.0.103"
rusqlite = { version = "0.24.2", features = ["bundled"], optional = true }
rust-ini = { version = "0.13" }
rustls = { version = "0.19", features = ["dangerous_configuration"] }
Expand Down
238 changes: 235 additions & 3 deletions mm2src/coins/lightning.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
#[cfg(not(target_arch = "wasm32"))]
use crate::utxo::rpc_clients::UtxoRpcClientEnum;
#[cfg(not(target_arch = "wasm32"))]
use crate::utxo::{sat_from_big_decimal, FeePolicy, UtxoCommonOps, UTXO_LOCK};
#[cfg(not(target_arch = "wasm32"))] use crate::MarketCoinOps;
use bigdecimal::BigDecimal;
#[cfg(not(target_arch = "wasm32"))]
use common::ip_addr::myipaddr;
use common::mm_ctx::MmArc;
use common::mm_error::prelude::*;
use ln_errors::{EnableLightningError, EnableLightningResult};
#[cfg(not(target_arch = "wasm32"))]
use ln_utils::{network_from_string, start_lightning, LightningConf};
use futures::compat::Future01CompatExt;
use ln_errors::{ConnectToNodeError, ConnectToNodeResult, EnableLightningError, EnableLightningResult,
OpenChannelError, OpenChannelResult};
#[cfg(not(target_arch = "wasm32"))]
use ln_utils::{connect_to_node, network_from_string, nodes_data_path, open_ln_channel, parse_node_info,
read_nodes_data_from_file, save_node_data_to_file, start_lightning, LightningConf, LightningContext};
use std::sync::atomic::AtomicU64;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::atomic::Ordering;
use std::sync::Arc;

#[cfg(not(target_arch = "wasm32"))]
use super::{lp_coinfind_or_err, MmCoinEnum};
Expand All @@ -15,6 +27,10 @@ mod ln_errors;
mod ln_rpc;
#[cfg(not(target_arch = "wasm32"))] mod ln_utils;

lazy_static! {
static ref REQUEST_IDX: Arc<AtomicU64> = Arc::new(AtomicU64::new(0));
artemii235 marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Deserialize)]
pub struct EnableLightningRequest {
pub coin: String,
Expand Down Expand Up @@ -54,6 +70,18 @@ pub async fn enable_lightning(ctx: MmArc, req: EnableLightningRequest) -> Enable
));
}

// Channel funding transactions need to spend segwit outputs
// and while the witness script can be generated from pubkey and be used
// it's better for the coin to be enabled in segwit to check if balance is enough for funding transaction, etc...
// TODO: when merging with the "mm2.1-orderbook-ticker-btc-segwit" PR, we should have a different coin called BTC-Lightning
// with same ticker as BTC and to open a channel the BTC-Segwit wallet should be used to fund the transaction
if !utxo_coin.as_ref().my_address.addr_format.is_segwit() {
return MmError::err(EnableLightningError::UnsupportedMode(
"Lightning network".into(),
"segwit".into(),
));
}

let client = match &utxo_coin.as_ref().rpc_client {
UtxoRpcClientEnum::Electrum(c) => c,
UtxoRpcClientEnum::Native(_) => {
Expand Down Expand Up @@ -94,7 +122,211 @@ pub async fn enable_lightning(ctx: MmArc, req: EnableLightningRequest) -> Enable
let port = req.port.unwrap_or(9735);

let conf = LightningConf::new(client.clone(), network, listen_addr, port, node_name, node_color);
start_lightning(&ctx, conf).await?;
start_lightning(ctx, utxo_coin, conf).await?;

Ok("success".into())
}

#[derive(Deserialize)]
pub struct ConnectToNodeRequest {
pub coin: String,
pub node_id: String,
}

#[cfg(target_arch = "wasm32")]
pub async fn connect_to_lightning_node(_ctx: MmArc, _req: ConnectToNodeRequest) -> ConnectToNodeResult<String> {
MmError::err(ConnectToNodeError::UnsupportedMode(
"'connect_to_lightning_node'".into(),
"native".into(),
))
}

/// Connect to a certain node on the lightning network.
#[cfg(not(target_arch = "wasm32"))]
pub async fn connect_to_lightning_node(ctx: MmArc, req: ConnectToNodeRequest) -> ConnectToNodeResult<String> {
let lightning_ctx = LightningContext::from_ctx(&ctx).unwrap();

{
let background_processor = lightning_ctx.background_processors.lock().await;
if !background_processor.contains_key(&req.coin) {
return MmError::err(ConnectToNodeError::LightningNotEnabled(req.coin));
}
}

let (node_pubkey, node_addr) = parse_node_info(req.node_id.clone())?;

let peer_managers = lightning_ctx.peer_managers.lock().await;
let peer_manager = peer_managers
.get(&req.coin)
.ok_or(ConnectToNodeError::LightningNotEnabled(req.coin))?;
let res = connect_to_node(node_pubkey, node_addr, peer_manager.clone()).await?;

Ok(res.to_string())
}

mod named_unit_variant {
named_unit_variant!(max);
}

#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(untagged)]
artemii235 marked this conversation as resolved.
Show resolved Hide resolved
pub enum ChannelOpenAmount {
Exact(BigDecimal),
#[serde(with = "named_unit_variant::max")]
Max,
}

fn get_true() -> bool { true }

#[allow(dead_code)]
#[derive(Debug, Deserialize, PartialEq)]
pub struct OpenChannelRequest {
pub coin: String,
pub node_id: String,
pub amount: ChannelOpenAmount,
#[serde(default = "get_true")]
pub announce_channel: bool,
}

#[cfg(target_arch = "wasm32")]
pub async fn open_channel(_ctx: MmArc, _req: OpenChannelRequest) -> OpenChannelResult<String> {
MmError::err(OpenChannelError::UnsupportedMode(
"'open_channel'".into(),
"native".into(),
))
}

/// Opens a channel on the lightning network.
#[cfg(not(target_arch = "wasm32"))]
pub async fn open_channel(ctx: MmArc, req: OpenChannelRequest) -> OpenChannelResult<String> {
let coin = lp_coinfind_or_err(&ctx, &req.coin).await?;

let utxo_coin = match coin {
MmCoinEnum::UtxoCoin(utxo) => utxo,
_ => {
return MmError::err(OpenChannelError::UnsupportedCoin(
req.coin,
"Only utxo coins are supported in lightning".into(),
))
},
};

let lightning_ctx = LightningContext::from_ctx(&ctx).unwrap();

{
let background_processor = lightning_ctx.background_processors.lock().await;
if !background_processor.contains_key(&req.coin) {
return MmError::err(OpenChannelError::LightningNotEnabled(req.coin));
}
}

let decimals = utxo_coin.as_ref().decimals;
let (amount, fee_policy) = match req.amount.clone() {
ChannelOpenAmount::Exact(value) => {
let balance = utxo_coin.my_spendable_balance().compat().await?;
if balance < value {
return MmError::err(OpenChannelError::BalanceError(format!(
"Not enough balance to open channel, Current balance: {}",
balance
)));
}
let amount = sat_from_big_decimal(&value, decimals)?;
(amount, FeePolicy::SendExact)
},
ChannelOpenAmount::Max => {
let _utxo_lock = UTXO_LOCK.lock().await;
let (unspents, _) = utxo_coin
.ordered_mature_unspents(&utxo_coin.as_ref().my_address)
.await?;
(
unspents.iter().fold(0, |sum, unspent| sum + unspent.value),
FeePolicy::DeductFromOutput(0),
)
},
};

let (node_pubkey, node_addr) = parse_node_info(req.node_id.clone())?;

{
let peer_managers = lightning_ctx.peer_managers.lock().await;
let peer_manager = peer_managers
.get(&req.coin)
.ok_or_else(|| ConnectToNodeError::LightningNotEnabled(req.coin.clone()))?;
connect_to_node(node_pubkey, node_addr, peer_manager.clone()).await?;
}

let nodes_data = read_nodes_data_from_file(&nodes_data_path(&ctx))?;
if !nodes_data.contains_key(&node_pubkey) {
save_node_data_to_file(&nodes_data_path(&ctx), &req.node_id)?;
}

// Helps in tracking which FundingGenerationReady events corresponds to which open_channel call
let request_id = REQUEST_IDX.fetch_add(1, Ordering::Relaxed);

{
let mut funding_tx_params = lightning_ctx.funding_tx_params.lock().await;
funding_tx_params.insert(request_id, fee_policy);
}

let temporary_channel_id = {
let channel_managers = lightning_ctx.channel_managers.lock().await;
let channel_manager = channel_managers
.get(&req.coin)
.ok_or(ConnectToNodeError::LightningNotEnabled(req.coin))?;
open_ln_channel(
node_pubkey,
amount,
request_id,
req.announce_channel,
channel_manager.clone(),
)?
};

Ok(format!(
"Initiated opening channel with temporary ID: {:?} with node: {} and request id: {}",
sergeyboyko0791 marked this conversation as resolved.
Show resolved Hide resolved
temporary_channel_id, req.node_id, request_id
))
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::{self as json};

#[test]
fn test_deserialize_open_channel_request() {
let req = json!({
"coin":"test",
"node_id": "pubkey@ip",
"amount": 0.1,
});

let expected = OpenChannelRequest {
coin: "test".into(),
node_id: "pubkey@ip".into(),
amount: ChannelOpenAmount::Exact(0.1.into()),
announce_channel: true,
};

let actual: OpenChannelRequest = json::from_value(req).unwrap();

assert_eq!(expected, actual);

let req = json!({
"coin":"test",
"node_id": "pubkey@ip",
"amount": "max",
});

let expected = OpenChannelRequest {
coin: "test".into(),
node_id: "pubkey@ip".into(),
amount: ChannelOpenAmount::Max,
announce_channel: true,
};

let actual: OpenChannelRequest = json::from_value(req).unwrap();

assert_eq!(expected, actual);
}
}
Loading