def test_random_draw(self):
     utxo_pool = self.estimates(utxo(2 * CENT), utxo(3 * CENT),
                                utxo(4 * CENT))
     selector = CoinSelector(utxo_pool, CENT, 0, '\x00')
     match = selector.select()
     self.assertEqual([2 * CENT], [c.txo.amount for c in match])
     self.assertFalse(selector.exact_match)
 def test_exact_match(self):
     fee = utxo(CENT).get_estimator(self.ledger).fee
     utxo_pool = self.estimates(utxo(CENT + fee), utxo(CENT),
                                utxo(CENT - fee))
     selector = CoinSelector(utxo_pool, CENT, 0)
     match = selector.select()
     self.assertEqual([CENT + fee], [c.txo.amount for c in match])
     self.assertTrue(selector.exact_match)
    def test_branch_and_bound_coin_selection(self):
        self.ledger.fee_per_byte = 0

        utxo_pool = self.estimates(utxo(1 * CENT), utxo(2 * CENT),
                                   utxo(3 * CENT), utxo(4 * CENT))

        # Select 1 Cent
        self.assertEqual([1 * CENT], search(utxo_pool, 1 * CENT, 0.5 * CENT))

        # Select 2 Cent
        self.assertEqual([2 * CENT], search(utxo_pool, 2 * CENT, 0.5 * CENT))

        # Select 5 Cent
        self.assertEqual([3 * CENT, 2 * CENT],
                         search(utxo_pool, 5 * CENT, 0.5 * CENT))

        # Select 11 Cent, not possible
        self.assertIsNone(search(utxo_pool, 11 * CENT, 0.5 * CENT))

        # Select 10 Cent
        utxo_pool += self.estimates(utxo(5 * CENT))
        self.assertEqual([4 * CENT, 3 * CENT, 2 * CENT, 1 * CENT],
                         search(utxo_pool, 10 * CENT, 0.5 * CENT))

        # Negative effective value
        # Select 10 Cent but have 1 Cent not be possible because too small
        # TODO: bitcoin has [5, 3, 2]
        self.assertEqual([4 * CENT, 3 * CENT, 2 * CENT, 1 * CENT],
                         search(utxo_pool, 10 * CENT, 5000))

        # Select 0.25 Cent, not possible
        self.assertIsNone(search(utxo_pool, 0.25 * CENT, 0.5 * CENT))

        # Iteration exhaustion test
        utxo_pool, target = self.make_hard_case(17)
        selector = CoinSelector(utxo_pool, target, 0)
        self.assertIsNone(selector.branch_and_bound())
        self.assertEqual(selector.tries, MAXIMUM_TRIES)  # Should exhaust
        utxo_pool, target = self.make_hard_case(14)
        self.assertIsNotNone(search(utxo_pool, target,
                                    0))  # Should not exhaust

        # Test same value early bailout optimization
        utxo_pool = self.estimates([
            utxo(7 * CENT),
            utxo(7 * CENT),
            utxo(7 * CENT),
            utxo(7 * CENT),
            utxo(2 * CENT)
        ] + [utxo(5 * CENT)] * 50000)
        self.assertEqual([7 * CENT, 7 * CENT, 7 * CENT, 7 * CENT, 2 * CENT],
                         search(utxo_pool, 30 * CENT, 5000))

        # Select 1 Cent with pool of only greater than 5 Cent
        utxo_pool = self.estimates(utxo(i * CENT) for i in range(5, 21))
        for _ in range(100):
            self.assertIsNone(search(utxo_pool, 1 * CENT, 2 * CENT))
Exemple #4
0
 def test_pick(self):
     utxo_pool = self.estimates(
         utxo(1 * CENT),
         utxo(1 * CENT),
         utxo(3 * CENT),
         utxo(5 * CENT),
         utxo(10 * CENT),
     )
     selector = CoinSelector(utxo_pool, 3 * CENT, 0)
     match = selector.select()
     self.assertEqual([5 * CENT], [c.txo.amount for c in match])
