Skip to content

Commit 71e217c

Browse files
committed
ln: open rest api and ensure scenarios can open channels
1 parent 2406805 commit 71e217c

File tree

6 files changed

+133
-18
lines changed

6 files changed

+133
-18
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,9 @@ spec:
1616
targetPort: p2p
1717
protocol: TCP
1818
name: p2p
19+
- port: {{ .Values.RestPort }}
20+
targetPort: rest
21+
protocol: TCP
22+
name: rest
1923
selector:
2024
{{- include "lnd.selectorLabels" . | nindent 4 }}

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

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ affinity: {}
115115

116116
baseConfig: |
117117
norest=false
118+
restlisten=0.0.0.0:8080
118119
debuglevel=debug
119120
accept-keysend=true
120121
bitcoin.active=true

resources/scenarios/commander.py

+59
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import argparse
2+
import base64
23
import configparser
4+
import http.client
35
import json
46
import logging
57
import os
68
import pathlib
79
import random
810
import signal
11+
import ssl
912
import sys
1013
import tempfile
1114
from typing import Dict
@@ -22,6 +25,13 @@
2225

2326
WARNET_FILE = "/shared/warnet.json"
2427

28+
# hard-coded deterministic lnd credentials
29+
ADMIN_MACAROON_HEX = "0201036c6e6402f801030a1062beabbf2a614b112128afa0c0b4fdd61201301a160a0761646472657373120472656164120577726974651a130a04696e666f120472656164120577726974651a170a08696e766f69636573120472656164120577726974651a210a086d616361726f6f6e120867656e6572617465120472656164120577726974651a160a076d657373616765120472656164120577726974651a170a086f6666636861696e120472656164120577726974651a160a076f6e636861696e120472656164120577726974651a140a057065657273120472656164120577726974651a180a067369676e6572120867656e657261746512047265616400000620b17be53e367290871681055d0de15587f6d1cd47d1248fe2662ae27f62cfbdc6"
30+
# Don't worry about lnd's self-signed certificates
31+
INSECURE_CONTEXT = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
32+
INSECURE_CONTEXT.check_hostname = False
33+
INSECURE_CONTEXT.verify_mode = ssl.CERT_NONE
34+
2535
try:
2636
with open(WARNET_FILE) as file:
2737
WARNET = json.load(file)
@@ -39,6 +49,45 @@ def auth_proxy_request(self, method, path, postdata):
3949
AuthServiceProxy._request = auth_proxy_request
4050

4151

52+
class LND:
53+
def __init__(self, tank_name):
54+
self.conn = http.client.HTTPSConnection(
55+
host=f"{tank_name}-ln", port=8080, timeout=5, context=INSECURE_CONTEXT
56+
)
57+
58+
def get(self, uri):
59+
self.conn.request(
60+
method="GET", url=uri, headers={"Grpc-Metadata-macaroon": ADMIN_MACAROON_HEX}
61+
)
62+
return self.conn.getresponse().read().decode("utf8")
63+
64+
def post(self, uri, data):
65+
body = json.dumps(data)
66+
self.conn.request(
67+
method="POST",
68+
url=uri,
69+
body=body,
70+
headers={
71+
"Content-Type": "application/json",
72+
"Content-Length": str(len(body)),
73+
"Grpc-Metadata-macaroon": ADMIN_MACAROON_HEX,
74+
},
75+
)
76+
# Stream output, otherwise we get a timeout error
77+
res = self.conn.getresponse()
78+
stream = ""
79+
while True:
80+
try:
81+
data = res.read(1)
82+
if len(data) == 0:
83+
break
84+
else:
85+
stream += data.decode("utf8")
86+
except Exception:
87+
break
88+
return stream
89+
90+
4291
class Commander(BitcoinTestFramework):
4392
# required by subclasses of BitcoinTestFramework
4493
def set_test_params(self):
@@ -55,6 +104,10 @@ def ensure_miner(node):
55104
node.createwallet("miner", descriptors=True)
56105
return node.get_wallet_rpc("miner")
57106

