def test_handleGetConfigCommand(self):
        self.backend.storage = MockStorage(test=self)
        self.backend.BL4PAddress = 'BL4PAddress'

        MS = MockStorage(test=self)
        MS.configuration['bl4p.apiKey'] = 'foo'
        MS.configuration['bl4p.apiSecret'] = 'bar'

        self.backend.configuration = configuration.Configuration(MS)

        cmd = Mock()
        cmd.commandID = 42

        self.backend.handleGetConfigCommand(cmd)

        self.assertEqual(self.outgoingMessages,
         [messages.PluginCommandResult(
          commandID=42,
          result=\
           {
           'values':
            {
            'bl4p.url'              : '',
            'bl4p.apiKey'           : 'foo',
            'bl4p.apiSecret'        : 'bar',
            'bl4p.signingPrivateKey': '',
            }
           }
         )])
Beispiel #2
0
    def setUp(self):
        self.storage = MockStorage(test=self, startCount=42)

        self.outgoingMessages = asyncio.Queue()

        def handleOutgoingMessage(msg):
            self.outgoingMessages.put_nowait(msg)

        self.client = Mock()
        self.client.handleOutgoingMessage = handleOutgoingMessage
    def test_handleSellCommand(self):
        self.backend.storage = MockStorage(test=self)
        self.backend.BL4PAddress = 'BL4PAddress'

        cmd = Mock()
        cmd.commandID = 42
        cmd.amount = 123
        cmd.limitRate = 20000
        with patch.object(backend.ordertask, 'OrderTask', MockOrderTask):
            self.backend.handleSellCommand(cmd)

        self.assertEqual(set(self.backend.orderTasks.keys()), set([61]))
        self.assertEqual(set(self.backend.storage.sellOrders.keys()),
                         set([61]))

        ot = self.backend.orderTasks[61]
        stored = self.backend.storage.sellOrders[61]
        self.assertTrue(isinstance(ot, MockOrderTask))
        self.assertEqual(ot.client, self.client)
        self.assertEqual(ot.storage, self.backend.storage)
        self.assertTrue(ot.started)

        self.assertTrue(isinstance(ot.order, order.SellOrder))
        self.assertEqual(stored['amount'], ot.order.amount)
        self.assertEqual(stored['limitRate'], ot.order.limitRate)
        self.assertEqual(stored['amount'], 123)
        self.assertEqual(stored['limitRate'], 20000)

        self.assertEqual(
            self.outgoingMessages,
            [messages.PluginCommandResult(commandID=42, result=None)])
    def test_startup(self):
        storage = MockStorage()
        storage.configuration['bl4p.url'] = 'PresetURL'
        conf = configuration.Configuration(storage)

        self.assertEqual(
            conf.values, {
                'bl4p.url': 'PresetURL',
                'bl4p.apiKey': '',
                'bl4p.apiSecret': '',
                'bl4p.signingPrivateKey': '',
            })
        self.assertEqual(storage.configuration, conf.values)
    def test_handleSetConfigCommand(self):
        self.backend.storage = MockStorage(test=self)
        self.backend.BL4PAddress = 'BL4PAddress'

        MS = MockStorage(test=self)
        self.backend.configuration = configuration.Configuration(MS)

        cmd = Mock()
        cmd.commandID = 42
        cmd.values = {'bl4p.apiKey': 'foo', 'bl4p.apiSecret': 'bar'}

        self.backend.handleSetConfigCommand(cmd)

        self.assertEqual(self.backend.configuration.getValue('bl4p.apiKey'),
                         'foo')
        self.assertEqual(self.backend.configuration.getValue('bl4p.apiSecret'),
                         'bar')
        self.assertEqual(MS.configuration['bl4p.apiKey'], 'foo')
        self.assertEqual(MS.configuration['bl4p.apiSecret'], 'bar')

        self.assertEqual(
            self.outgoingMessages,
            [messages.PluginCommandResult(commandID=42, result=None)])
    def test_getset(self):
        storage = MockStorage()
        conf = configuration.Configuration(storage)

        self.assertEqual(conf.getValue('bl4p.apiKey'), '')

        conf.setValue('bl4p.apiKey', 'foo')

        self.assertEqual(storage.configuration['bl4p.apiKey'], 'foo')
        self.assertEqual(conf.getValue('bl4p.apiKey'), 'foo')

        self.assertEqual(
            conf.getAllValues(), {
                'bl4p.url': '',
                'bl4p.apiKey': 'foo',
                'bl4p.apiSecret': '',
                'bl4p.signingPrivateKey': '',
            })
    def test_handleSellCommand_noBL4P(self):
        self.backend.storage = MockStorage(test=self)
        self.backend.BL4PAddress = 'BL4PAddress'
        self.bl4pIsConnected = False

        cmd = Mock()
        cmd.commandID = 42
        cmd.amount = 123
        cmd.limitRate = 20000
        with patch.object(backend.ordertask, 'OrderTask', MockOrderTask):
            self.backend.handleSellCommand(cmd)

        self.assertEqual(set(self.backend.orderTasks.keys()), set([]))
        self.assertEqual(set(self.backend.storage.sellOrders.keys()), set([]))

        self.assertEqual(self.outgoingMessages, [
            messages.PluginCommandError(
                commandID=42,
                code=1,
                message=
                'Cannot perform this action while not connected to a BL4P server'
            )
        ])
