Skip to content

Commit cd77dcd

Browse files
authored
Merge pull request bitcoin-dev-project#410 from bitcoin-dev-project/cln-needs-a-full-channel
ln node might only know about 1 side of channel
2 parents 487c5fc + d4f37ca commit cd77dcd

File tree

3 files changed

+151
-114
lines changed

3 files changed

+151
-114
lines changed

src/warnet/cln.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from warnet.services import ServiceType
66
from warnet.utils import exponential_backoff, generate_ipv4_addr, handle_json
77

8-
from .lnchannel import LNChannel
8+
from .lnchannel import LNChannel, LNPolicy
99
from .lnnode import LNNode
1010
from .status import RunningStatus
1111

@@ -96,29 +96,46 @@ def get_graph_channels(self) -> list[LNChannel]:
9696
short_channel_ids = {chan["short_channel_id"]: chan for chan in cln_channels}.keys()
9797
channels = []
9898
for short_channel_id in short_channel_ids:
99-
node1, node2 = (
99+
nodes = [
100100
chans for chans in cln_channels if chans["short_channel_id"] == short_channel_id
101-
)
102-
channels.append(self.lnchannel_from_json(node1, node2))
101+
]
102+
# CLN has only heard about one side of the channel
103+
if len(nodes) == 1:
104+
channels.append(self.lnchannel_from_json(nodes[0], None))
105+
continue
106+
channels.append(self.lnchannel_from_json(nodes[0], nodes[1]))
103107
return channels
104108

105109
@staticmethod
106110
def lnchannel_from_json(node1: object, node2: object) -> LNChannel:
107111
assert node1["short_channel_id"] == node2["short_channel_id"]
108112
assert node1["direction"] != node2["direction"]
113+
if not node1:
114+
raise ValueError("node1 can't be None")
115+
116+
node2_policy = (
117+
LNPolicy(
118+
min_htlc=node2["htlc_minimum_msat"],
119+
max_htlc=node2["htlc_maximum_msat"],
120+
base_fee_msat=node2["base_fee_millisatoshi"],
121+
fee_rate_milli_msat=node2["fee_per_millionth"],
122+
)
123+
if node2 is not None
124+
else None
125+
)
126+
109127
return LNChannel(
110128
node1_pub=node1["source"],
111-
node2_pub=node2["source"],
129+
node2_pub=node1["destination"],
112130
capacity_msat=node1["amount_msat"],
113131
short_chan_id=node1["short_channel_id"],
114-
node1_min_htlc=node1["htlc_minimum_msat"],
115-
node2_min_htlc=node2["htlc_minimum_msat"],
116-
node1_max_htlc=node1["htlc_maximum_msat"],
117-
node2_max_htlc=node2["htlc_maximum_msat"],
118-
node1_base_fee_msat=node1["base_fee_millisatoshi"],
119-
node2_base_fee_msat=node2["base_fee_millisatoshi"],
120-
node1_fee_rate_milli_msat=node1["fee_per_millionth"],
121-
node2_fee_rate_milli_msat=node2["fee_per_millionth"],
132+
node1_policy=LNPolicy(
133+
min_htlc=node1["htlc_minimum_msat"],
134+
max_htlc=node1["htlc_maximum_msat"],
135+
base_fee_msat=node1["base_fee_millisatoshi"],
136+
fee_rate_milli_msat=node1["fee_per_millionth"],
137+
),
138+
node2_policy=node2_policy,
122139
)
123140

124141
def get_peers(self) -> list[str]:

src/warnet/lnchannel.py

Lines changed: 94 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,51 @@
11
import logging
22

33

