-
Notifications
You must be signed in to change notification settings - Fork 31
/
lnd.py
233 lines (192 loc) · 8.04 KB
/
lnd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
from binascii import hexlify
from lnaddr import lndecode
from utils import TailableProc, BITCOIND_CONFIG
import rpc_pb2_grpc as lnrpc_grpc
import rpc_pb2 as lnrpc
from ephemeral_port_reserve import reserve
import grpc
import logging
import os
import time
import codecs
# Needed for grpc to negotiate a valid cipher suite
os.environ["GRPC_SSL_CIPHER_SUITES"] = "ECDHE-ECDSA-AES256-GCM-SHA384"
class LndD(TailableProc):
CONF_NAME = 'lnd.conf'
def __init__(self, lightning_dir, bitcoind, port):
super().__init__(lightning_dir, 'lnd({})'.format(port))
self.lightning_dir = lightning_dir
self.bitcoind = bitcoind
self.port = port
self.rpc_port = str(reserve())
self.rest_port = str(reserve())
self.prefix = 'lnd'
self.cmd_line = [
'bin/lnd',
'--bitcoin.active',
'--bitcoin.regtest',
'--datadir={}'.format(lightning_dir),
'--debuglevel=trace',
'--rpclisten=127.0.0.1:{}'.format(self.rpc_port),
'--restlisten=127.0.0.1:{}'.format(self.rest_port),
'--listen=127.0.0.1:{}'.format(self.port),
'--tlscertpath=tls.cert',
'--tlskeypath=tls.key',
'--bitcoin.node=bitcoind',
'--bitcoind.rpchost=127.0.0.1:{}'.format(BITCOIND_CONFIG.get('rpcport', 18332)),
'--bitcoind.rpcuser=rpcuser',
'--bitcoind.rpcpass=rpcpass',
'--bitcoind.zmqpubrawblock=tcp://127.0.0.1:{}'.format(self.bitcoind.zmqpubrawblock_port),
'--bitcoind.zmqpubrawtx=tcp://127.0.0.1:{}'.format(self.bitcoind.zmqpubrawtx_port),
'--configfile={}'.format(os.path.join(lightning_dir, self.CONF_NAME)),
'--no-macaroons',
'--nobootstrap',
'--noseedbackup',
'--trickledelay=500'
]
if not os.path.exists(lightning_dir):
os.makedirs(lightning_dir)
with open(os.path.join(lightning_dir, self.CONF_NAME), "w") as f:
f.write("""[Application Options]\n""")
def start(self):
super().start()
self.wait_for_log('RPC server listening on')
self.wait_for_log('Done catching up block hashes')
time.sleep(5)
logging.info('LND started (pid: {})'.format(self.proc.pid))
def stop(self):
self.proc.terminate()
time.sleep(3)
if self.proc.poll() is None:
self.proc.kill()
self.proc.wait()
super().save_log()
class LndNode(object):
displayName = 'lnd'
def __init__(self, lightning_dir, lightning_port, bitcoind, executor=None, node_id=0):
self.bitcoin = bitcoind
self.executor = executor
self.daemon = LndD(lightning_dir, bitcoind, port=lightning_port)
self.rpc = LndRpc(self.daemon.rpc_port)
self.logger = logging.getLogger('lnd-node({})'.format(lightning_port))
self.myid = None
self.node_id = node_id
def id(self):
if not self.myid:
self.myid = self.info()['id']
return self.myid
def ping(self):
""" Simple liveness test to see if the node is up and running
Returns true if the node is reachable via RPC, false otherwise.
"""
try:
self.rpc.stub.GetInfo(lnrpc.GetInfoRequest())
return True
except Exception as e:
print(e)
return False
def peers(self):
peers = self.rpc.stub.ListPeers(lnrpc.ListPeersRequest()).peers
return [p.pub_key for p in peers]
def check_channel(self, remote):
""" Make sure that we have an active channel with remote
"""
self_id = self.id()
remote_id = remote.id()
channels = self.rpc.stub.ListChannels(lnrpc.ListChannelsRequest()).channels
channel_by_remote = {c.remote_pubkey: c for c in channels}
if remote_id not in channel_by_remote:
self.logger.warning("Channel {} -> {} not found".format(self_id, remote_id))
return False
channel = channel_by_remote[remote_id]
self.logger.debug("Channel {} -> {} state: {}".format(self_id, remote_id, channel))
return channel.active
def addfunds(self, bitcoind, satoshis):
req = lnrpc.NewAddressRequest(type=1)
addr = self.rpc.stub.NewAddress(req).address
btc_addr = bitcoind.rpc.getnewaddress()
bitcoind.rpc.sendtoaddress(addr, float(satoshis) / 10**8)
self.daemon.wait_for_log("Inserting unconfirmed transaction")
bitcoind.rpc.generatetoaddress(1, btc_addr)
self.daemon.wait_for_log("Marking unconfirmed transaction")
# The above still doesn't mean the wallet balance is updated,
# so let it settle a bit
i = 0
while self.rpc.stub.WalletBalance(lnrpc.WalletBalanceRequest()).total_balance == satoshis and i < 30:
time.sleep(1)
i += 1
assert(self.rpc.stub.WalletBalance(lnrpc.WalletBalanceRequest()).total_balance == satoshis)
def openchannel(self, node_id, host, port, satoshis):
peers = self.rpc.stub.ListPeers(lnrpc.ListPeersRequest()).peers
peers_by_pubkey = {p.pub_key: p for p in peers}
if node_id not in peers_by_pubkey:
raise ValueError("Could not find peer {} in peers {}".format(node_id, peers))
peer = peers_by_pubkey[node_id]
self.rpc.stub.OpenChannel(lnrpc.OpenChannelRequest(
node_pubkey=codecs.decode(peer.pub_key, 'hex_codec'),
local_funding_amount=satoshis,
push_sat=0
))
# Somehow broadcasting a tx is slow from time to time
time.sleep(5)
def getchannels(self):
req = lnrpc.ChannelGraphRequest()
rep = self.rpc.stub.DescribeGraph(req)
channels = []
for e in rep.edges:
channels.append((e.node1_pub, e.node2_pub))
channels.append((e.node2_pub, e.node1_pub))
return channels
def getnodes(self):
req = lnrpc.ChannelGraphRequest()
rep = self.rpc.stub.DescribeGraph(req)
nodes = set([n.pub_key for n in rep.nodes]) - set([self.id()])
return nodes
def invoice(self, amount):
req = lnrpc.Invoice(value=int(amount/1000))
rep = self.rpc.stub.AddInvoice(req)
return rep.payment_request
def send(self, bolt11):
req = lnrpc.SendRequest(payment_request=bolt11)
res = self.rpc.stub.SendPaymentSync(req)
if res.payment_error:
raise ValueError(res.payment_error)
return hexlify(res.payment_preimage)
def connect(self, host, port, node_id):
addr = lnrpc.LightningAddress(pubkey=node_id, host="{}:{}".format(host, port))
req = lnrpc.ConnectPeerRequest(addr=addr, perm=True)
logging.debug(self.rpc.stub.ConnectPeer(req))
def info(self):
r = self.rpc.stub.GetInfo(lnrpc.GetInfoRequest())
return {
'id': r.identity_pubkey,
'blockheight': r.block_height,
}
def block_sync(self, blockhash):
print("Waiting for node to learn about", blockhash)
self.daemon.wait_for_log('NTFN: New block: height=([0-9]+), sha={}'.format(blockhash))
def restart(self):
self.daemon.stop()
time.sleep(5)
self.daemon.start()
self.rpc = LndRpc(self.daemon.rpc_port)
def stop(self):
self.daemon.stop()
def start(self):
self.daemon.start()
self.rpc = LndRpc(self.daemon.rpc_port)
def check_route(self, node_id, amount):
try:
req = lnrpc.QueryRoutesRequest(pub_key=node_id, amt=int(amount/1000), num_routes=1)
r = self.rpc.stub.QueryRoutes(req)
except grpc._channel._Rendezvous as e:
if (str(e).find("unable to find a path to destination") > 0):
return False
raise
return True
class LndRpc(object):
def __init__(self, rpc_port):
self.port = rpc_port
cred = grpc.ssl_channel_credentials(open('tls.cert', 'rb').read())
channel = grpc.secure_channel('localhost:{}'.format(rpc_port), cred)
self.stub = lnrpc_grpc.LightningStub(channel)