Beispiel #8
0
class TestOrderTask(unittest.TestCase):
    def setUp(self):
        self.storage = MockStorage(test=self, startCount=42)

        self.outgoingMessages = asyncio.Queue()

        def handleOutgoingMessage(msg):
            self.outgoingMessages.put_nowait(msg)

        self.client = Mock()
        self.client.handleOutgoingMessage = handleOutgoingMessage

    async def shutdownOrderTask(self, task):
        #While we await for task.shutdown, the task calls BL4P to remove the
        #offer; it then awaits for the result.
        #However, we can't give it the result since we're awaiting shutdown.
        #To work around this, we patch the waitForIncomingMessage method, so
        #that the task receives the message it expects once it starts waiting
        #for it.

        async def waitForIncomingMessage(expectedResultType):
            self.assertEqual(expectedResultType,
                             messages.BL4PRemoveOfferResult)
            return messages.BL4PRemoveOfferResult(request=None)

        with patch.object(task, 'waitForIncomingMessage',
                          waitForIncomingMessage):
            await task.shutdown()

        msg = await self.outgoingMessages.get()
        self.assertEqual(
            msg, messages.BL4PRemoveOffer(
                localOrderID=42,
                offerID=6,
            ))

    @asynciotest
    async def test_waitForBL4PConnection(self):
        self.client.isBL4PConnected = Mock(return_value=True)
        task = ordertask.OrderTask(self.client, self.storage, None)
        await task.waitForBL4PConnection()
        self.client.isBL4PConnected.assert_called_with()

        self.client.isBL4PConnected = Mock(return_value=False)
        calls = []

        async def waitForBL4PConnection():
            calls.append(None)

        self.client.waitForBL4PConnection = waitForBL4PConnection
        task = ordertask.OrderTask(self.client, self.storage, None)
        await task.waitForBL4PConnection()
        self.client.isBL4PConnected.assert_called_with()
        self.assertEqual(len(calls), 1)

    def test_BuyTransaction(self):
        with patch.object(ordertask.StoredObject, 'createStoredObject',
                          Mock(return_value=43)):
            self.assertEqual(
                ordertask.BuyTransaction.create('foo', 'baa', 'bab', 'bac',
                                                'bad'), 43)

            ordertask.StoredObject.createStoredObject.assert_called_once_with(
                'foo',
                'buyTransactions',
                buyOrder='baa',
                fiatAmount='bab',
                cryptoAmount='bac',
                paymentHash='bad',
                status=0,
                paymentPreimage=None,
            )

            storage = Mock()
            cursor = Mock()
            cursor.description = [['ID'], ['status'], ['paymentHash']]
            cursor.fetchone = Mock(return_value=[42, 1, b'cafecafe'])
            storage.execute = Mock(return_value=cursor)

            buy = ordertask.BuyTransaction(storage, 42)

            self.assertEqual(buy.ID, 42)
            self.assertEqual(buy._tableName, 'buyTransactions')
            self.assertEqual(buy.status, 1)
            self.assertEqual(buy.paymentHash, b'cafecafe')

    def test_SellTransaction(self):
        with patch.object(ordertask.StoredObject, 'createStoredObject',
                          Mock(return_value=43)):
            self.assertEqual(
                ordertask.SellTransaction.create('foo', 'baa', 'bab', 'bac',
                                                 'bae', 'baf', 'bag', 'bah'),
                43)

            ordertask.StoredObject.createStoredObject.assert_called_once_with(
                'foo',
                'sellTransactions',
                sellOrder='baa',
                counterOffer='bab',
                buyerFiatAmount='bac',
                buyerCryptoAmount='bae',
                senderTimeoutDelta='baf',
                lockedTimeoutDelta='bag',
                CLTVExpiryDelta='bah',
                status=0,
                sellerFiatAmount=None,
                sellerCryptoAmount=None,
                paymentHash=None,
                paymentPreimage=None,
            )

            storage = Mock()
            cursor = Mock()
            cursor.description = [['ID'], ['status'], ['paymentHash']]
            cursor.fetchone = Mock(return_value=[42, 1, b'cafecafe'])
            storage.execute = Mock(return_value=cursor)

            sell = ordertask.SellTransaction(storage, 42)

            self.assertEqual(sell.ID, 42)
            self.assertEqual(sell._tableName, 'sellTransactions')
            self.assertEqual(sell.status, 1)
            self.assertEqual(sell.paymentHash, b'cafecafe')

    def test_CounterOffer(self):
        with patch.object(ordertask.StoredObject, 'createStoredObject',
                          Mock(return_value=43)):
            PB2 = Mock()
            PB2.SerializeToString = Mock(return_value=b'bar')
            counterOffer = Mock()
            counterOffer.toPB2 = Mock(return_value=PB2)

            self.assertEqual(
                ordertask.CounterOffer.create('foo', counterOffer), 43)

            ordertask.StoredObject.createStoredObject.assert_called_once_with(
                'foo', 'counterOffers', blob=b'bar')

            storage = Mock()
            cursor = Mock()
            cursor.description = [['ID'], ['blob']]
            cursor.fetchone = Mock(return_value=[42, b'cafecafe'])
            storage.execute = Mock(return_value=cursor)

            ParseFromString = Mock()

            def fromPB2(co):
                self.assertTrue(isinstance(co, ordertask.offer_pb2.Offer))
                return 'bar'

            with patch.object(ordertask.offer_pb2.Offer, 'ParseFromString',
                              ParseFromString):
                with patch.object(ordertask.Offer, 'fromPB2', fromPB2):
                    co = ordertask.CounterOffer(storage, 42)

            ParseFromString.assert_called_once_with(b'cafecafe')

            self.assertEqual(co.ID, 42)
            self.assertEqual(co._tableName, 'counterOffers')
            self.assertEqual(co.blob, b'cafecafe')
            self.assertEqual(co.counterOffer, 'bar')

    def test_constructor(self):
        client = object()
        storage = object()
        order = object()
        task = ordertask.OrderTask(client, storage, order)

        self.assertEqual(task.client, client)
        self.assertEqual(task.storage, storage)
        self.assertEqual(task.order, order)
        self.assertEqual(task.counterOffer, None)
        self.assertEqual(task.transaction, None)

    @asynciotest
    async def test_task(self):
        task = ordertask.OrderTask(None, None, None)

        result = []

        async def count():
            try:
                i = 1
                while True:
                    await asyncio.sleep(0.1)
                    result.append(i)
                    i += 1
            except asyncio.CancelledError:
                pass

        task.doTrading = count
        task.startup()
        await asyncio.sleep(0.35)
        await task.shutdown()
        self.assertEqual(result, [1, 2, 3])

        result = []

        async def countTo3():
            for i in range(1, 4):
                await asyncio.sleep(0.1)
                result.append(i)

        task.doTrading = countTo3
        task.startup()
        await task.waitFinished()
        self.assertEqual(result, [1, 2, 3])

    def test_cancel(self):
        orderID = ordertask.BuyOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000  #mCent    = 1234 EUR
        )
        order = ordertask.BuyOrder(self.storage, orderID, 'lnAddress')
        task = ordertask.OrderTask(self.client, self.storage, order)
        task.task = Mock()
        task.cancel()
        self.assertEqual(order.status, ORDER_STATUS_CANCELED)
        task.task.cancel.assert_called_with()

        task.transaction = Mock()
        task.cancel()
        self.assertEqual(order.status, ORDER_STATUS_CANCEL_REQUESTED)

    def test_getListInfo(self):
        orderID = ordertask.BuyOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000  #mCent    = 1234 EUR
        )
        order = ordertask.BuyOrder(self.storage, orderID, 'lnAddress')
        task = ordertask.OrderTask(self.client, self.storage, order)

        self.assertEqual(
            task.getListInfo(), {
                'ID': orderID,
                'status': 'active',
                'limitRate': 190000,
                'amount': 123400000,
            })

        txID = task.transaction = ordertask.BuyTransaction.create(
            self.storage,
            buyOrder=orderID,
            fiatAmount=12,
            cryptoAmount=34,
            paymentHash=b'foobar')
        task.transaction = ordertask.BuyTransaction(self.storage, txID)
        self.assertEqual(
            task.getListInfo(), {
                'ID': orderID,
                'status': 'active',
                'limitRate': 190000,
                'amount': 123400000,
                'transaction': {
                    'status': 'initial',
                    'fiatAmount': 12,
                    'cryptoAmount': 34,
                }
            })

        txID = task.transaction = ordertask.SellTransaction.create(
            self.storage,
            sellOrder=orderID,
            counterOffer=0,
            buyerFiatAmount=12,
            buyerCryptoAmount=56,
            senderTimeoutDelta=0,
            lockedTimeoutDelta=0,
            CLTVExpiryDelta=0,
        )
        task.transaction = ordertask.SellTransaction(self.storage, txID)
        task.transaction.sellerFiatAmount = 34
        task.transaction.sellerCryptoAmount = 78
        self.assertEqual(
            task.getListInfo(), {
                'ID': orderID,
                'status': 'active',
                'limitRate': 190000,
                'amount': 123400000,
                'transaction': {
                    'status': 'initial',
                    'buyerFiatAmount': 12,
                    'sellerFiatAmount': 34,
                    'buyerCryptoAmount': 56,
                    'sellerCryptoAmount': 78,
                }
            })

    @asynciotest
    async def test_buyer_goodFlow(self):
        orderID = ordertask.BuyOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000  #mCent    = 1234 EUR
        )
        order = ordertask.BuyOrder(self.storage, orderID, 'lnAddress')

        task = ordertask.OrderTask(self.client, self.storage, order)
        task.startup()

        remainingAmount = order.amount
        for txAmount in [100000000, 23400000]:
            self.storage.reset(
                startCount=43)  #clean up from previous iteration
            self.storage.buyOrders[orderID] = {}
            order.setAmount = Mock()  #Replace mock object with a fresh one

            #Offer gets published
            msg = await self.outgoingMessages.get()
            self.assertEqual(
                msg, messages.BL4PAddOffer(
                    localOrderID=42,
                    offer=order,
                ))
            task.setCallResult(
                messages.BL4PAddOfferResult(
                    request=None,
                    ID=6,
                ))

            #TODO: is the next message in a race condition with the previous?
            await asyncio.sleep(0.1)

            #Incoming LN transaction arrives
            paymentPreimage = b'foo'
            paymentHash = sha256(paymentPreimage)
            task.setCallResult(
                messages.LNIncoming(
                    offerID=42,
                    CLTVExpiryDelta=0,
                    fiatAmount=txAmount,
                    cryptoAmount=100000000000000,
                    paymentHash=paymentHash,
                ))

            msg = await self.outgoingMessages.get()

            remainingAmount -= txAmount
            order.setAmount.assert_called_once_with(remainingAmount)
            order.amount = remainingAmount
            self.assertEqual(
                self.storage.buyTransactions, {
                    43: {
                        'ID': 43,
                        'status': ordertask.TX_STATUS_INITIAL,
                        'buyOrder': 42,
                        'fiatAmount': txAmount,
                        'cryptoAmount': 100000000000000,
                        'paymentHash': paymentHash,
                        'paymentPreimage': None,
                    }
                })

            #Funds get sent on BL4P
            self.assertEqual(msg, messages.BL4PSend(
             localOrderID=42,

             amount=txAmount,
             paymentHash=paymentHash,
             max_locked_timeout_delta_s = 3600*24*14,
             selfReport = \
              {
              'paymentHash'         : paymentHash.hex(),
              'offerID'             : str(orderID),
              'receiverCryptoAmount': '1000.00000000000',
              'cryptoCurrency'      : 'btc',
              },
             ))
            task.setCallResult(
                messages.BL4PSendResult(
                    request=None,
                    paymentPreimage=paymentPreimage,
                ))

            msg = await self.outgoingMessages.get()

            self.assertEqual(
                self.storage.buyTransactions, {
                    43: {
                        'ID': 43,
                        'status': ordertask.TX_STATUS_FINISHED,
                        'buyOrder': 42,
                        'fiatAmount': txAmount,
                        'cryptoAmount': 100000000000000,
                        'paymentHash': paymentHash,
                        'paymentPreimage': paymentPreimage,
                    }
                })
            self.assertEqual(task.transaction, None)

            #LN transaction gets finished
            self.assertEqual(
                msg,
                messages.LNFinish(
                    paymentHash=paymentHash,
                    paymentPreimage=paymentPreimage,
                ))

            #Old offer gets removed
            msg = await self.outgoingMessages.get()
            self.assertEqual(
                msg, messages.BL4PRemoveOffer(
                    localOrderID=42,
                    offerID=6,
                ))
            task.setCallResult(messages.BL4PRemoveOfferResult(request=None, ))

        await task.waitFinished()

        self.assertEqual(self.storage.buyOrders[orderID]['status'],
                         1)  #completed

    @asynciotest
    async def test_refusedBuyTransaction(self):
        orderID = ordertask.BuyOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000  #mCent    = 1234 EUR
        )
        order = ordertask.BuyOrder(self.storage, orderID, 'lnAddress')

        task = ordertask.OrderTask(self.client, self.storage, order)
        task.startup()

        self.storage.reset(startCount=43)  #clean up from previous iteration
        self.storage.buyOrders[orderID] = {}
        order.setAmount = Mock()  #Replace mock object with a fresh one

        #Offer gets published
        msg = await self.outgoingMessages.get()
        self.assertEqual(msg,
                         messages.BL4PAddOffer(
                             localOrderID=42,
                             offer=order,
                         ))
        task.setCallResult(messages.BL4PAddOfferResult(
            request=None,
            ID=6,
        ))

        #TODO: is the next message in a race condition with the previous?
        await asyncio.sleep(0.1)

        #Incoming LN transaction arrives
        paymentPreimage = b'foo'
        paymentHash = sha256(paymentPreimage)
        task.setCallResult(
            messages.LNIncoming(
                offerID=42,
                CLTVExpiryDelta=0,
                fiatAmount=100000000,  #mCent = 1000 EUR
                cryptoAmount=50,  #mBTC = 0.00000000050 BTC
                paymentHash=paymentHash,
            ))

        msg = await self.outgoingMessages.get()

        await self.shutdownOrderTask(task)

    @asynciotest
    async def test_continueBuyTransaction(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')

        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',
          },
         ))

        await task.shutdown()

        #Database inconsistency exception:
        self.storage.buyTransactions = \
        {
        41:
         {
         'ID': 41,
         'buyOrder': orderID,
         'status': 100,
         }
        }

        task = ordertask.OrderTask(self.client, self.storage, order)
        with self.assertRaises(Exception):
            await task.continueBuyTransaction()

    @asynciotest
    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)

    @asynciotest
    async def test_buyer_repeatFinishedTransaction(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': 4, #finished
         'fiatAmount': 100000000,
         'cryptoAmount': 200000000,
         'paymentHash': b'foo',
         'paymentPreimage': b'bar',
         },
        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.LNFinish(paymentHash=b'foo', paymentPreimage=b'bar'))

        await self.shutdownOrderTask(task)

    @asynciotest
    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)

    @asynciotest
    async def test_seller_goodFlow(self):
        orderID = ordertask.SellOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000000000  #mSatoshi    = 1234 BTC
        )
        order = ordertask.SellOrder(self.storage, orderID, 'sellerAddress')

        task = ordertask.OrderTask(self.client, self.storage, order)
        task.startup()

        o1 = Mock()
        o1.getConditionMin = Mock(return_value=23)
        o1.getConditionMax = Mock(return_value=53)
        toPB2_return = Mock()
        toPB2_return.SerializeToString = Mock(return_value=b'bar')
        o1.toPB2 = Mock(return_value=toPB2_return)
        o1.ask.max_amount = 1000  #BTC
        o1.ask.max_amount_divisor = 1
        o1.bid.max_amount = 2000  #EUR
        o1.bid.max_amount_divisor = 1
        o1.ID = 6
        o1.address = 'buyerAddress'

        #We're going to match the sell order (order) twice with the
        #counter-offer (o1).

        remainingAmount = order.amount
        for i in range(2):
            self.storage.reset(
                startCount=43)  #clean up from previous iteration
            self.storage.sellOrders[orderID] = {}
            order.setAmount = Mock()  #Replace mock object with a fresh one

            #Expected data:
            buyerCryptoAmount = \
            [
            100000000000000, #1000 BTC = min(1234, 1000)
            22900000000000,  # 229 BTC = min(1234 - 1005, 1000)
            ][i]
            buyerFiatAmount = \
            [
            200000000, #2000 EUR = 1000 BTC * buyer limit rate
            45800000,  #458 EUR  =  229 BTC * buyer limit rate
            ][i]
            minSellerFiatAmount = \
            [
            190000000, #1900   EUR = 1000 BTC * seller limit rate
             43510000, # 435.1 EUR =  229 BTC * seller limit rate
            ][i]
            maxSellerCryptoAmount = \
            [
            101000000000000, #1010    BTC = 1.01 * 1000 BTC
             23129000000000, # 231.29 BTC = 1.01 *  229 BTC
            ][i]
            sellerCryptoAmount = \
            [
            100500000000000, #1005 BTC, just slightly more than nominalCryptoAmount
             23500000000000, # 235 BTC, just slightly more than nominalCryptoAmount
            ][i]
            buyerCryptoAmount_str = \
            [
            '1000.00000000000', #1000 BTC, Equals the nominal amount
            '229.00000000000',  # 229 BTC, Equals the nominal amount
            ][i]
            sellerFiatAmount = \
            [
            199000000, #1990 EUR, just slightly less than nominalFiatAmount
            45000000,  # 450 EUR, just slightly less than nominalFiatAmount
            ][i]

            #Offers get found
            msg = await self.outgoingMessages.get()
            self.assertEqual(
                msg, messages.BL4PFindOffers(
                    localOrderID=42,
                    query=order,
                ))
            task.setCallResult(
                messages.BL4PFindOffersResult(
                    request=None,
                    offers=[o1, Mock()],
                ))

            msg = await self.outgoingMessages.get()

            #For now, behavior is to always select the first:
            self.assertEqual(task.counterOffer, o1)

            self.assertEqual(self.storage.counterOffers,
                             {43: {
                                 'ID': 43,
                                 'blob': b'bar',
                             }})
            self.assertEqual(
                self.storage.sellTransactions,
                {
                    44: {
                        'ID': 44,
                        'sellOrder': 42,
                        'counterOffer': 43,
                        'status': 0,
                        'senderTimeoutDelta': 2000,  #highest minimum
                        'lockedTimeoutDelta': 53,  #lowest maximum
                        'CLTVExpiryDelta': 23,  #highest minimum
                        'buyerCryptoAmount': buyerCryptoAmount,
                        'sellerCryptoAmount': None,
                        'buyerFiatAmount': buyerFiatAmount,
                        'sellerFiatAmount': None,
                        'paymentHash': None,
                        'paymentPreimage': None,
                    }
                })

            #Transaction starts on BL4P
            paymentPreimage = b'foo'
            paymentHash = sha256(paymentPreimage)
            self.assertEqual(
                msg,
                messages.BL4PStart(
                    localOrderID=42,
                    amount=buyerFiatAmount,
                    sender_timeout_delta_ms=2000,
                    locked_timeout_delta_s=53,
                    receiver_pays_fee=True,
                ))
            task.setCallResult(
                messages.BL4PStartResult(
                    request=None,
                    senderAmount=buyerFiatAmount,
                    receiverAmount=sellerFiatAmount,
                    paymentHash=paymentHash,
                ))

            msg = await self.outgoingMessages.get()

            self.assertEqual(
                self.storage.sellTransactions, {
                    44: {
                        'ID': 44,
                        'sellOrder': 42,
                        'counterOffer': 43,
                        'status': ordertask.TX_STATUS_STARTED,
                        'senderTimeoutDelta': 2000,
                        'lockedTimeoutDelta': 53,
                        'CLTVExpiryDelta': 23,
                        'buyerCryptoAmount': buyerCryptoAmount,
                        'sellerCryptoAmount': None,
                        'buyerFiatAmount': buyerFiatAmount,
                        'sellerFiatAmount': sellerFiatAmount,
                        'paymentHash': paymentHash,
                        'paymentPreimage': None,
                    }
                })

            #Self-reporting on BL4P
            self.assertEqual(msg, messages.BL4PSelfReport(
             localOrderID=42,

             selfReport=\
              {
              'paymentHash'         : paymentHash.hex(),
              'offerID'             : str(o1.ID),
              'receiverCryptoAmount': buyerCryptoAmount_str,
              'cryptoCurrency'      : 'btc',
              },
             ))
            task.setCallResult(messages.BL4PSelfReportResult(request=None, ))

            msg = await self.outgoingMessages.get()

            self.assertEqual(
                self.storage.sellTransactions, {
                    44: {
                        'ID': 44,
                        'sellOrder': 42,
                        'counterOffer': 43,
                        'status': ordertask.TX_STATUS_LOCKED,
                        'senderTimeoutDelta': 2000,
                        'lockedTimeoutDelta': 53,
                        'CLTVExpiryDelta': 23,
                        'buyerCryptoAmount': buyerCryptoAmount,
                        'sellerCryptoAmount': None,
                        'buyerFiatAmount': buyerFiatAmount,
                        'sellerFiatAmount': sellerFiatAmount,
                        'paymentHash': paymentHash,
                        'paymentPreimage': None,
                    }
                })

            #LN transaction gets performed
            self.assertEqual(
                msg,
                messages.LNPay(
                    localOrderID=42,
                    destinationNodeID='buyerAddress',
                    paymentHash=paymentHash,
                    recipientCryptoAmount=buyerCryptoAmount,
                    maxSenderCryptoAmount=maxSellerCryptoAmount,
                    minCLTVExpiryDelta=23,
                    fiatAmount=buyerFiatAmount,
                    offerID=6,
                ))
            task.setCallResult(
                messages.LNPayResult(
                    localOrderID=44,
                    paymentHash=paymentHash,
                    senderCryptoAmount=sellerCryptoAmount,
                    paymentPreimage=paymentPreimage,
                ))

            msg = await self.outgoingMessages.get()

            self.assertEqual(
                self.storage.sellTransactions, {
                    44: {
                        'ID': 44,
                        'sellOrder': 42,
                        'counterOffer': 43,
                        'status': ordertask.TX_STATUS_RECEIVED_PREIMAGE,
                        'senderTimeoutDelta': 2000,
                        'lockedTimeoutDelta': 53,
                        'CLTVExpiryDelta': 23,
                        'buyerCryptoAmount': buyerCryptoAmount,
                        'sellerCryptoAmount': sellerCryptoAmount,
                        'buyerFiatAmount': buyerFiatAmount,
                        'sellerFiatAmount': sellerFiatAmount,
                        'paymentHash': paymentHash,
                        'paymentPreimage': paymentPreimage,
                    }
                })
            remainingAmount -= sellerCryptoAmount
            if remainingAmount < 0:
                remainingAmount = 0
            order.setAmount.assert_called_once_with(remainingAmount)
            order.amount = remainingAmount
            order.updateOfferMaxAmounts()

            #Funds get received on BL4P
            self.assertEqual(
                msg,
                messages.BL4PReceive(
                    localOrderID=42,
                    paymentPreimage=paymentPreimage,
                ))
            task.setCallResult(messages.BL4PReceiveResult(request=None, ))

            await asyncio.sleep(0.1)

            self.assertEqual(
                self.storage.sellTransactions, {
                    44: {
                        'ID': 44,
                        'sellOrder': 42,
                        'counterOffer': 43,
                        'status': ordertask.TX_STATUS_FINISHED,
                        'senderTimeoutDelta': 2000,
                        'lockedTimeoutDelta': 53,
                        'CLTVExpiryDelta': 23,
                        'buyerCryptoAmount': buyerCryptoAmount,
                        'sellerCryptoAmount': sellerCryptoAmount,
                        'buyerFiatAmount': buyerFiatAmount,
                        'sellerFiatAmount': sellerFiatAmount,
                        'paymentHash': paymentHash,
                        'paymentPreimage': paymentPreimage,
                    }
                })
            self.assertEqual(task.transaction, None)

        await task.waitFinished()

        self.assertEqual(self.storage.sellOrders[orderID]['status'],
                         1)  #completed

    @asynciotest
    async def test_continueSellTransaction(self):
        orderID = ordertask.SellOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000000000  #mSatoshi    = 1234 BTC
        )
        order = ordertask.SellOrder(self.storage, orderID, 'sellerAddress')

        ID = ordertask.BuyOrder.create(
            self.storage,
            210000,  #mCent / BTC = 2.1 EUR/BTC
            100000  #mCent       = 1000 EUR
        )

        originalCounterOffer = ordertask.BuyOrder(self.storage, ID,
                                                  'buyerAddress')
        self.storage.counterOffers = \
        {40:
         {
         'ID': 40,
         'blob': originalCounterOffer.toPB2().SerializeToString()
         }
        }

        #status -> message:
        expectedMessages = \
        {
        ordertask.TX_STATUS_INITIAL: messages.BL4PStart(
          localOrderID=42,

          amount = 1200,
          sender_timeout_delta_ms = 34,
          locked_timeout_delta_s = 56,
          receiver_pays_fee = True,
          ),
        ordertask.TX_STATUS_STARTED: messages.BL4PSelfReport(
          localOrderID=42,

          selfReport = \
           {
           'paymentHash'         : '666f6f', #foo in hex
           'offerID'             : '43',
           'receiverCryptoAmount': '0.00000010000',
           'cryptoCurrency'      : 'btc',
           },
          ),
        ordertask.TX_STATUS_LOCKED: messages.LNPay(
          localOrderID=42,

                        destinationNodeID     = 'buyerAddress',
                        offerID               = 43,

                        recipientCryptoAmount = 10000,
                        maxSenderCryptoAmount = 10100,
                        fiatAmount            = 1200,

                        minCLTVExpiryDelta    = 78,

                        paymentHash           = b'foo',
          ),
        ordertask.TX_STATUS_RECEIVED_PREIMAGE: messages.BL4PReceive(
          localOrderID=42,

          paymentPreimage = b'bar',
          ),
        }

        for status, expectedMessage in expectedMessages.items():
            self.storage.sellTransactions = \
            {
            41:
             {
             'ID': 41,
             'sellOrder': orderID,
             'counterOffer': 40,
             'status': status,

             'buyerFiatAmount': 1200,
             'buyerCryptoAmount': 10000,

             'senderTimeoutDelta': 34,
             'lockedTimeoutDelta': 56,
             'CLTVExpiryDelta'   : 78,

             'paymentHash': b'foo',
             'paymentPreimage': b'bar',
             }
            }

            task = ordertask.OrderTask(self.client, self.storage, order)
            task.startup()

            msg = await self.outgoingMessages.get()
            self.assertEqual(msg, expectedMessage)

            await task.shutdown()

        #Database inconsistency exception:
        self.storage.sellTransactions = \
        {
        41:
         {
         'ID': 41,
         'sellOrder': orderID,
         'counterOffer': 40,
         'status': 100,
         }
        }

        task = ordertask.OrderTask(self.client, self.storage, order)
        with self.assertRaises(Exception):
            await task.continueSellTransaction()

    @asynciotest
    async def test_canceledSellTransaction(self):
        orderID = ordertask.SellOrder.create(
            self.storage,
            190000,  #mCent / BTC = 1.9 EUR/BTC
            123400000000000  #mSatoshi    = 1234 BTC
        )
        order = ordertask.SellOrder(self.storage, orderID, 'sellerAddress')

        ID = ordertask.BuyOrder.create(
            self.storage,
            210000,  #mCent / BTC = 2.1 EUR/BTC
            100000  #mCent       = 1000 EUR
        )

        originalCounterOffer = ordertask.BuyOrder(self.storage, ID,
                                                  'buyerAddress')
        self.storage.counterOffers = \
        {40:
         {
         'ID': 40,
         'blob': originalCounterOffer.toPB2().SerializeToString()
         }
        }

        #An ongoing tx that is just about to be sent over Lightning:
        self.storage.sellTransactions = \
        {
        41:
         {
         'ID': 41,
         'sellOrder': orderID,
         'counterOffer': 40,
         'status': ordertask.TX_STATUS_LOCKED,

         'buyerFiatAmount': 1200,
         'buyerCryptoAmount': 10000,

         'senderTimeoutDelta': 34,
         'lockedTimeoutDelta': 56,
         'CLTVExpiryDelta'   : 78,

         'paymentHash': b'foo',
         }
        }

        task = ordertask.OrderTask(self.client, self.storage, order)
        task.startup()

        msg = await self.outgoingMessages.get()
        self.assertEqual(
            msg,
            messages.LNPay(
                localOrderID=42,
                destinationNodeID='buyerAddress',
                offerID=43,
                recipientCryptoAmount=10000,
                maxSenderCryptoAmount=10100,
                fiatAmount=1200,
                minCLTVExpiryDelta=78,
                paymentHash=b'foo',
            ))

        #Lightning tx ends up canceled:
        task.setCallResult(
            messages.LNPayResult(
                localOrderID=0,
                paymentHash=b'foo',
                senderCryptoAmount=10500,
                paymentPreimage=None,
            ))

        msg = await self.outgoingMessages.get()
        self.assertEqual(
            msg, messages.BL4PCancelStart(
                localOrderID=42,
                paymentHash=b'foo',
            ))

        task.setCallResult(messages.BL4PCancelStartResult(request=None, ))

        msg = await self.outgoingMessages.get()

        self.maxDiff = None
        self.assertEqual(
            self.storage.sellTransactions, {
                41: {
                    'ID': 41,
                    'sellOrder': orderID,
                    'counterOffer': 40,
                    'status': ordertask.TX_STATUS_CANCELED,
                    'senderTimeoutDelta': 34,
                    'lockedTimeoutDelta': 56,
                    'CLTVExpiryDelta': 78,
                    'buyerCryptoAmount': 10000,
                    'buyerFiatAmount': 1200,
                    'paymentHash': b'foo',
                }
            })
        self.assertEqual(task.transaction, None)

        await task.shutdown()

    @asynciotest
    async def test_setCallResult_exceptions(self):
        task = ordertask.OrderTask(None, None, None)
        with self.assertRaises(ordertask.UnexpectedResult):
            task.setCallResult(3.0)

        waitTask = asyncio.ensure_future(task.waitForIncomingMessage(int))
        await asyncio.sleep(0.1)

        with self.assertRaises(ordertask.UnexpectedResult):
            task.setCallResult(3.0)

        task.setCallResult(6)

        value = await waitTask
        self.assertEqual(value, 6)

    @asynciotest
    async def test_doTrading_canceledOrder(self):
        orderID = ordertask.BuyOrder.create(self.storage, 2, 1234)
        order = ordertask.BuyOrder(self.storage, orderID, 'lnAddress')
        task = ordertask.OrderTask(Mock(), None, order)
        order.status = ORDER_STATUS_CANCEL_REQUESTED

        async def dummy():
            pass

        task.continueBuyTransaction = dummy
        task.publishOffer = dummy
        task.waitForIncomingTransaction = dummy
        task.unpublishOffer = dummy
        await task.doTrading()

        self.assertEqual(order.status, ORDER_STATUS_CANCELED)

    @asynciotest
    async def test_doTrading_exceptions(self):
        #Unsupported order type:
        task = ordertask.OrderTask(None, None, None)
        with patch.object(logging, 'exception', Mock()) as logException:
            with self.assertRaises(Exception):
                await task.doTrading()
            logException.assert_called_once()

        #Canceled exception:
        orderID = ordertask.BuyOrder.create(self.storage, 2, 1234)
        order = ordertask.BuyOrder(self.storage, orderID, 'lnAddress')
        task = ordertask.OrderTask(Mock(), None, order)

        async def continueBuyTransaction():
            raise asyncio.CancelledError()

        with patch.object(task, 'continueBuyTransaction',
                          continueBuyTransaction):
            #No exception:
            await task.doTrading()

    @asynciotest
    async def test_doOfferSearch_loop(self):
        order = Mock()
        order.ID = 42
        order.remoteOfferID = None
        task = ordertask.OrderTask(self.client, None, order)

        done = []

        async def doTransaction():
            self.assertEqual(done, [])
            done.append(True)

        task.doTransaction = doTransaction

        searchTask = asyncio.ensure_future(task.doOfferSearch())

        #No BL4P connection:
        received = []

        def raiseNoMessageHandler(msg):
            received.append(msg)
            raise messages.NoMessageHandler()

        with patch.object(self.client, 'handleOutgoingMessage',
                          raiseNoMessageHandler):
            await asyncio.sleep(1)
        self.assertEqual(
            received,
            [messages.BL4PFindOffers(
                localOrderID=42,
                query=order,
            )])

        #No results:
        msg = await self.outgoingMessages.get()
        self.assertEqual(
            msg, messages.BL4PFindOffers(
                localOrderID=42,
                query=order,
            ))
        task.setCallResult(
            messages.BL4PFindOffersResult(
                request=None,
                offers=[],
            ))

        #Offer gets published:
        msg = await self.outgoingMessages.get()
        self.assertEqual(msg,
                         messages.BL4PAddOffer(
                             localOrderID=42,
                             offer=order,
                         ))
        task.setCallResult(messages.BL4PAddOfferResult(
            request=None,
            ID=6,
        ))

        #No results again:
        t0 = time.time()
        msg = await self.outgoingMessages.get()
        t1 = time.time()
        self.assertAlmostEqual(t1 - t0, 1.0, places=2)
        self.assertEqual(
            msg, messages.BL4PFindOffers(
                localOrderID=42,
                query=order,
            ))
        task.setCallResult(
            messages.BL4PFindOffersResult(
                request=None,
                offers=[],
            ))
        self.assertEqual(order.remoteOfferID, 6)

        #Multiple results:
        #Non-matching offers returned by BL4P must not be selected.
        offer0 = Mock()
        offer1 = Mock()
        offer2 = Mock()
        offer0.matches = Mock(return_value=False)
        offer1.matches = Mock(return_value=True)
        offer2.matches = Mock(return_value=True)
        msg = await self.outgoingMessages.get()
        self.assertEqual(
            msg, messages.BL4PFindOffers(
                localOrderID=42,
                query=order,
            ))
        task.setCallResult(
            messages.BL4PFindOffersResult(
                request=None,
                offers=[offer0, offer1, offer2],
            ))

        await searchTask

        self.assertEqual(done, [True])
        self.assertEqual(task.counterOffer, offer1)

        offer0.matches.assert_called_with(order)
        offer1.matches.assert_called_with(order)
        offer2.matches.assert_called_with(order)