4+
class LNPolicy:
5+
def __init__(
6+
self,
7+
min_htlc: int,
8+
max_htlc: int,
9+
base_fee_msat: int,
10+
fee_rate_milli_msat: int,
11+
time_lock_delta: int = 0,
12+
) -> None:
13+
self.min_htlc = min_htlc
14+
self.max_htlc = max_htlc
15+
self.base_fee_msat = base_fee_msat
16+
self.fee_rate_milli_msat = fee_rate_milli_msat
17+
self.time_lock_delta = time_lock_delta
18+
19+
def __str__(self) -> str:
20+
return (
21+
f"LNPolicy(min_htlc={self.min_htlc}, "
22+
f"max_htlc={self.max_htlc}, "
23+
f"base_fee={self.base_fee_msat}, "
24+
f"fee_rate={self.fee_rate_milli_msat}, "
25+
f"time_lock_delta={self.time_lock_delta})"
26+
)
27+
28+
429
class LNChannel:
530
def __init__(
631
self,
732
node1_pub: str,
833
node2_pub: str,
934
capacity_msat: int = 0,
1035
short_chan_id: str = "",
11-
node1_min_htlc: int = 0,
12-
node2_min_htlc: int = 0,
13-
node1_max_htlc: int = 0,
14-
node2_max_htlc: int = 0,
15-
node1_base_fee_msat: int = 0,
16-
node2_base_fee_msat: int = 0,
17-
node1_fee_rate_milli_msat: int = 0,
18-
node2_fee_rate_milli_msat: int = 0,
19-
node1_time_lock_delta: int = 0,
20-
node2_time_lock_delta: int = 0,
36+
node1_policy: LNPolicy = None,
37+
node2_policy: LNPolicy = None,
2138
) -> None:
2239
# Ensure that the node with the lower pubkey is node1
2340
if node1_pub > node2_pub:
2441
node1_pub, node2_pub = node2_pub, node1_pub
25-
node1_min_htlc, node2_min_htlc = node2_min_htlc, node1_min_htlc
26-
node1_max_htlc, node2_max_htlc = node2_max_htlc, node1_max_htlc
27-
node1_base_fee_msat, node2_base_fee_msat = node2_base_fee_msat, node1_base_fee_msat
28-
node1_fee_rate_milli_msat, node2_fee_rate_milli_msat = (
29-
node2_fee_rate_milli_msat,
30-
node1_fee_rate_milli_msat,
31-
)
32-
node1_time_lock_delta, node2_time_lock_delta = (
33-
node2_time_lock_delta,
34-
node1_time_lock_delta,
35-
)
42+
node1_policy, node2_policy = node2_policy, node1_policy
3643
self.node1_pub = node1_pub
3744
self.node2_pub = node2_pub
3845
self.capacity_msat = capacity_msat
3946
self.short_chan_id = short_chan_id
40-
self.node1_min_htlc = node1_min_htlc
41-
self.node2_min_htlc = node2_min_htlc
42-
self.node1_max_htlc = node1_max_htlc
43-
self.node2_max_htlc = node2_max_htlc
44-
self.node1_base_fee_msat = node1_base_fee_msat
45-
self.node2_base_fee_msat = node2_base_fee_msat
46-
self.node1_fee_rate_milli_msat = node1_fee_rate_milli_msat
47-
self.node2_fee_rate_milli_msat = node2_fee_rate_milli_msat
48-
self.node1_time_lock_delta = node1_time_lock_delta
49-
self.node2_time_lock_delta = node2_time_lock_delta
47+
self.node1_policy = node1_policy
48+
self.node2_policy = node2_policy
5049
self.logger = logging.getLogger("lnchan")
5150

5251
def __str__(self) -> str:
@@ -55,16 +54,8 @@ def __str__(self) -> str:
5554
f"capacity_msat={self.capacity_msat}, "
5655
f"node1_pub={self.node1_pub[:8]}..., "
5756
f"node2_pub={self.node2_pub[:8]}..., "
58-
f"node1_policy=(min_htlc={self.node1_min_htlc}, "
59-
f"max_htlc={self.node1_max_htlc}, "
60-
f"base_fee={self.node1_base_fee_msat}, "
61-
f"fee_rate={self.node1_fee_rate_milli_msat}, "
62-
f"time_lock_delta={self.node1_time_lock_delta}), "
63-
f"node2_policy=(min_htlc={self.node2_min_htlc}, "
64-
f"max_htlc={self.node2_max_htlc}, "
65-
f"base_fee={self.node2_base_fee_msat}, "
66-
f"fee_rate={self.node2_fee_rate_milli_msat}, "
67-
f"time_lock_delta={self.node2_time_lock_delta}))"
57+
f"node1_policy=({self.node1_policy.__str__()}), "
58+
f"node2_policy=({self.node2_policy.__str__()}))"
6859
)
6960

