Skip to content

Commit fac8d11

Browse files
committed
store channel data from network.yaml in configmaps to open channels
1 parent e9dfabe commit fac8d11

File tree

9 files changed

+182
-57
lines changed

9 files changed

+182
-57
lines changed

resources/charts/bitcoincore/charts/lnd/templates/configmap.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,15 @@ data:
1717
alias={{ include "lnd.fullname" . }}
1818
externalhosts={{ include "lnd.fullname" . }}
1919
tlsextradomain={{ include "lnd.fullname" . }}
20+
---
21+
apiVersion: v1
22+
kind: ConfigMap
23+
metadata:
24+
name: {{ include "lnd.fullname" . }}-channels
25+
labels:
26+
channels: "true"
27+
{{- include "lnd.labels" . | nindent 4 }}
28+
data:
29+
source: {{ include "lnd.fullname" . }}
30+
channels: |
31+
{{ .Values.channels | toJson }}

resources/charts/bitcoincore/charts/lnd/templates/pod.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ metadata:
44
name: {{ include "lnd.fullname" . }}
55
labels:
66
{{- include "lnd.labels" . | nindent 4 }}
7-
{{- with .Values.extraLabels }}
8-
{{- toYaml . | nindent 4 }}
9-
{{- end }}
107
{{- with .Values.podLabels }}
118
{{- toYaml . | nindent 4 }}
129
{{- end }}

resources/charts/bitcoincore/charts/lnd/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,4 @@ config: ""
118118

119119
defaultConfig: ""
120120

121-
extraLabels: {}
121+
channels: []

