lsp_plugin: add htlc_accepted handler for no-mpp

Adds the service side (LSP) for a simple no-mpp trusted jit channel
opening. This is only an intermediate step, we are going to add support
for multiple htlcs.
This is experimental and can drain on-chain fees from the LSP if used in
public.

Signed-off-by: Peter Neuroth <pet.v.ne@gmail.com>
This commit is contained in:
Peter Neuroth
2025-09-22 15:43:16 +02:00
committed by Rusty Russell
parent ecb1f4c7e7
commit 7f3789b624
6 changed files with 1761 additions and 13 deletions

View File

@@ -0,0 +1,727 @@
//! Backfill structs for missing or incomplete Core Lightning types.
//!
//! This module provides struct implementations that are not available or
//! fully accessible in the core-lightning crate, enabling better compatibility
//! and interoperability with Core Lightning's RPC interface.
use cln_rpc::primitives::{Amount, ShortChannelId};
use hex::FromHex;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::lsps2::cln::tlv::TlvStream;
pub const TLV_FORWARD_AMT: u64 = 2;
pub const TLV_OUTGOING_CLTV: u64 = 4;
pub const TLV_SHORT_CHANNEL_ID: u64 = 6;
pub const TLV_PAYMENT_SECRET: u64 = 8;
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Onion {
pub forward_msat: Option<Amount>,
#[serde(deserialize_with = "from_hex")]
pub next_onion: Vec<u8>,
pub outgoing_cltv_value: Option<u32>,
pub payload: TlvStream,
// pub payload: TlvStream,
#[serde(deserialize_with = "from_hex")]
pub shared_secret: Vec<u8>,
pub short_channel_id: Option<ShortChannelId>,
pub total_msat: Option<Amount>,
#[serde(rename = "type")]
pub type_: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct Htlc {
pub amount_msat: Amount,
pub cltv_expiry: u32,
pub cltv_expiry_relative: u16,
pub id: u64,
#[serde(deserialize_with = "from_hex")]
pub payment_hash: Vec<u8>,
pub short_channel_id: ShortChannelId,
pub extra_tlvs: Option<TlvStream>,
}
#[derive(Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HtlcAcceptedResult {
Continue,
Fail,
Resolve,
}
impl std::fmt::Display for HtlcAcceptedResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
HtlcAcceptedResult::Continue => "continue",
HtlcAcceptedResult::Fail => "fail",
HtlcAcceptedResult::Resolve => "resolve",
};
write!(f, "{s}")
}
}
#[derive(Debug, Deserialize)]
pub struct HtlcAcceptedRequest {
pub htlc: Htlc,
pub onion: Onion,
pub forward_to: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct HtlcAcceptedResponse {
pub result: HtlcAcceptedResult,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", serialize_with = "to_hex")]
pub payload: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none", serialize_with = "to_hex")]
pub forward_to: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none", serialize_with = "to_hex")]
pub extra_tlvs: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", serialize_with = "to_hex")]
pub failure_onion: Option<Vec<u8>>,
}
impl HtlcAcceptedResponse {
pub fn continue_(
payload: Option<Vec<u8>>,
forward_to: Option<Vec<u8>>,
extra_tlvs: Option<Vec<u8>>,
) -> Self {
Self {
result: HtlcAcceptedResult::Continue,
payment_key: None,
payload,
forward_to,
extra_tlvs,
failure_message: None,
failure_onion: None,
}
}
pub fn fail(failure_message: Option<String>, failure_onion: Option<Vec<u8>>) -> Self {
Self {
result: HtlcAcceptedResult::Fail,
payment_key: None,
payload: None,
forward_to: None,
extra_tlvs: None,
failure_message,
failure_onion,
}
}
}
/// Deserializes a lowercase hex string to a `Vec<u8>`.
pub fn from_hex<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string| Vec::from_hex(string).map_err(|err| Error::custom(err.to_string())))
}
pub fn to_hex<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match bytes {
Some(data) => serializer.serialize_str(&hex::encode(data)),
None => serializer.serialize_none(),
}
}
pub mod tlv {
use anyhow::Result;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
use std::{convert::TryFrom, fmt};
#[derive(Clone, Debug)]
pub struct TlvRecord {
pub type_: u64,
pub value: Vec<u8>,
}
#[derive(Clone, Debug, Default)]
pub struct TlvStream(pub Vec<TlvRecord>);
#[derive(Debug)]
pub enum TlvError {
DuplicateType(u64),
NotSorted,
LengthMismatch(u64, usize, usize),
Truncated,
NonCanonicalBigSize,
TrailingBytes,
Hex(hex::FromHexError),
Other(String),
}
impl fmt::Display for TlvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TlvError::DuplicateType(t) => write!(f, "duplicate tlv type {}", t),
TlvError::NotSorted => write!(f, "tlv types must be strictly increasing"),
TlvError::LengthMismatch(t, e, g) => {
write!(f, "length mismatch type {}: expected {}, got {}", t, e, g)
}
TlvError::Truncated => write!(f, "truncated input"),
TlvError::NonCanonicalBigSize => write!(f, "non-canonical bigsize encoding"),
TlvError::TrailingBytes => write!(f, "leftover bytes after parsing"),
TlvError::Hex(e) => write!(f, "hex error: {}", e),
TlvError::Other(s) => write!(f, "{}", s),
}
}
}
impl std::error::Error for TlvError {}
impl From<hex::FromHexError> for TlvError {
fn from(e: hex::FromHexError) -> Self {
TlvError::Hex(e)
}
}
impl TlvStream {
pub fn to_bytes(&mut self) -> Result<Vec<u8>> {
self.0.sort_by_key(|r| r.type_);
for w in self.0.windows(2) {
if w[0].type_ == w[1].type_ {
return Err(TlvError::DuplicateType(w[0].type_).into());
}
if w[0].type_ > w[1].type_ {
return Err(TlvError::NotSorted.into());
}
}
let mut out = Vec::new();
for rec in &self.0 {
out.extend(encode_bigsize(rec.type_));
out.extend(encode_bigsize(rec.value.len() as u64));
out.extend(&rec.value);
}
Ok(out)
}
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let mut recs = Vec::new();
let mut last_type: Option<u64> = None;
while !bytes.is_empty() {
let (t, n1) = decode_bigsize(bytes)?;
bytes = &bytes[n1..];
let (len, n2) = decode_bigsize(bytes)?;
bytes = &bytes[n2..];
let l =
usize::try_from(len).map_err(|_| TlvError::Other("length too large".into()))?;
if bytes.len() < l {
return Err(TlvError::Truncated.into());
}
let v = bytes[..l].to_vec();
bytes = &bytes[l..];
if let Some(prev) = last_type {
if t == prev {
return Err(TlvError::DuplicateType(t).into());
}
if t < prev {
return Err(TlvError::NotSorted.into());
}
}
last_type = Some(t);
recs.push(TlvRecord { type_: t, value: v });
}
Ok(TlvStream(recs))
}
pub fn from_bytes_with_length_prefix(bytes: &[u8]) -> Result<Self> {
if bytes.is_empty() {
return Err(TlvError::Truncated.into());
}
let (length, length_bytes) = decode_bigsize(bytes)?;
let remaining = &bytes[length_bytes..];
let length_usize = usize::try_from(length)
.map_err(|_| TlvError::Other("length prefix too large".into()))?;
if remaining.len() != length_usize {
return Err(TlvError::LengthMismatch(0, length_usize, remaining.len()).into());
}
Self::from_bytes(remaining)
}
/// Attempt to auto-detect whether the input has a length prefix or not
/// First tries to parse as length-prefixed, then falls back to raw TLV
/// parsing.
pub fn from_bytes_auto(bytes: &[u8]) -> Result<Self> {
// Try length-prefixed first
if let Ok(stream) = Self::from_bytes_with_length_prefix(bytes) {
return Ok(stream);
}
// Fall back to raw TLV parsing
Self::from_bytes(bytes)
}
/// Get a reference to the value of a TLV record by type.
pub fn get(&self, type_: u64) -> Option<&[u8]> {
self.0
.iter()
.find(|rec| rec.type_ == type_)
.map(|rec| rec.value.as_slice())
}
/// Insert a TLV record (replaces if type already exists).
pub fn insert(&mut self, type_: u64, value: Vec<u8>) {
// If the type already exists, replace its value.
if let Some(rec) = self.0.iter_mut().find(|rec| rec.type_ == type_) {
rec.value = value;
return;
}
// Otherwise push and re-sort to maintain canonical order.
self.0.push(TlvRecord { type_, value });
self.0.sort_by_key(|r| r.type_);
}
/// Remove a record by type.
pub fn remove(&mut self, type_: u64) -> Option<Vec<u8>> {
if let Some(pos) = self.0.iter().position(|rec| rec.type_ == type_) {
Some(self.0.remove(pos).value)
} else {
None
}
}
/// Check if a type exists.
pub fn contains(&self, type_: u64) -> bool {
self.0.iter().any(|rec| rec.type_ == type_)
}
/// Insert or override a `tu64` value for `type_` (keeps canonical TLV order).
pub fn set_tu64(&mut self, type_: u64, value: u64) {
let enc = encode_tu64(value);
if let Some(rec) = self.0.iter_mut().find(|r| r.type_ == type_) {
rec.value = enc;
} else {
self.0.push(TlvRecord { type_, value: enc });
self.0.sort_by_key(|r| r.type_);
}
}
/// Read a `tu64` if present, validating minimal encoding.
/// Returns Ok(None) if the type isn't present.
pub fn get_tu64(&self, type_: u64) -> Result<Option<u64>, TlvError> {
if let Some(rec) = self.0.iter().find(|r| r.type_ == type_) {
Ok(Some(decode_tu64(&rec.value)?))
} else {
Ok(None)
}
}
}
impl Serialize for TlvStream {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut tmp = self.clone();
let bytes = tmp.to_bytes().map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&hex::encode(bytes))
}
}
impl<'de> Deserialize<'de> for TlvStream {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = TlvStream;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a hex string representing a Lightning TLV stream")
}
fn visit_str<E: DeError>(self, s: &str) -> Result<Self::Value, E> {
let bytes = hex::decode(s).map_err(E::custom)?;
TlvStream::from_bytes_auto(&bytes).map_err(E::custom)
}
}
deserializer.deserialize_str(V)
}
}
impl TryFrom<&[u8]> for TlvStream {
type Error = anyhow::Error;
fn try_from(value: &[u8]) -> Result<Self> {
TlvStream::from_bytes(value)
}
}
impl From<Vec<TlvRecord>> for TlvStream {
fn from(v: Vec<TlvRecord>) -> Self {
TlvStream(v)
}
}
/// BOLT #1 BigSize encoding
fn encode_bigsize(x: u64) -> Vec<u8> {
let mut out = Vec::new();
if x < 0xfd {
out.push(x as u8);
} else if x <= 0xffff {
out.push(0xfd);
out.extend_from_slice(&(x as u16).to_be_bytes());
} else if x <= 0xffff_ffff {
out.push(0xfe);
out.extend_from_slice(&(x as u32).to_be_bytes());
} else {
out.push(0xff);
out.extend_from_slice(&x.to_be_bytes());
}
out
}
fn decode_bigsize(input: &[u8]) -> Result<(u64, usize)> {
if input.is_empty() {
return Err(TlvError::Truncated.into());
}
match input[0] {
n @ 0x00..=0xfc => Ok((n as u64, 1)),
0xfd => {
if input.len() < 3 {
return Err(TlvError::Truncated.into());
}
let v = u16::from_be_bytes([input[1], input[2]]) as u64;
if v < 0xfd {
return Err(TlvError::NonCanonicalBigSize.into());
}
Ok((v, 3))
}
0xfe => {
if input.len() < 5 {
return Err(TlvError::Truncated.into());
}
let v = u32::from_be_bytes([input[1], input[2], input[3], input[4]]) as u64;
if v <= 0xffff {
return Err(TlvError::NonCanonicalBigSize.into());
}
Ok((v, 5))
}
0xff => {
if input.len() < 9 {
return Err(TlvError::Truncated.into());
}
let v = u64::from_be_bytes([
input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8],
]);
if v <= 0xffff_ffff {
return Err(TlvError::NonCanonicalBigSize.into());
}
Ok((v, 9))
}
}
}
/// Encode a BOLT #1 `tu64`: big-endian, minimal length (no leading 0x00).
/// Value 0 is encoded as zero-length.
pub fn encode_tu64(v: u64) -> Vec<u8> {
if v == 0 {
return Vec::new();
}
let bytes = v.to_be_bytes();
let first = bytes.iter().position(|&b| b != 0).unwrap(); // safe: v != 0
bytes[first..].to_vec()
}
/// Decode a BOLT #1 `tu64`, enforcing minimal form.
/// Empty slice -> 0. Leading 0x00 or >8 bytes is invalid.
fn decode_tu64(raw: &[u8]) -> Result<u64, TlvError> {
if raw.is_empty() {
return Ok(0);
}
if raw.len() > 8 {
return Err(TlvError::Other("tu64 too long".into()));
}
if raw[0] == 0 {
return Err(TlvError::Other("non-minimal tu64 (leading zero)".into()));
}
let mut buf = [0u8; 8];
buf[8 - raw.len()..].copy_from_slice(raw);
Ok(u64::from_be_bytes(buf))
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
// Small helpers to keep tests readable
fn rec(type_: u64, value: &[u8]) -> TlvRecord {
TlvRecord {
type_,
value: value.to_vec(),
}
}
fn build_bytes(type_: u64, value: &[u8]) -> Vec<u8> {
let mut v = Vec::new();
v.extend(super::encode_bigsize(type_));
v.extend(super::encode_bigsize(value.len() as u64));
v.extend(value);
v
}
#[test]
fn encode_then_decode_roundtrip() -> Result<()> {
let mut stream = TlvStream(vec![rec(1, &[0x01, 0x02]), rec(5, &[0xaa])]);
// Encode
let bytes = stream.to_bytes()?;
// Expect exact TLV sequence:
// type=1 -> 0x01, len=2 -> 0x02, value=0x01 0x02
// type=5 -> 0x05, len=1 -> 0x01, value=0xaa
assert_eq!(hex::encode(&bytes), "010201020501aa");
// Decode back
let decoded = TlvStream::from_bytes(&bytes)?;
assert_eq!(decoded.0.len(), 2);
assert_eq!(decoded.0[0].type_, 1);
assert_eq!(decoded.0[0].value, vec![0x01, 0x02]);
assert_eq!(decoded.0[1].type_, 5);
assert_eq!(decoded.0[1].value, vec![0xaa]);
Ok(())
}
#[test]
fn json_hex_roundtrip() -> Result<()> {
let stream = TlvStream(vec![rec(1, &[0x01, 0x02]), rec(5, &[0xaa])]);
// Serialize to hex string in JSON
let json = serde_json::to_string(&stream)?;
// It's a quoted hex string; check inner value
let s: String = serde_json::from_str(&json)?;
assert_eq!(s, "010201020501aa");
// And back from JSON hex
let back: TlvStream = serde_json::from_str(&json)?;
assert_eq!(back.0.len(), 2);
assert_eq!(back.0[0].type_, 1);
assert_eq!(back.0[0].value, vec![0x01, 0x02]);
assert_eq!(back.0[1].type_, 5);
assert_eq!(back.0[1].value, vec![0xaa]);
Ok(())
}
#[test]
fn decode_with_len_prefix() -> Result<()> {
let payload = "1202039896800401760608000073000f2c0007";
let stream = TlvStream::from_bytes_with_length_prefix(&hex::decode(payload).unwrap())?;
// let stream: TlvStream = serde_json::from_str(payload)?;
println!("TLV {:?}", stream.0);
Ok(())
}
#[test]
fn bigsize_boundary_minimal_encodings() -> Result<()> {
// Types at 0xfc, 0xfd, 0x10000 to exercise size switches
let mut stream = TlvStream(vec![
rec(0x00fc, &[0x11]), // single-byte bigsize
rec(0x00fd, &[0x22]), // 0xfd prefix + u16
rec(0x0001_0000, &[0x33]), // 0xfe prefix + u32
]);
let bytes = stream.to_bytes()?; // just ensure it encodes
// Decode back to confirm roundtrip/canonical encodings accepted
let back = TlvStream::from_bytes(&bytes)?;
assert_eq!(back.0[0].type_, 0x00fc);
assert_eq!(back.0[1].type_, 0x00fd);
assert_eq!(back.0[2].type_, 0x0001_0000);
Ok(())
}
#[test]
fn decode_rejects_non_canonical_bigsize() {
// (1) Non-canonical: 0xfd 00 fc encodes 0xfc but should be a single byte
let mut bytes = Vec::new();
bytes.extend([0xfd, 0x00, 0xfc]); // non-canonical type
bytes.extend([0x01]); // len = 1
bytes.extend([0x00]); // value
let err = TlvStream::from_bytes(&bytes).unwrap_err();
assert!(format!("{}", err).contains("non-canonical"));
// (2) Non-canonical: 0xfe 00 00 00 ff encodes 0xff but should be 0xfd-form
let mut bytes = Vec::new();
bytes.extend([0xfe, 0x00, 0x00, 0x00, 0xff]);
bytes.extend([0x01]);
bytes.extend([0x00]);
let err = TlvStream::from_bytes(&bytes).unwrap_err();
assert!(format!("{}", err).contains("non-canonical"));
// (3) Non-canonical: 0xff 00..01 encodes 1, which should be single byte
let mut bytes = Vec::new();
bytes.extend([0xff, 0, 0, 0, 0, 0, 0, 0, 1]);
bytes.extend([0x01]);
bytes.extend([0x00]);
let err = TlvStream::from_bytes(&bytes).unwrap_err();
assert!(format!("{}", err).contains("non-canonical"));
}
#[test]
fn decode_rejects_out_of_order_types() {
// Build two TLVs but put type 5 before type 1
let mut bad = Vec::new();
bad.extend(build_bytes(5, &[0xaa]));
bad.extend(build_bytes(1, &[0x00]));
let err = TlvStream::from_bytes(&bad).unwrap_err();
assert!(
format!("{}", err).contains("increasing") || format!("{}", err).contains("sorted"),
"expected ordering error, got: {err}"
);
}
#[test]
fn decode_rejects_duplicate_types() {
// Two records with same type=1
let mut bad = Vec::new();
bad.extend(build_bytes(1, &[0x01]));
bad.extend(build_bytes(1, &[0x02]));
let err = TlvStream::from_bytes(&bad).unwrap_err();
assert!(
format!("{}", err).contains("duplicate"),
"expected duplicate error, got: {err}"
);
}
#[test]
fn encode_rejects_duplicate_types() {
// insert duplicate types and expect encode to fail
let mut s = TlvStream(vec![rec(1, &[0x01]), rec(1, &[0x02])]);
let err = s.to_bytes().unwrap_err();
assert!(
format!("{}", err).contains("duplicate"),
"expected duplicate error, got: {err}"
);
}
#[test]
fn decode_truncated_value() {
// type=1, len=2 but only 1 byte of value provided
let mut bytes = Vec::new();
bytes.extend(encode_bigsize(1));
bytes.extend(encode_bigsize(2));
bytes.push(0x00); // missing one more byte
let err = TlvStream::from_bytes(&bytes).unwrap_err();
assert!(
format!("{}", err).contains("truncated"),
"expected truncated error, got: {err}"
);
}
#[test]
fn set_and_get_tu64_basic() -> Result<()> {
let mut s = TlvStream::default();
s.set_tu64(42, 123456789);
assert_eq!(s.get_tu64(42)?, Some(123456789));
Ok(())
}
#[test]
fn set_tu64_overwrite_keeps_order() -> Result<()> {
let mut s = TlvStream(vec![
TlvRecord {
type_: 1,
value: vec![0xaa],
},
TlvRecord {
type_: 10,
value: vec![0xbb],
},
]);
// insert between 1 and 10
s.set_tu64(5, 7);
assert_eq!(
s.0.iter().map(|r| r.type_).collect::<Vec<_>>(),
vec![1, 5, 10]
);
assert_eq!(s.get_tu64(5)?, Some(7));
// overwrite existing 5 (no duplicate, order preserved)
s.set_tu64(5, 9);
let types: Vec<u64> = s.0.iter().map(|r| r.type_).collect();
assert_eq!(types, vec![1, 5, 10]);
assert_eq!(s.0.iter().filter(|r| r.type_ == 5).count(), 1);
assert_eq!(s.get_tu64(5)?, Some(9));
Ok(())
}
#[test]
fn tu64_zero_encodes_empty_and_roundtrips() -> Result<()> {
let mut s = TlvStream::default();
s.set_tu64(3, 0);
// stored value is zero-length
let rec = s.0.iter().find(|r| r.type_ == 3).unwrap();
assert!(rec.value.is_empty());
// wire round-trip
let mut sc = s.clone();
let bytes = sc.to_bytes()?;
let s2 = TlvStream::from_bytes(&bytes)?;
assert_eq!(s2.get_tu64(3)?, Some(0));
Ok(())
}
#[test]
fn get_tu64_missing_returns_none() -> Result<()> {
let s = TlvStream::default();
assert_eq!(s.get_tu64(999)?, None);
Ok(())
}
#[test]
fn get_tu64_rejects_non_minimal_and_too_long() {
// non-minimal: leading zero
let mut s = TlvStream::default();
s.0.push(TlvRecord {
type_: 9,
value: vec![0x00, 0x01],
});
assert!(s.get_tu64(9).is_err());
// too long: 9 bytes
let mut s2 = TlvStream::default();
s2.0.push(TlvRecord {
type_: 9,
value: vec![0; 9],
});
assert!(s2.get_tu64(9).is_err());
}
#[test]
fn tu64_multi_roundtrip_bytes_and_json() -> Result<()> {
let mut s = TlvStream::default();
s.set_tu64(42, 0);
s.set_tu64(7, 256);
// wire roundtrip
let mut sc = s.clone();
let bytes = sc.to_bytes()?;
let s2 = TlvStream::from_bytes(&bytes)?;
assert_eq!(s2.get_tu64(42)?, Some(0));
assert_eq!(s2.get_tu64(7)?, Some(256));
// json hex roundtrip (custom Serialize/Deserialize)
let json = serde_json::to_string(&s)?;
let s3: TlvStream = serde_json::from_str(&json)?;
assert_eq!(s3.get_tu64(42)?, Some(0));
assert_eq!(s3.get_tu64(7)?, Some(256));
Ok(())
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
use cln_plugin::options;
pub mod cln;
pub mod handler;
pub mod model;

View File

@@ -7,6 +7,11 @@ use chrono::Utc;
use log::debug;
use serde::{Deserialize, Serialize};
pub mod failure_codes {
pub const TEMPORARY_CHANNEL_FAILURE: &'static str = "1007";
pub const UNKNOWN_NEXT_PEER: &'static str = "4010";
}
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
InvalidOpeningFeeParams,
@@ -256,6 +261,18 @@ pub struct Lsps2PolicyGetInfoResponse {
pub policy_opening_fee_params_menu: Vec<PolicyOpeningFeeParams>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Lsps2PolicyGetChannelCapacityRequest {
pub opening_fee_params: OpeningFeeParams,
pub init_payment_size: Msat,
pub scid: ShortChannelId,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Lsps2PolicyGetChannelCapacityResponse {
pub channel_capacity_msat: Option<u64>,
}
/// An internal representation of a policy of parameters for calculating the
/// opening fee for a JIT channel.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@@ -6,6 +6,8 @@ use cln_lsps::jsonrpc::{server::JsonRpcServer, JsonRpcRequest};
use cln_lsps::lsps0::handler::Lsps0ListProtocolsHandler;
use cln_lsps::lsps0::model::Lsps0listProtocolsRequest;
use cln_lsps::lsps0::transport::{self, CustomMsg};
use cln_lsps::lsps2::cln::{HtlcAcceptedRequest, HtlcAcceptedResponse};
use cln_lsps::lsps2::handler::{ClnApiRpc, HtlcAcceptedHookHandler};
use cln_lsps::lsps2::model::{Lsps2BuyRequest, Lsps2GetInfoRequest};
use cln_lsps::util::wrap_payload_with_peer_id;
use cln_lsps::{lsps0, lsps2, util, LSP_FEATURE_BIT};
@@ -27,6 +29,7 @@ const OPTION_ENABLED: options::FlagConfigOption = ConfigOption::new_flag(
#[derive(Clone)]
struct State {
lsps_service: JsonRpcServer,
lsps2_enabled: bool,
}
#[tokio::main]
@@ -44,6 +47,7 @@ async fn main() -> Result<(), anyhow::Error> {
util::feature_bit_to_hex(LSP_FEATURE_BIT),
)
.hook("custommsg", on_custommsg)
.hook("htlc_accepted", on_htlc_accepted)
.configure()
.await?
{
@@ -63,7 +67,7 @@ async fn main() -> Result<(), anyhow::Error> {
}),
);
if plugin.option(&lsps2::OPTION_ENABLED)? {
let lsps2_enabled = if plugin.option(&lsps2::OPTION_ENABLED)? {
log::debug!("lsps2 enabled");
let secret_hex = plugin.option(&lsps2::OPTION_PROMISE_SECRET)?;
if let Some(secret_hex) = secret_hex {
@@ -104,11 +108,17 @@ async fn main() -> Result<(), anyhow::Error> {
)
.with_handler(Lsps2BuyRequest::METHOD.to_string(), Arc::new(buy_handler));
}
}
true
} else {
false
};
let lsps_service = lsps_builder.build();
let state = State { lsps_service };
let state = State {
lsps_service,
lsps2_enabled,
};
let plugin = plugin.start(state).await?;
plugin.join().await
} else {
@@ -116,6 +126,27 @@ async fn main() -> Result<(), anyhow::Error> {
}
}
async fn on_htlc_accepted(
p: Plugin<State>,
v: serde_json::Value,
) -> Result<serde_json::Value, anyhow::Error> {
if !p.state().lsps2_enabled {
// just continue.
// Fixme: Add forward and extra tlvs from incoming.
let res = serde_json::to_value(&HtlcAcceptedResponse::continue_(None, None, None))?;
return Ok(res);
}
let req: HtlcAcceptedRequest = serde_json::from_value(v)?;
let rpc_path = Path::new(&p.configuration().lightning_dir).join(&p.configuration().rpc_file);
let api = ClnApiRpc::new(rpc_path);
// Fixme: Use real htlc_minimum_amount.
let handler = HtlcAcceptedHookHandler::new(api, 1000);
let res = handler.handle(req).await?;
let res_val = serde_json::to_value(&res)?;
Ok(res_val)
}
async fn on_custommsg(
p: Plugin<State>,
v: serde_json::Value,

View File

@@ -41,5 +41,16 @@ def lsps2_getpolicy(request):
]
}
@plugin.method("dev-lsps2-getchannelcapacity")
def lsps2_getchannelcapacity(request, init_payment_size, scid, opening_fee_params):
""" Returns an opening fee menu for the LSPS2 plugin.
"""
now = datetime.now(timezone.utc)
# Is ISO 8601 format "YYYY-MM-DDThh:mm:ss.uuuZ"
valid_until = (now + timedelta(hours=1)).isoformat().replace('+00:00', 'Z')
return { "channel_capacity_msat": 100000000 }
plugin.run()