7061
# Only used to compare warnet channels imported from a mainnet source file
@@ -76,22 +67,22 @@ def flip(self) -> "LNChannel":
7667
node2_pub=self.node2_pub,
7768
capacity_msat=self.capacity_msat,
7869
short_chan_id=self.short_chan_id,
79-
# Flip the policies
80-
node1_min_htlc=self.node2_min_htlc,
81-
node2_min_htlc=self.node1_min_htlc,
82-
node1_max_htlc=self.node2_max_htlc,
83-
node2_max_htlc=self.node1_max_htlc,
84-
node1_base_fee_msat=self.node2_base_fee_msat,
85-
node2_base_fee_msat=self.node1_base_fee_msat,
86-
node1_fee_rate_milli_msat=self.node2_fee_rate_milli_msat,
87-
node2_fee_rate_milli_msat=self.node1_fee_rate_milli_msat,
88-
node1_time_lock_delta=self.node2_time_lock_delta,
89-
node2_time_lock_delta=self.node1_time_lock_delta,
70+
node1_policy=self.node2_policy,
71+
node2_policy=self.node1_policy,
9072
)
9173

9274
def policy_match(self, ch2: "LNChannel") -> bool:
9375
assert isinstance(ch2, LNChannel)
9476

77+
node1_policy_match = False
78+
node2_policy_match = False
79+
80+
if self.node1_policy is None and ch2.node1_policy is None:
81+
node1_policy_match = True
82+
83+
if self.node2_policy is None and ch2.node2_policy is None:
84+
node2_policy_match = True
85+
9586
def compare_attributes(attr1, attr2, min_value=0, attr_name=""):
9687
if attr1 == 0 or attr2 == 0:
9788
return True
@@ -100,30 +91,59 @@ def compare_attributes(attr1, attr2, min_value=0, attr_name=""):
10091
self.logger.debug(f"Mismatch in {attr_name}: {attr1} != {attr2}")
10192
return result
10293

103-
attributes_to_compare = [
104-
(self.node1_time_lock_delta, ch2.node1_time_lock_delta, 18, "node1_time_lock_delta"),
105-
(self.node2_time_lock_delta, ch2.node2_time_lock_delta, 18, "node2_time_lock_delta"),
106-
(self.node1_min_htlc, ch2.node1_min_htlc, 1, "node1_min_htlc"),
107-
(self.node2_min_htlc, ch2.node2_min_htlc, 1, "node2_min_htlc"),
108-
(self.node1_base_fee_msat, ch2.node1_base_fee_msat, 0, "node1_base_fee_msat"),
109-
(self.node2_base_fee_msat, ch2.node2_base_fee_msat, 0, "node2_base_fee_msat"),
110-
(
111-
self.node1_fee_rate_milli_msat,
112-
ch2.node1_fee_rate_milli_msat,
113-
0,
114-
"node1_fee_rate_milli_msat",
115-
),
116-
(
117-
self.node2_fee_rate_milli_msat,
118-
ch2.node2_fee_rate_milli_msat,
119-
0,
120-
"node2_fee_rate_milli_msat",
121-
),
122-
]
123-
124-
return all(compare_attributes(*attrs) for attrs in attributes_to_compare)
94+
if self.node1_policy is not None and ch2.node1_policy is not None:
95+
attributes_to_compare = [
96+
(
97+
self.node1_policy.time_lock_delta,
98+
ch2.node1_policy.time_lock_delta,
99+
18,
100+
"node1_time_lock_delta",
101+
),
102+
(self.node1_policy.min_htlc, ch2.node1_policy.min_htlc, 1, "node1_min_htlc"),
103+
(
104+
self.node1_policy.base_fee_msat,
105+
ch2.node1_policy.base_fee_msat,
106+
0,
107+
"node1_base_fee_msat",
108+
),
109+
(
110+
self.node1_policy.fee_rate_milli_msat,
111+
ch2.node1_policy.fee_rate_milli_msat,
112+
0,
113+
"node1_fee_rate_milli_msat",
114+
),
115+
]
116+
node1_policy_match = all(compare_attributes(*attrs) for attrs in attributes_to_compare)
117+
118+
if self.node2_policy is not None and ch2.node2_policy is not None:
119+
attributes_to_compare = [
120+
(
121+
self.node2_policy.time_lock_delta,
122+
ch2.node2_policy.time_lock_delta,
123+
18,
124+
"node2_time_lock_delta",
125+
),
126+
(self.node2_policy.min_htlc, ch2.node2_policy.min_htlc, 1, "node2_min_htlc"),
127+
(
128+
self.node2_policy.base_fee_msat,
129+
ch2.node2_policy.base_fee_msat,
130+
0,
131+
"node2_base_fee_msat",
132+
),
133+
(
134+
self.node2_policy.fee_rate_milli_msat,
135+
ch2.node2_policy.fee_rate_milli_msat,
136+
0,
137+
"node2_fee_rate_milli_msat",
138+
),
139+
]
140+
node2_policy_match = all(compare_attributes(*attrs) for attrs in attributes_to_compare)
141+
142+
return node1_policy_match and node2_policy_match
125143