src/warnet/deploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def deploy_network(directory: Path, debug: bool = False, namespace: Optional[str
243243
try:
244244
temp_override_file_path = ""
245245
node_name = node.get("name")
246-
node_config_override = {k: v for k, v in node.items() if k != "name" and k != "lnd"}
246+
node_config_override = {k: v for k, v in node.items() if k != "name"}
247247

248248
cmd = f"{HELM_COMMAND} {node_name} {BITCOIN_CHART_LOCATION} --namespace {namespace} -f {defaults_file_path}"
249249
if debug:

src/warnet/k8s.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,19 @@ def get_pod_exit_status(pod_name, namespace: Optional[str] = None):
8383
return None
8484

8585

86-
def get_edges(namespace: Optional[str] = None) -> any:
86+
def get_channels(namespace: Optional[str] = None) -> any:
8787
namespace = get_default_namespace_or(namespace)
8888
sclient = get_static_client()
89-
configmap = sclient.read_namespaced_config_map(name="edges", namespace=namespace)
90-
return json.loads(configmap.data["data"])
89+
config_maps = sclient.list_namespaced_config_map(
90+
namespace=namespace, label_selector="channels=true"
91+
)
92+
channels = []
93+
for cm in config_maps.items:
94+
channel_jsons = json.loads(cm.data["channels"])
95+
for channel_json in channel_jsons:
96+
channel_json["source"] = cm.data["source"]
97+
channels.append(channel_json)
98+
return channels
9199

92100

93101
def create_kubernetes_object(

src/warnet/ln.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
import click
55

6-
from .k8s import get_default_namespace_or, get_pod
6+
from .k8s import (
7+
get_channels,
8+
get_default_namespace_or,
9+
get_pod,
10+
)
711
from .process import run_command
812

913

@@ -25,8 +29,6 @@ def rpc(pod: str, method: str, params: str, namespace: Optional[str]):
2529

2630

2731
def _rpc(pod_name: str, method: str, params: str = "", namespace: Optional[str] = None):
28-
# TODO: when we add back cln we'll need to describe the pod,
29-
# get a label with implementation type and then adjust command
3032
pod = get_pod(pod_name)
3133
namespace = get_default_namespace_or(namespace)
3234
chain = pod.metadata.labels["chain"]
@@ -42,9 +44,12 @@ def pubkey(
4244
"""
4345
Get lightning node pub key from <ln pod name>
4446
"""
45-
# TODO: again here, cln will need a different command
47+
print(_pubkey(pod))
48+
49+
50+
def _pubkey(pod: str):
4651
info = _rpc(pod, "getinfo")
47-
print(json.loads(info)["identity_pubkey"])
52+
return json.loads(info)["identity_pubkey"]
4853

4954

5055
@ln.command()
@@ -55,8 +60,40 @@ def host(
5560
"""
5661
Get lightning node host from <ln pod name>
5762
"""
58-
# TODO: again here, cln will need a different command
63+
print(_host(pod))
64+
65+
66+
def _host(pod):
5967
info = _rpc(pod, "getinfo")
6068
uris = json.loads(info)["uris"]
6169
if uris and len(uris) >= 0:
62-
print(uris[0].split("@")[1])
70+
return uris[0].split("@")[1]
71+
else:
72+
return ""
73+
74+
75+
@ln.command()
76+
def open_all_channels():
77+
"""
78+
Open all channels with source policies defined in the network.yaml
79+
<!> IGNORES HARD CODED CHANNEL IDs <!>
80+
<!> Should only be run once or you'll end up with duplicate channels <!>
81+
"""
82+
channels = get_channels()
83+
commands = []
84+
for ch in channels:
85+
pk = _pubkey(ch["target"])
86+
host = _host(ch["target"])
87+
local_amt = ch["local_amt"]
88+
push_amt = ch.get("push_amt", 0)
89+
assert pk, f"{ch['target']} has no public key"
90+
assert host, f"{ch['target']} has no host"
91+
assert local_amt, "Channel has no local_amount"
92+
commands.append(
93+
(
94+
ch["source"],
95+
f"openchannel --node_key {pk} --connect {host} --local_amt {local_amt} --push_amt {push_amt}",
96+
)
97+
)
98+
for command in commands:
99+
_rpc(*command)

test/data/ln/network.yaml

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,47 @@ nodes:
1515
addnode:
1616
- tank-0000
1717
ln:
18-
lnd: true
18+
lnd: true
19+
20+
- name: tank-0003
21+
addnode:
22+
- tank-0000
23+
ln:
24+
lnd: true
25+
lnd:
26+
config: |
27+
bitcoin.timelockdelta=33
28+
channels:
29+
- id:
30+
block: 300
31+
index: 1
32+
target: tank-0004-ln
33+
local_amt: 100000
34+
push_amt: 50000
35+
36+
- name: tank-0004
37+
addnode:
38+
- tank-0000
39+
ln:
40+
lnd: true
41+
lnd:
42+
channels:
43+
- id:
44+
block: 300
45+
index: 2
46+
target: tank-0005-ln
47+
local_amt: 50000
48+
push_amt: 25000
49+
50+
- name: tank-0005
51+
addnode:
52+
- tank-0000
53+
ln:
54+
lnd: true
55+
lnd:
56+
channels:
57+
- id:
58+
block: 301
59+
index: 1
60+
target: tank-0000-ln
61+
local_amt: 25000

test/data/ln/node-defaults.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ image:
22
repository: bitcoindevproject/bitcoin
33
pullPolicy: IfNotPresent
44
tag: "27.0"
5+
6+
lnd:
7+
defaultConfig: |
8+
color=#000000

test/ln_basic_test.py

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,33 @@ class LNBasicTest(TestBase):
1212
def __init__(self):
1313
super().__init__()
1414
self.network_dir = Path(os.path.dirname(__file__)) / "data" / "ln"
15-
self.miner_addr = ""
15+
self.lns = [
16+
"tank-0000-ln",
17+
"tank-0001-ln",
18+
"tank-0002-ln",
19+
"tank-0003-ln",
20+
"tank-0004-ln",
21+
"tank-0005-ln",
22+
]
1623

1724
def run_test(self):
1825
try:
26+
# Wait for all nodes to wake up
1927
self.setup_network()
28+
# Send money to all LN nodes
2029
self.fund_wallets()
30+
31+
# Manually open two channels between first three nodes
32+
# and send a payment
2133
self.manual_open_channels()
22-
self.wait_for_gossip_sync()
23-
self.pay_invoice()
34+
self.wait_for_gossip_sync(self.lns[:3], 2)
35+
self.pay_invoice(sender="tank-0000-ln", recipient="tank-0002-ln")
36+
37+
# Automatically open channels from network.yaml
38+
self.automatic_open_channels()
39+
self.wait_for_gossip_sync(self.lns[3:], 3)
40+
# push_amt should enable payments from target to source
41+
self.pay_invoice(sender="tank-0005-ln", recipient="tank-0003-ln")
2442
finally:
2543
self.cleanup()
2644

@@ -29,43 +47,41 @@ def setup_network(self):
2947
self.log.info(self.warnet(f"deploy {self.network_dir}"))
3048
self.wait_for_all_tanks_status(target="running")
3149

50+
self.warnet("bitcoin rpc tank-0000 createwallet miner")
51+
self.warnet("bitcoin rpc tank-0000 -generate 110")
52+
self.wait_for_predicate(
53+
lambda: int(self.warnet("bitcoin rpc tank-0000 getblockcount")) > 100
54+
)
55+
3256
def wait_for_all_ln_rpc():
33-
nodes = ["tank-0000-ln", "tank-0001-ln", "tank-0002-ln"]
34-
for node in nodes:
57+
for ln in self.lns:
3558
try:
36-
self.warnet(f"ln rpc {node} getinfo")
59+
self.warnet(f"ln rpc {ln} getinfo")
3760
except Exception:
38-
print(f"LN node {node} not ready for rpc yet")
61+
print(f"LN node {ln} not ready for rpc yet")
3962
return False
4063
return True
4164

4265
self.wait_for_predicate(wait_for_all_ln_rpc)
4366

4467
def fund_wallets(self):
45-
self.warnet("bitcoin rpc tank-0000 createwallet miner")
46-
self.warnet("bitcoin rpc tank-0000 -generate 110")
47-
self.wait_for_predicate(
48-
lambda: int(self.warnet("bitcoin rpc tank-0000 getblockcount")) > 100
49-
)
50-
51-
addrs = []
52-
for lnd in ["tank-0000-ln", "tank-0001-ln", "tank-0002-ln"]:
53-
addrs.append(json.loads(self.warnet(f"ln rpc {lnd} newaddress p2wkh"))["address"])
54-
55-
self.warnet(
56-
"bitcoin rpc tank-0000 sendmany '' '{"
57-
+ f'"{addrs[0]}":10,"{addrs[1]}":10,"{addrs[2]}":10'
58-
+ "}'"
59-
)
68+
outputs = ""
69+
for lnd in self.lns:
70+
addr = json.loads(self.warnet(f"ln rpc {lnd} newaddress p2wkh"))["address"]
71+
outputs += f',"{addr}":10'
72+
# trim first comma
73+
outputs = outputs[1:]
74+
75+
self.warnet("bitcoin rpc tank-0000 sendmany '' '{" + outputs + "}'")
6076
self.warnet("bitcoin rpc tank-0000 -generate 1")
6177

6278
def manual_open_channels(self):
6379
# 0 -> 1 -> 2
6480
pk1 = self.warnet("ln pubkey tank-0001-ln")
6581
pk2 = self.warnet("ln pubkey tank-0002-ln")
6682

67-
host1 = None
68-
host2 = None
83+
host1 = ""
84+
host2 = ""
6985

7086
while not host1 or not host2:
7187
if not host1:
@@ -92,28 +108,36 @@ def wait_for_two_txs():
92108

93109
self.warnet("bitcoin rpc tank-0000 -generate 10")
94110

95-
def wait_for_gossip_sync(self):
96-
chs0 = []
97-
chs1 = []
98-
chs2 = []
99-
100-
while len(chs0) != 2 or len(chs1) != 2 or len(chs2) != 2:
101-
if len(chs0) != 2:
102-
chs0 = json.loads(self.warnet("ln rpc tank-0000-ln describegraph"))["edges"]
103-
if len(chs1) != 2:
104-
chs1 = json.loads(self.warnet("ln rpc tank-0001-ln describegraph"))["edges"]
105-
if len(chs2) != 2:
106-
chs2 = json.loads(self.warnet("ln rpc tank-0002-ln describegraph"))["edges"]
111+
def wait_for_gossip_sync(self, nodes, expected):
112+
while len(nodes) > 0:
113+
for node in nodes:
114+
chs = json.loads(self.warnet(f"ln rpc {node} describegraph"))["edges"]
115+
if len(chs) >= expected:
116+
nodes.remove(node)
107117
sleep(1)
108118

109-
def pay_invoice(self):
110-
inv = json.loads(self.warnet("ln rpc tank-0002-ln addinvoice --amt 1000"))
119+
def pay_invoice(self, sender: str, recipient: str):
120+
init_balance = int(json.loads(self.warnet(f"ln rpc {recipient} channelbalance"))["balance"])
121+
inv = json.loads(self.warnet(f"ln rpc {recipient} addinvoice --amt 1000"))
111122
print(inv)
112-
print(self.warnet(f"ln rpc tank-0000-ln payinvoice -f {inv['payment_request']}"))
123+
print(self.warnet(f"ln rpc {sender} payinvoice -f {inv['payment_request']}"))
113124

114125
def wait_for_success():
115-
return json.loads(self.warnet("ln rpc tank-0002-ln channelbalance"))["balance"] == 1000
116-
self.wait_for_predicate(wait_for_success)
126+
return (
127+
int(json.loads(self.warnet(f"ln rpc {recipient} channelbalance"))["balance"])
128+
== init_balance + 1000
129+
)
130+
131+
self.wait_for_predicate(wait_for_success)
132+
133+
def automatic_open_channels(self):
134+
self.warnet("ln open-all-channels")
135+
136+
def wait_for_three_txs():
137+
return json.loads(self.warnet("bitcoin rpc tank-0000 getmempoolinfo"))["size"] == 3
138+
139+
self.wait_for_predicate(wait_for_three_txs)
140+
self.warnet("bitcoin rpc tank-0000 -generate 10")
117141

118142

119143
if __name__ == "__main__":

0 commit comments

Comments
 (0)