def __init__(self, lnddatadir): super().__init__('TIThread', 'TipperInvoiceThread') config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.sfn = boto3.client('stepfunctions', config=config, region_name='us-west-2') self.lnd = Client(lnddatadir)
def run(self): self.logger.info('Starting Thread...') while True: try: self.logger.info('Listening for ' + binascii.hexlify(self.hash).decode()) invoices = self.lnd.subscribeSingleInvoice(self.hash) for invoice in invoices: self.logger.info(str(invoice)) self.logger.info({ 'memo': invoice.memo, 'r_hash': binascii.hexlify(invoice.r_hash).decode() }) sleep(1) except grpc._channel._Rendezvous as e: self.logger.error('LND appears to be down...') self.lnd = Client() sleep(60) except Exception as e: self.logger.error('Error type: {}'.format(type(e))) self.logger.info('{}\n\n{}'.format(e, traceback.format_exc())) sleep(60)
def __init__(self, preimage, hash): super().__init__() self.lnd = Client() config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.lamb = boto3.client('lambda', config=config, region_name='us-west-2') self.hash = hash self.preimage = preimage self.logger = logging.getLogger(name='SingleInvoiceListener') self.start()
class TipperInvoiceThread(CommonThread): def __init__(self, lnddatadir): super().__init__('TIThread', 'TipperInvoiceThread') config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.sfn = boto3.client('stepfunctions', config=config, region_name='us-west-2') self.lnd = Client(lnddatadir) def tryRun(self): super().tryRun() # Make sure lnd is active before getting a task self.lnd.getInfo() response = self.sfn.get_activity_task( activityArn= 'arn:aws:states:us-west-2:434623153115:activity:CdkStackgetTipperInvoiceActivity1238A05D', workerName='LNTipServer') if 'taskToken' in response and 'input' in response: try: token = response['taskToken'] data = json.loads(response['input']) self.logger.info(data) if data['type'] == 'HodlTip': preimage = os.urandom(32) hash = hashlib.sha256(preimage) data['preimage'] = binascii.hexlify(preimage).decode() data['hash'] = hash.hexdigest() data['tipperInvoice'] = self.lnd.requestHoldInvoice( int(data['amount']), hash.digest(), 172800).payment_request SingleInvoiceListener(preimage, hash.digest()) else: data['tipperInvoice'] = self.lnd.requestInvoice( int(data['amount']), data['id']).payment_request self.sfn.send_task_success( taskToken=token, output=json.dumps(data), ) except Exception as e: self.logger.error('Payment failed with {} {}'.format( type(e), e)) self.sfn.send_task_failure(taskToken=token, error="Failed", cause=str(e)) with self.cond: self.cond.wait(5)
def __init__(self, datadir, lnddatadir): super().__init__('ISThread', 'InvoiceSubscriptionThread') self.lnd = Client(lnddatadir) config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.lamb = boto3.client('lambda', config=config, region_name='us-west-2') self.configPath = os.path.join(datadir, "config.json") if not os.path.isfile(self.configPath): self.logger.info( 'Invoice subscription configuration not found, recreating with settleIndex 0' ) json.dump({'settleIndex': 0}, open(self.configPath, 'w')) self.config = json.load(open(self.configPath))
def __init__(self, lnddatadir, btcdatadir): super().__init__('MetricThread', 'MetricThread') self.lnd = Client(lnddatadir) config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.cw = boto3.client('cloudwatch', config=config, region_name='us-west-2') with open(os.path.join(btcdatadir, 'bitcoin.conf'), 'r') as f: configLines = f.read().splitlines() self.btcConfig = { 'rpcuser': next(filter(lambda x: x.startswith('rpcuser='******'rpcport': next(filter(lambda x: x.startswith('rpcport='), configLines), None)[8:], 'rpcpassword': next(filter(lambda x: x.startswith('rpcpassword='), configLines), None)[12:], }
class SingleInvoiceListener(Thread): def __init__(self, preimage, hash): super().__init__() self.lnd = Client() config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.lamb = boto3.client('lambda', config=config, region_name='us-west-2') self.hash = hash self.preimage = preimage self.logger = logging.getLogger(name='SingleInvoiceListener') self.start() def run(self): self.logger.info('Starting Thread...') while True: try: self.logger.info('Listening for ' + binascii.hexlify(self.hash).decode()) invoices = self.lnd.subscribeSingleInvoice(self.hash) for invoice in invoices: self.logger.info(str(invoice)) self.logger.info({ 'memo': invoice.memo, 'r_hash': binascii.hexlify(invoice.r_hash).decode() }) sleep(1) except grpc._channel._Rendezvous as e: self.logger.error('LND appears to be down...') self.lnd = Client() sleep(60) except Exception as e: self.logger.error('Error type: {}'.format(type(e))) self.logger.info('{}\n\n{}'.format(e, traceback.format_exc())) sleep(60)
class MetricThread(CommonThread): def __init__(self, lnddatadir, btcdatadir): super().__init__('MetricThread', 'MetricThread') self.lnd = Client(lnddatadir) config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.cw = boto3.client('cloudwatch', config=config, region_name='us-west-2') with open(os.path.join(btcdatadir, 'bitcoin.conf'), 'r') as f: configLines = f.read().splitlines() self.btcConfig = { 'rpcuser': next(filter(lambda x: x.startswith('rpcuser='******'rpcport': next(filter(lambda x: x.startswith('rpcport='), configLines), None)[8:], 'rpcpassword': next(filter(lambda x: x.startswith('rpcpassword='******'http://{}:{}@127.0.0.1:{}'.format( self.btcConfig['rpcuser'], self.btcConfig['rpcpassword'], self.btcConfig['rpcport']), json={'method': 'getnetworkinfo'}) return response.json()['result']['networkactive'] except: return False def isLndUp(self): try: self.lnd.getInfo() return True except: return False def tryRun(self): if datetime.utcnow().minute == 0: self.logger.info('Sending status...') btcUp = self.isBtcUp() lndUp = self.isLndUp() self.cw.put_metric_data(Namespace='LNTipBot', MetricData=[{ 'MetricName': 'LndUp', 'Timestamp': datetime.now(), 'Value': 1 if lndUp else 0, 'Unit': 'None', 'StorageResolution': 60 }, { 'MetricName': 'BtcUp', 'Timestamp': datetime.now(), 'Value': 1 if btcUp else 0, 'Unit': 'None', 'StorageResolution': 60 }]) self.throttle()
class PayInvoiceThread(CommonThread): def __init__(self, lnddatadir): super().__init__('PIThread', 'PayInvoiceThread') config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.sfn = boto3.client('stepfunctions', config=config, region_name='us-west-2') self.lnd = Client(lnddatadir) def handleTaskError(self, token, errorMessage): self.sfn.send_task_failure(taskToken=token, error="Failed", cause=str(errorMessage)) def handleTask(self, token, data): self.logger.info('Thread started for payment: {}'.format(data)) paymentResponse = None for x in range(1): try: paymentResponse = self.lnd.sendPayment( data['invoice'], int(data['amount'] / 1000000)) self.logger.info('paymentResponse: {}'.format(paymentResponse)) except Exception as e: error = e else: if paymentResponse.payment_error: error = paymentResponse.payment_error else: data['paymentResponse'] = { 'payment_preimage': paymentResponse.payment_preimage.hex(), 'payment_route': str(paymentResponse.payment_route), } self.logger.info('Payment succeeded with {}'.format( paymentResponse.payment_preimage.hex())) self.sfn.send_task_success( taskToken=token, output=json.dumps(data), ) break self.logger.error('Payment failed with {} {}'.format( type(error), error)) else: self.handleTaskError(token, error) def tryRun(self): # Make sure lnd is active before getting a task self.lnd.getInfo() response = self.sfn.get_activity_task( activityArn= 'arn:aws:states:us-west-2:434623153115:activity:CdkStackpayInvoiceActivityB30C5FBC', workerName='LNTipServer') if 'taskToken' in response and 'input' in response: token = response['taskToken'] data = json.loads(response['input']) # TODO: join all threads before exiting this one Thread(target=self.handleTask, args=(token, data)).start() with self.cond: self.cond.wait(10)
class InvoiceSubscriptionThread(CommonThread): def __init__(self, datadir, lnddatadir): super().__init__('ISThread', 'InvoiceSubscriptionThread') self.lnd = Client(lnddatadir) config = Config(read_timeout=65, retries=dict(max_attempts=10)) self.lamb = boto3.client('lambda', config=config, region_name='us-west-2') self.configPath = os.path.join(datadir, "config.json") if not os.path.isfile(self.configPath): self.logger.info( 'Invoice subscription configuration not found, recreating with settleIndex 0' ) json.dump({'settleIndex': 0}, open(self.configPath, 'w')) self.config = json.load(open(self.configPath)) def shutdown(self): super().shutdown() self.lnd.channel.close() def updateSettleIndex(self, index): self.config['settleIndex'] = index json.dump(self.config, open(self.configPath, 'w')) def tryRun(self): try: invoices = self.lnd.subscribeInvoices( settleIndex=self.config['settleIndex']) for invoice in invoices: if invoice.settle_index > 0: payload = { 'memo': invoice.memo, 'value': str(invoice.value), 'amt_paid': str(invoice.amt_paid * 1000) } self.logger.info(payload) response = self.lamb.invoke( FunctionName= 'arn:aws:lambda:us-west-2:434623153115:function:CdkStack-settledInvoiceHandler38092B08-4MojK9KjXaDx', InvocationType='RequestResponse', Payload=json.dumps(payload), ) self.updateSettleIndex(invoice.settle_index) if 'FunctionError' in response: self.logger.error(response) else: self.logger.info({ 'memo': invoice.memo, 'r_hash': invoice.r_hash }) except grpc._channel._Rendezvous as e: if e.code() == grpc.StatusCode.CANCELLED: self.logger.info('Subscription interrupted') return with self.cond: self.cond.wait(1)