def test_handleHTLCAccepted_failed(self): self.interface.handleRequest( 6, 'htlc_accepted', { 'onion': { 'payload': '12fe424c34500cf1f2f3f4f5f6f7f8c1c2c3c4', }, 'htlc': { 'amount': '1234msat', 'cltv_expiry': 42, 'payment_hash': 'cafecafe', }, }) self.assertEqual(self.output.buffer, b'') self.client.handleIncomingMessage.assert_called_once_with( messages.LNIncoming( paymentHash=b'\xca\xfe\xca\xfe', cryptoAmount=1234, CLTVExpiryDelta=42, fiatAmount=0xf1f2f3f4f5f6f7f8, offerID=0xc1c2c3c4, )) self.interface.handleMessage( messages.LNFail(paymentHash=b'\xca\xfe\xca\xfe', )) self.checkJSONOutput({ 'jsonrpc': '2.0', 'id': 6, 'result': { 'result': 'fail', }, })
async def cancelTransactionOnLightning(self) -> None: assert isinstance(self.order, BuyOrder) assert isinstance(self.transaction, BuyTransaction) self.client.handleOutgoingMessage( messages.LNFail(paymentHash=self.transaction.paymentHash, )) self.transaction = None logging.info('Buy transaction is canceled') #TODO: is this really needed? await self.updateOrderAfterTransaction()
def handleLNIncoming(self, message: messages.LNIncoming) -> None: localID = message.offerID #type: int try: self.orderTasks[localID].setCallResult(message) except: logging.exception( 'Exception on trying to handle an incoming Lightning transaction for local ID ' + str(localID)) logging.error( 'Apparently we can\'t handle the transaction right now, so we are refusing the incoming transaction.' ) self.client.handleOutgoingMessage( messages.LNFail(paymentHash=message.paymentHash))
async def test_buyer_repeatCanceledTransaction(self): orderID = ordertask.BuyOrder.create( self.storage, 190000, #mCent / BTC = 1.9 EUR/BTC 123400000 #mCent = 1234 EUR ) order = ordertask.BuyOrder(self.storage, orderID, 'buyerAddress') order.remoteOfferID = 6 order.setAmount = Mock() self.storage.buyTransactions = \ { 41: { 'ID': 41, 'buyOrder': orderID, 'status': 5, #canceled 'fiatAmount': 100000000, 'cryptoAmount': 200000000, 'paymentHash': b'foo', }, 42: { 'ID': 42, 'buyOrder': orderID, 'status': 0, 'fiatAmount': 300000000, 'cryptoAmount': 600000000, 'paymentHash': b'foo2', } } task = ordertask.OrderTask(self.client, self.storage, order) task.startup() await asyncio.sleep(0.1) task.setCallResult( messages.LNIncoming( offerID=42, CLTVExpiryDelta=0, fiatAmount=100000000, cryptoAmount=200000000, paymentHash=b'foo', )) msg = await self.outgoingMessages.get() self.assertEqual(msg, messages.LNFail(paymentHash=b'foo', )) await self.shutdownOrderTask(task)
async def test_failedBuyTransaction(self): orderID = ordertask.BuyOrder.create( self.storage, 190000, #mCent / BTC = 1.9 EUR/BTC 123400000 #mCent = 1234 EUR ) order = ordertask.BuyOrder(self.storage, orderID, 'buyerAddress') order.remoteOfferID = 6 order.setAmount = Mock() self.storage.buyTransactions = \ { 41: { 'ID': 41, 'buyOrder': orderID, 'status': 0, 'fiatAmount': 100000000, 'cryptoAmount': 200000000, 'paymentHash': b'foo', } } task = ordertask.OrderTask(self.client, self.storage, order) task.startup() await asyncio.sleep(0.1) task.setCallResult( messages.LNIncoming( offerID=42, CLTVExpiryDelta=0, fiatAmount=100000000, cryptoAmount=200000000, paymentHash=b'foo', )) msg = await self.outgoingMessages.get() self.assertEqual(msg, messages.BL4PSend( localOrderID=42, amount = 100000000, paymentHash = b'foo', max_locked_timeout_delta_s = 3600*24*14, selfReport = \ { 'paymentHash' : '666f6f', #foo in hex 'offerID' : str(orderID), 'receiverCryptoAmount': '0.00200000000', 'cryptoCurrency' : 'btc', }, )) task.setCallResult(messages.BL4PError(request=None, )) #LN transaction gets canceled msg = await self.outgoingMessages.get() self.assertEqual(msg, messages.LNFail(paymentHash=b'foo', )) order.setAmount.assert_called_once_with(223400000) self.assertEqual( self.storage.buyTransactions, { 41: { 'ID': 41, 'status': ordertask.TX_STATUS_CANCELED, 'buyOrder': orderID, 'fiatAmount': 100000000, 'cryptoAmount': 200000000, 'paymentHash': b'foo', } }) self.assertEqual(task.transaction, None) #Old offer gets removed msg = await self.outgoingMessages.get() self.assertTrue(isinstance(msg, messages.BL4PRemoveOffer)) task.setCallResult(messages.BL4PRemoveOfferResult(request=None, )) #New offer gets added msg = await self.outgoingMessages.get() self.assertTrue(isinstance(msg, messages.BL4PAddOffer)) task.setCallResult(messages.BL4PAddOfferResult( request=None, ID=6, )) #Continues to next iteration: await asyncio.sleep(0.1) await self.shutdownOrderTask(task)
def test_sendFail_withoutCall(self): #This must be a NOP: self.interface.handleMessage( messages.LNFail(paymentHash=b'\xca\xfe\xca\xfe', )) self.assertEqual(self.output.buffer, b'')
async def waitForIncomingTransaction(self) -> None: assert isinstance(self.order, BuyOrder) message = cast(messages.LNIncoming, await self.waitForIncomingMessage( messages.LNIncoming)) #type: messages.LNIncoming logging.info('Received incoming Lightning transaction') #TODO: log transaction characteristics #TODO: maybe refuse tx if we're not connected to BL4P #Check if this is a new notification for an already ongoing tx. cursor = self.storage.execute( 'SELECT ID from buyTransactions WHERE paymentHash = ?', [message.paymentHash]) #type: Cursor transactions = [BuyTransaction(self.storage, row[0]) for row in cursor] if transactions: logging.info( 'A transaction with this payment hash already exists in our database' ) self.transaction = transactions[0] if self.transaction.paymentPreimage is not None: logging.info( 'We already have the preimage, so we claim the Lightning funds' ) await self.finishTransactionOnLightning() return #TODO (bug 19): maybe check if the incoming tx equals this tx? if self.transaction.status == TX_STATUS_CANCELED: logging.info( 'The transaction was canceled, so we cancel the Lightning tx' ) await self.cancelTransactionOnLightning() elif self.transaction.status == TX_STATUS_INITIAL: logging.info( 'The transaction was not finished yet, so try again to finish it' ) await self.sendFundsOnBL4P() else: #TODO: properly report database inconsistency error raise Exception( 'Invalid transaction status value in unfinished transaction' ) return #Check if lntx conforms to our order: counterOffer = Offer( #These are equivalent to our order, except for max_amount. #max_amount will be overwritten - see below bid=copy.deepcopy(self.order.ask), ask=copy.deepcopy(self.order.bid), #dummy values: address='', ID=0, #Don't specify sender_timeout: we can just try if we're still within the timeout #Don't specify locked_timeout: it is unknown to us; we will inform BL4P about our maximum ) #type: offer.Offer counterOffer.bid.max_amount = message.cryptoAmount counterOffer.ask.max_amount = message.fiatAmount try: counterOffer.verifyMatches(self.order) except offer.MismatchError as error: logging.info( 'Received transaction did not match our order - refusing it.') logging.info('The mismatch is: ' + str(error)) self.client.handleOutgoingMessage( messages.LNFail(paymentHash=message.paymentHash, )) return #TODO: (bug 5) check max per-tx amount #TODO: (bug 5) check that we still have sufficient time #according to our cltv_expiry_delta #Check if remaining order size is sufficient: assert message.fiatAmount <= self.order.amount buyTransactionID = BuyTransaction.create( self.storage, buyOrder=self.order.ID, fiatAmount=message.fiatAmount, cryptoAmount=message.cryptoAmount, paymentHash=message.paymentHash, ) #type: int self.order.setAmount(self.order.amount - message.fiatAmount) self.transaction = BuyTransaction(self.storage, buyTransactionID) await self.sendFundsOnBL4P()