lsp_plugin: add client side check for zero_conf
We only allow zero_conf channels if we approved the a jit-channel from the LSP in advance. Signed-off-by: Peter Neuroth <pet.v.ne@gmail.com>
This commit is contained in:
committed by
Rusty Russell
parent
ffc0e42f7d
commit
e789b969ec
@@ -17,7 +17,9 @@ use cln_lsps::lsps2::model::{
|
||||
use cln_lsps::util;
|
||||
use cln_lsps::LSP_FEATURE_BIT;
|
||||
use cln_plugin::options;
|
||||
use cln_rpc::model::requests::{DatastoreMode, DatastoreRequest, ListpeersRequest};
|
||||
use cln_rpc::model::requests::{
|
||||
DatastoreMode, DatastoreRequest, DeldatastoreRequest, ListdatastoreRequest, ListpeersRequest,
|
||||
};
|
||||
use cln_rpc::model::responses::InvoiceResponse;
|
||||
use cln_rpc::primitives::{AmountOrAny, PublicKey, ShortChannelId};
|
||||
use cln_rpc::ClnRpc;
|
||||
@@ -238,7 +240,6 @@ async fn on_lsps_lsps2_approve(
|
||||
) -> Result<serde_json::Value, anyhow::Error> {
|
||||
let req: ClnRpcLsps2Approve = serde_json::from_value(v)?;
|
||||
let ds_rec = DatastoreRecord {
|
||||
lsp_id: req.lsp_id,
|
||||
jit_channel_scid: req.jit_channel_scid,
|
||||
client_trusts_lsp: req.client_trusts_lsp.unwrap_or_default(),
|
||||
};
|
||||
@@ -253,11 +254,7 @@ async fn on_lsps_lsps2_approve(
|
||||
hex: None,
|
||||
mode: Some(DatastoreMode::CREATE_OR_REPLACE),
|
||||
string: Some(ds_rec_json),
|
||||
key: vec![
|
||||
"lsps".to_string(),
|
||||
"client".to_string(),
|
||||
req.jit_channel_scid.to_string(),
|
||||
],
|
||||
key: vec!["lsps".to_string(), "client".to_string(), req.lsp_id],
|
||||
};
|
||||
let _ds_res = cln_client.call_typed(&ds_req).await?;
|
||||
Ok(serde_json::Value::default())
|
||||
@@ -494,19 +491,52 @@ async fn on_htlc_accepted(
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Allows `zero_conf` channels to the client if the LSP is on the allowlist.
|
||||
async fn on_openchannel(
|
||||
_p: cln_plugin::Plugin<State>,
|
||||
_v: serde_json::Value,
|
||||
p: cln_plugin::Plugin<State>,
|
||||
v: serde_json::Value,
|
||||
) -> Result<serde_json::Value, anyhow::Error> {
|
||||
// Fixme: Register a list of trusted LSPs and check if LSP is allowlisted.
|
||||
// And if we expect a channel to be opened.
|
||||
// - either datastore or invoice label possible.
|
||||
info!("Allowing zero-conf channel from LSP");
|
||||
Ok(serde_json::json!({
|
||||
"result": "continue",
|
||||
"reserve": "0msat",
|
||||
"mindepth": 0,
|
||||
}))
|
||||
#[derive(Deserialize)]
|
||||
struct Request {
|
||||
id: String,
|
||||
}
|
||||
|
||||
let req: Request = serde_json::from_value(v.get("openchannel").unwrap().clone())
|
||||
.context("Failed to parse request JSON")?;
|
||||
let dir = p.configuration().lightning_dir;
|
||||
let rpc_path = Path::new(&dir).join(&p.configuration().rpc_file);
|
||||
let mut cln_client = cln_rpc::ClnRpc::new(rpc_path.clone()).await?;
|
||||
|
||||
let ds_req = ListdatastoreRequest {
|
||||
key: Some(vec![
|
||||
"lsps".to_string(),
|
||||
"client".to_string(),
|
||||
req.id.clone(),
|
||||
]),
|
||||
};
|
||||
let ds_res = cln_client.call_typed(&ds_req).await?;
|
||||
if let Some(_rec) = ds_res.datastore.iter().next() {
|
||||
info!("Allowing zero-conf channel from LSP {}", &req.id);
|
||||
let ds_req = DeldatastoreRequest {
|
||||
generation: None,
|
||||
key: vec!["lsps".to_string(), "client".to_string(), req.id.clone()],
|
||||
};
|
||||
if let Some(err) = cln_client.call_typed(&ds_req).await.err() {
|
||||
// We can do nothing but report that there was an issue deleting the
|
||||
// datastore record.
|
||||
warn!("Failed to delete LSP record from datastore: {}", err);
|
||||
}
|
||||
// Fixme: Check that we actually use client-trusts-LSP mode - can be
|
||||
// found in the ds record.
|
||||
return Ok(serde_json::json!({
|
||||
"result": "continue",
|
||||
"reserve": "0msat",
|
||||
"mindepth": 0,
|
||||
}));
|
||||
} else {
|
||||
// Not a requested JIT-channel opening, continue.
|
||||
Ok(serde_json::json!({"result": "continue"}))
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_lsps_listprotocols(
|
||||
@@ -659,7 +689,6 @@ struct ClnRpcLsps2Approve {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct DatastoreRecord {
|
||||
lsp_id: String,
|
||||
jit_channel_scid: ShortChannelId,
|
||||
client_trusts_lsp: bool,
|
||||
}
|
||||
|
||||
@@ -507,8 +507,7 @@ impl<A: ClnApi> HtlcAcceptedHookHandler<A> {
|
||||
push_msat: None,
|
||||
request_amt: None,
|
||||
reserve: None,
|
||||
channel_type: None, // Fimxe: Core-Lightning is complaining that it doesn't support these channel_types
|
||||
// channel_type: Some(vec![46, 50]), // Sets `option_zeroconf` and `option_scid_alias`
|
||||
channel_type: Some(vec![12, 22, 50]),
|
||||
utxos: None,
|
||||
amount: AmountOrAll::Amount(Amount::from_msat(cap)),
|
||||
id: ds_rec.peer_id,
|
||||
|
||||
@@ -161,3 +161,59 @@ def test_lsps2_buyjitchannel_no_mpp_var_invoice(node_factory, bitcoind):
|
||||
# l1 should have gotten a jit-channel.
|
||||
chs = l1.rpc.listpeerchannels()['channels']
|
||||
assert len(chs) == 1
|
||||
|
||||
|
||||
def test_lsps2_non_approved_zero_conf(node_factory, bitcoind):
|
||||
""" Checks that we don't allow zerof_conf channels from an LSP if we did
|
||||
not approve it first.
|
||||
"""
|
||||
# We need a policy service to fetch from.
|
||||
plugin = os.path.join(os.path.dirname(__file__), 'plugins/lsps2_policy.py')
|
||||
|
||||
l1, l2, l3= node_factory.get_nodes(3, opts=[
|
||||
{"dev-lsps-client-enabled": None},
|
||||
{
|
||||
"dev-lsps-service-enabled": None,
|
||||
"dev-lsps2-service-enabled": None,
|
||||
"dev-lsps2-promise-secret": "00" * 32,
|
||||
"plugin": plugin,
|
||||
"fee-base": 0, # We are going to deduct our fee anyways,
|
||||
"fee-per-satoshi": 0, # We are going to deduct our fee anyways,
|
||||
},
|
||||
{"disable-mpp": None},
|
||||
])
|
||||
|
||||
# Give the LSP some funds to open jit-channels
|
||||
addr = l2.rpc.newaddr()['bech32']
|
||||
bitcoind.rpc.sendtoaddress(addr, 1)
|
||||
bitcoind.generate_block(1)
|
||||
|
||||
node_factory.join_nodes([l3, l2], fundchannel=True, wait_for_announce=True)
|
||||
node_factory.join_nodes([l1, l2], fundchannel=False)
|
||||
|
||||
chanid = only_one(l3.rpc.listpeerchannels(l2.info['id'])['channels'])['short_channel_id']
|
||||
|
||||
fee_opt = l1.rpc.lsps_lsps2_getinfo(lsp_id=l2.info['id'])['opening_fee_params_menu'][0]
|
||||
buy_res = l1.rpc.lsps_lsps2_buy(lsp_id=l2.info['id'], opening_fee_params=fee_opt)
|
||||
|
||||
hint = [[{
|
||||
"id": l2.info['id'],
|
||||
"short_channel_id": buy_res['jit_channel_scid'],
|
||||
"fee_base_msat": 0,
|
||||
"fee_proportional_millionths": 0,
|
||||
"cltv_expiry_delta": buy_res['lsp_cltv_expiry_delta'],
|
||||
}]]
|
||||
|
||||
bolt11 = l1.dev_invoice(
|
||||
amount_msat="any",
|
||||
description="lsp-invoice-1",
|
||||
label="lsp-invoice-1",
|
||||
dev_routes=hint,
|
||||
)['bolt11']
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
l3.rpc.pay(bolt11, amount_msat=10000000)
|
||||
|
||||
# l1 shouldn't have a new channel.
|
||||
chs = l1.rpc.listpeerchannels()['channels']
|
||||
assert len(chs) == 0
|
||||
|
||||
Reference in New Issue
Block a user