1
1
import base64
2
2
import http .client
3
3
import json
4
+ import logging
4
5
import os
5
6
import ssl
6
7
from abc import ABC , abstractmethod
@@ -112,12 +113,14 @@ def to_lnd_chanpolicy(self, capacity):
112
113
113
114
class LNNode (ABC ):
114
115
@abstractmethod
115
- def __init__ (self , pod_name , logger = None ):
116
- self .log = logger
116
+ def __init__ (self , pod_name ):
117
117
self .name = pod_name
118
-
119
- def setLogger (self , logger ):
120
- self .log = logger
118
+ self .log = logging .getLogger (pod_name )
119
+ handler = logging .StreamHandler ()
120
+ formatter = logging .Formatter (f'%(name)-8s - %(levelname)s: %(message)s' )
121
+ handler .setFormatter (formatter )
122
+ self .log .addHandler (handler )
123
+ self .log .setLevel (logging .INFO )
121
124
122
125
@staticmethod
123
126
def param_dict_to_list (params : dict ) -> list [str ]:
@@ -164,11 +167,78 @@ def update(self, txid_hex: str, policy: dict, capacity: int) -> dict:
164
167
165
168
166
169
class CLN (LNNode ):
167
- def __init__ (self , pod_name , logger = None ):
168
- super ().__init__ (pod_name , logger )
170
+ def __init__ (self , pod_name ):
171
+ super ().__init__ (pod_name )
172
+ self .conn = http .client .HTTPSConnection (
173
+ host = pod_name , port = 8080 , timeout = 5 , context = INSECURE_CONTEXT
174
+ )
169
175
self .headers = {}
170
176
self .impl = "cln"
171
177
178
+ def reset_connection (self ):
179
+ self .conn = http .client .HTTPSConnection (
180
+ host = self .name , port = 3010 , timeout = 5 , context = INSECURE_CONTEXT
181
+ )
182
+
183
+ def setRune (self , rune ):
184
+ self .headers = {
185
+ "Rune" : rune
186
+ }
187
+
188
+ def get (self , uri ):
189
+ attempt = 0
190
+ while True :
191
+ try :
192
+ self .log .warning (f"headers: { self .headers } " )
193
+ self .conn .request (
194
+ method = "GET" ,
195
+ url = uri ,
196
+ headers = self .headers ,
197
+ )
198
+ return self .conn .getresponse ().read ().decode ("utf8" )
199
+ except Exception as e :
200
+ self .reset_connection ()
201
+ attempt += 1
202
+ if attempt > 5 :
203
+ self .log .error (f"Error CLN GET, Abort: { e } " )
204
+ return None
205
+ sleep (1 )
206
+
207
+ def post (self , uri , data = {}):
208
+ body = json .dumps (data )
209
+ post_header = self .headers
210
+ post_header ["Content-Length" ] = str (len (body ))
211
+ post_header ["Content-Type" ] = "application/json"
212
+ attempt = 0
213
+ while True :
214
+ try :
215
+ self .conn .request (
216
+ method = "POST" ,
217
+ url = uri ,
218
+ body = body ,
219
+ headers = post_header ,
220
+ )
221
+ # Stream output, otherwise we get a timeout error
222
+ res = self .conn .getresponse ()
223
+ stream = ""
224
+ while True :
225
+ try :
226
+ data = res .read (1 )
227
+ if len (data ) == 0 :
228
+ break
229
+ else :
230
+ stream += data .decode ("utf8" )
231
+ except Exception :
232
+ break
233
+ return stream
234
+ except Exception as e :
235
+ self .reset_connection ()
236
+ attempt += 1
237
+ if attempt > 5 :
238
+ self .log .error (f"Error CLN POST, Abort: { e } " )
239
+ return None
240
+ sleep (1 )
241
+
172
242
def rpc (
173
243
self ,
174
244
method : str ,
@@ -191,12 +261,26 @@ def rpc(
191
261
self .log .error (f"CLN rpc error: { e } , wait and retry..." )
192
262
sleep (2 )
193
263
return None
264
+
265
+ def createrune (self , max_tries = 2 ):
266
+ attempt = 0
267
+ while attempt < max_tries :
268
+ attempt += 1
269
+ response = self .rpc ("createrune" )
270
+ if not response :
271
+ sleep (2 )
272
+ continue
273
+ res = json .loads (response )
274
+ self .setRune (res ["rune" ])
275
+ return
276
+ raise Exception (f"Unable to fetch rune from { self .name } " )
194
277
195
278
def newaddress (self , max_tries = 2 ):
279
+ self .createrune ()
196
280
attempt = 0
197
281
while attempt < max_tries :
198
282
attempt += 1
199
- response = self .rpc ( " newaddr" )
283
+ response = self .post ( "/v1/ newaddr" )
200
284
if not response :
201
285
sleep (2 )
202
286
continue
@@ -211,7 +295,7 @@ def newaddress(self, max_tries=2):
211
295
return False , ""
212
296
213
297
def uri (self ):
214
- res = json .loads (self .rpc ( " getinfo" ))
298
+ res = json .loads (self .post ( "/v1/ getinfo" ))
215
299
if len (res ["address" ]) < 1 :
216
300
return None
217
301
return f"{ res ['id' ]} @{ res ['address' ][0 ]['address' ]} :{ res ['address' ][0 ]['port' ]} "
@@ -220,7 +304,7 @@ def walletbalance(self, max_tries=2) -> int:
220
304
attempt = 0
221
305
while attempt < max_tries :
222
306
attempt += 1
223
- response = self .rpc ( " listfunds" )
307
+ response = self .post ( "/v1/ listfunds" )
224
308
if not response :
225
309
sleep (2 )
226
310
continue
@@ -232,7 +316,7 @@ def channelbalance(self, max_tries=2) -> int:
232
316
attempt = 0
233
317
while attempt < max_tries :
234
318
attempt += 1
235
- response = self .rpc ( " listfunds" )
319
+ response = self .post ( "/v1/ listfunds" )
236
320
if not response :
237
321
sleep (2 )
238
322
continue
@@ -244,7 +328,7 @@ def connect(self, target_uri, max_tries=5) -> dict:
244
328
attempt = 0
245
329
while attempt < max_tries :
246
330
attempt += 1
247
- response = self .rpc ( " connect" , [ target_uri ] )
331
+ response = self .post ( "/v1/ connect" , { "id" : target_uri } )
248
332
if response :
249
333
res = json .loads (response )
250
334
if "id" in res :
@@ -269,7 +353,7 @@ def channel(self, pk, capacity, push_amt, fee_rate, max_tries=5) -> dict:
269
353
attempt = 0
270
354
while attempt < max_tries :
271
355
attempt += 1
272
- response = self .rpc ( " fundchannel" , self . param_dict_to_list ( data ) )
356
+ response = self .post ( "/v1/ fundchannel" , data )
273
357
if response :
274
358
res = json .loads (response )
275
359
if "txid" in res :
@@ -283,14 +367,14 @@ def channel(self, pk, capacity, push_amt, fee_rate, max_tries=5) -> dict:
283
367
return None
284
368
285
369
def createinvoice (self , sats , label , description = "new invoice" ) -> str :
286
- response = self .rpc ("invoice" , [ sats * 1000 , label , description ] )
370
+ response = self .post ("invoice" , { "amount_msat" : sats * 1000 , " label" : label , " description" : description } )
287
371
if response :
288
372
res = json .loads (response )
289
373
return res ["bolt11" ]
290
374
return None
291
375
292
376
def payinvoice (self , payment_request ) -> str :
293
- response = self .rpc ("pay" , [ payment_request ] )
377
+ response = self .rpc ("pay" , { "bolt11" : payment_request } )
294
378
if response :
295
379
res = json .loads (response )
296
380
if "code" in res :
@@ -303,7 +387,7 @@ def graph(self, max_tries=2) -> dict:
303
387
attempt = 0
304
388
while attempt < max_tries :
305
389
attempt += 1
306
- response = self .rpc ( " listchannels" )
390
+ response = self .post ( "/v1/ listchannels" )
307
391
if response :
308
392
res = json .loads (response )
309
393
if "channels" in res :
@@ -329,8 +413,8 @@ def update(self, txid_hex: str, policy: dict, capacity: int, max_tries=2) -> dic
329
413
330
414
331
415
class LND (LNNode ):
332
- def __init__ (self , pod_name , logger = None ):
333
- super ().__init__ (pod_name , logger )
416
+ def __init__ (self , pod_name ):
417
+ super ().__init__ (pod_name )
334
418
self .conn = http .client .HTTPSConnection (
335
419
host = pod_name , port = 8080 , timeout = 5 , context = INSECURE_CONTEXT
336
420
)
0 commit comments