Exemple #5
0
 async def get_spendable_utxos(self, amount: int, funding_accounts):
     async with self._utxo_reservation_lock:
         txos = await self.get_effective_amount_estimators(funding_accounts)
         selector = CoinSelector(
             txos, amount,
             self.transaction_class.output_class.pay_pubkey_hash(
                 COIN, NULL_HASH32).get_fee(self))
         spendables = selector.select()
         if spendables:
             await self.reserve_outputs(s.txo for s in spendables)
         return spendables
Exemple #6
0
 def get_spendable_utxos(self, amount: int, funding_accounts):
     yield self._utxo_reservation_lock.acquire()
     try:
         txos = yield self.get_effective_amount_estimators(funding_accounts)
         selector = CoinSelector(
             txos, amount,
             self.transaction_class.output_class.pay_pubkey_hash(
                 COIN, NULL_HASH32).get_fee(self))
         spendables = selector.select()
         if spendables:
             yield self.reserve_outputs(s.txo for s in spendables)
     except Exception:
         log.exception('Failed to get spendable utxos:')
         raise
     finally:
         self._utxo_reservation_lock.release()
     defer.returnValue(spendables)
Exemple #7
0
    def pay(cls, outputs, funding_accounts, change_account, reserve_outputs=True):
        # type: (List[BaseOutput], List[torba.baseaccount.BaseAccount], torba.baseaccount.BaseAccount) -> defer.Deferred
        """ Efficiently spend utxos from funding_accounts to cover the new outputs. """

        tx = cls().add_outputs(outputs)
        ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
        amount = tx.output_sum + ledger.get_transaction_base_fee(tx)
        txos = yield ledger.get_effective_amount_estimators(funding_accounts)
        selector = CoinSelector(
            txos, amount,
            ledger.get_input_output_fee(
                cls.output_class.pay_pubkey_hash(COIN, NULL_HASH)
            )
        )

        spendables = selector.select()
        if not spendables:
            raise ValueError('Not enough funds to cover this transaction.')

        reserved_outputs = [s.txo.txoid for s in spendables]
        if reserve_outputs:
            yield ledger.db.reserve_spent_outputs(reserved_outputs)

        try:
            spent_sum = sum(s.effective_amount for s in spendables)
            if spent_sum > amount:
                change_address = yield change_account.change.get_or_create_usable_address()
                change_hash160 = change_account.ledger.address_to_hash160(change_address)
                change_amount = spent_sum - amount
                tx.add_outputs([cls.output_class.pay_pubkey_hash(change_amount, change_hash160)])

            tx.add_inputs([s.txi for s in spendables])
            yield tx.sign(funding_accounts)

        except Exception:
            if reserve_outputs:
                yield ledger.db.release_reserved_outputs(reserved_outputs)
            raise

        defer.returnValue(tx)
 def test_skip_binary_search_if_total_not_enough(self):
     fee = utxo(CENT).get_estimator(self.ledger).fee
     big_pool = self.estimates(utxo(CENT + fee) for _ in range(100))
     selector = CoinSelector(big_pool, 101 * CENT, 0)
     self.assertIsNone(selector.select())
     self.assertEqual(selector.tries, 0)  # Never tried.
     # check happy path
     selector = CoinSelector(big_pool, 100 * CENT, 0)
     self.assertEqual(len(selector.select()), 100)
     self.assertEqual(selector.tries, 201)
 def test_empty_coins(self):
     self.assertIsNone(CoinSelector([], 0, 0).select())
def search(*args, **kwargs):
    selection = CoinSelector(*args, **kwargs).branch_and_bound()
    return [o.txo.amount for o in selection] if selection else selection
Exemple #11
0
 def test_empty_coins(self):
     self.assertEqual(CoinSelector([], 0, 0).select(), [])