126144
def channel_match(self, ch2: "LNChannel") -> bool:
145+
print(self)
146+
print(ch2)
127147
if self.capacity_msat != ch2.capacity_msat:
128148
self.logger.debug(f"Capacity mismatch: {self.capacity_msat} != {ch2.capacity_msat}")
129149
return False

src/warnet/lnd.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from warnet.services import ServiceType
66
from warnet.utils import exponential_backoff, generate_ipv4_addr, handle_json
77

8-
from .lnchannel import LNChannel
8+
from .lnchannel import LNChannel, LNPolicy
99
from .lnnode import LNNode, lnd_to_cl_scid
1010
from .status import RunningStatus
1111

@@ -113,37 +113,37 @@ def get_graph_channels(self) -> list[LNChannel]:
113113

114114
@staticmethod
115115
def lnchannel_from_json(edge: object) -> LNChannel:
116+
node1_policy = (
117+
LNPolicy(
118+
min_htlc=int(edge["node1_policy"]["min_htlc"]),
119+
max_htlc=int(edge["node1_policy"]["max_htlc_msat"]),
120+
base_fee_msat=int(edge["node1_policy"]["fee_base_msat"]),
121+
fee_rate_milli_msat=int(edge["node1_policy"]["fee_rate_milli_msat"]),
122+
time_lock_delta=int(edge["node1_policy"]["time_lock_delta"]),
123+
)
124+
if edge["node1_policy"]
125+
else None
126+
)
127+
128+
node2_policy = (
129+
LNPolicy(
130+
min_htlc=int(edge["node2_policy"]["min_htlc"]),
131+
max_htlc=int(edge["node2_policy"]["max_htlc_msat"]),
132+
base_fee_msat=int(edge["node2_policy"]["fee_base_msat"]),
133+
fee_rate_milli_msat=int(edge["node2_policy"]["fee_rate_milli_msat"]),
134+
time_lock_delta=int(edge["node2_policy"]["time_lock_delta"]),
135+
)
136+
if edge["node2_policy"]
137+
else None
138+
)
139+
116140
return LNChannel(
117141
node1_pub=edge["node1_pub"],
118142
node2_pub=edge["node2_pub"],
119143
capacity_msat=(int(edge["capacity"]) * 1000),
120144
short_chan_id=lnd_to_cl_scid(edge["channel_id"]),
121-
node1_min_htlc=int(edge["node1_policy"]["min_htlc"]) if edge["node1_policy"] else 0,
122-
node2_min_htlc=int(edge["node2_policy"]["min_htlc"]) if edge["node2_policy"] else 0,
123-
node1_max_htlc=int(edge["node1_policy"]["max_htlc_msat"])
124-
if edge["node1_policy"]
125-
else 0,
126-
node2_max_htlc=int(edge["node2_policy"]["max_htlc_msat"])
127-
if edge["node2_policy"]
128-
else 0,
129-
node1_base_fee_msat=int(edge["node1_policy"]["fee_base_msat"])
130-
if edge["node1_policy"]
131-
else 0,
132-
node2_base_fee_msat=int(edge["node2_policy"]["fee_base_msat"])
133-
if edge["node2_policy"]
134-
else 0,
135-
node1_fee_rate_milli_msat=int(edge["node1_policy"]["fee_rate_milli_msat"])
136-
if edge["node1_policy"]
137-
else 0,
138-
node2_fee_rate_milli_msat=int(edge["node2_policy"]["fee_rate_milli_msat"])
139-
if edge["node2_policy"]
140-
else 0,
141-
node1_time_lock_delta=int(edge["node1_policy"]["time_lock_delta"])
142-
if edge["node1_policy"]
143-
else 0,
144-
node2_time_lock_delta=int(edge["node2_policy"]["time_lock_delta"])
145-
if edge["node2_policy"]
146-
else 0,
145+
node1_policy=node1_policy,
146+
node2_policy=node2_policy,
147147
)
148148

149149
def get_peers(self) -> list[str]:

0 commit comments

Comments
 (0)