107+
@staticmethod
108+
def hex_to_b64(hex):
109+
return base64.b64encode(bytes.fromhex(hex)).decode()
110+
58111
def handle_sigterm(self, signum, frame):
59112
print("SIGTERM received, stopping...")
60113
self.shutdown()
@@ -108,6 +161,12 @@ def setup(self):
108161
)
109162
node.rpc_connected = True
110163
node.init_peers = tank["init_peers"]
164+
165+
# Tank might not even have an ln node, that's
166+
# not our problem, it'll just 404 if scenario tries
167+
# to connect to it
168+
node.lnd = LND(tank["tank"])
169+
111170
self.nodes.append(node)
112171
self.tanks[tank["tank"]] = node
113172

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
5+
from commander import Commander
6+
7+
8+
class LNBasic(Commander):
9+
def set_test_params(self):
10+
self.num_nodes = None
11+
12+
def add_options(self, parser):
13+
parser.description = "Open a channel between two LN nodes using REST + macaroon"
14+
parser.usage = "warnet run /path/to/ln_init.py"
15+
16+
def run_test(self):
17+
info = json.loads(self.tanks["tank-0003"].lnd.get("/v1/getinfo"))
18+
uri = info["uris"][0]
19+
pk3, host = uri.split("@")
20+
21+
print(
22+
self.tanks["tank-0002"].lnd.post(
23+
"/v1/peers", data={"addr": {"pubkey": pk3, "host": host}}
24+
)
25+
)
26+
27+
print(
28+
self.tanks["tank-0002"].lnd.post(
29+
"/v1/channels/stream",
30+
data={"local_funding_amount": 100000, "node_pubkey": self.hex_to_b64(pk3)},
31+
)
32+
)
33+
34+
# Mine it ourself
35+
self.wait_until(lambda: self.tanks["tank-0002"].getmempoolinfo()["size"] == 1)
36+
print(self.tanks["tank-0002"].generate(5, invalid_call=False))
37+
38+
39+
def main():
40+
LNBasic().main()
41+
42+
43+
if __name__ == "__main__":
44+
main()

test/data/ln/network.yaml

+1-8
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,4 @@ nodes:
5151
addnode:
5252
- tank-0000
5353
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
54+
lnd: true

test/ln_basic_test.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class LNBasicTest(TestBase):
1212
def __init__(self):
1313
super().__init__()
1414
self.network_dir = Path(os.path.dirname(__file__)) / "data" / "ln"
15+
self.scen_dir = Path(os.path.dirname(__file__)).parent / "resources" / "scenarios"
1516
self.lns = [
1617
"tank-0000-ln",
1718
"tank-0001-ln",
@@ -29,16 +30,21 @@ def run_test(self):
2930
self.fund_wallets()
3031

3132
# Manually open two channels between first three nodes
32-
# and send a payment
33+
# and send a payment using warnet RPC
3334
self.manual_open_channels()
3435
self.wait_for_gossip_sync(self.lns[:3], 2)
3536
self.pay_invoice(sender="tank-0000-ln", recipient="tank-0002-ln")
3637

37-
# Automatically open channels from network.yaml
38+
# Automatically open channels from network.yaml using warnet RPC
3839
self.automatic_open_channels()
39-
self.wait_for_gossip_sync(self.lns[3:], 3)
40+
self.wait_for_gossip_sync(self.lns[3:], 2)
4041
# push_amt should enable payments from target to source
4142
self.pay_invoice(sender="tank-0005-ln", recipient="tank-0003-ln")
43+
44+
# Automatically open channels from inside a scenario commander
45+
self.scenario_open_channels()
46+
self.pay_invoice(sender="tank-0002-ln", recipient="tank-0003-ln")
47+
4248
finally:
4349
self.cleanup()
4450

@@ -75,6 +81,11 @@ def fund_wallets(self):
7581
self.warnet("bitcoin rpc tank-0000 sendmany '' '{" + outputs + "}'")
7682
self.warnet("bitcoin rpc tank-0000 -generate 1")
7783

84+
def wait_for_two_txs(self):
85+
self.wait_for_predicate(
86+
lambda: json.loads(self.warnet("bitcoin rpc tank-0000 getmempoolinfo"))["size"] == 2
87+
)
88+
7889
def manual_open_channels(self):
7990
# 0 -> 1 -> 2
8091
pk1 = self.warnet("ln pubkey tank-0001-ln")
@@ -101,10 +112,7 @@ def manual_open_channels(self):
101112
)
102113
)
103114

104-
def wait_for_two_txs():
105-
return json.loads(self.warnet("bitcoin rpc tank-0000 getmempoolinfo"))["size"] == 2
106-
107-
self.wait_for_predicate(wait_for_two_txs)
115+
self.wait_for_two_txs()
108116

109117
self.warnet("bitcoin rpc tank-0000 -generate 10")
110118

@@ -131,14 +139,20 @@ def wait_for_success():
131139
self.wait_for_predicate(wait_for_success)
132140

133141
def automatic_open_channels(self):
142+
# 3 -> 4 -> 5
134143
self.warnet("ln open-all-channels")
135144

136-
def wait_for_three_txs():
137-
return json.loads(self.warnet("bitcoin rpc tank-0000 getmempoolinfo"))["size"] == 3
145+
self.wait_for_two_txs()
138146

139-
self.wait_for_predicate(wait_for_three_txs)
140147
self.warnet("bitcoin rpc tank-0000 -generate 10")
141148

149+
def scenario_open_channels(self):
150+
# 2 -> 3
151+
# connecting all six ln nodes in the graph
152+
scenario_file = self.scen_dir / "test_scenarios" / "ln_basic.py"
153+
self.log.info(f"Running scenario from: {scenario_file}")
154+
self.warnet(f"run {scenario_file} --source_dir={self.scen_dir} --debug")
155+
142156

143157
if __name__ == "__main__":
144158
test = LNBasicTest()

0 commit comments

Comments
 (0)