add option to only use confirmed utxos

This commit is contained in:
Alex Grintsvayg 2019-06-17 18:29:17 -04:00 committed by Lex Berezhny
parent c30e905e19
commit f2bd0edc51
4 changed files with 37 additions and 4 deletions

View file

@ -85,6 +85,16 @@ class TestCoinSelectionTests(BaseSelectionTestCase):
match = selector.select() match = selector.select()
self.assertEqual([5*CENT], [c.txo.amount for c in match]) self.assertEqual([5*CENT], [c.txo.amount for c in match])
def test_prefer_confirmed_strategy(self):
utxo_pool = self.estimates(
utxo(11*CENT, height=5),
utxo(11*CENT, height=0),
utxo(11*CENT, height=-2),
utxo(11*CENT, height=5),
)
selector = CoinSelector(utxo_pool, 20*CENT, 0)
match = selector.select("confirmed_only")
self.assertEqual([5,5], [c.txo.tx_ref.height for c in match])
class TestOfficialBitcoinCoinSelectionTests(BaseSelectionTestCase): class TestOfficialBitcoinCoinSelectionTests(BaseSelectionTestCase):

View file

@ -14,8 +14,8 @@ FEE_PER_BYTE = 50
FEE_PER_CHAR = 200000 FEE_PER_CHAR = 200000
def get_output(amount=CENT, pubkey_hash=NULL_HASH): def get_output(amount=CENT, pubkey_hash=NULL_HASH, height=-2):
return ledger_class.transaction_class() \ return ledger_class.transaction_class(height=height) \
.add_outputs([ledger_class.transaction_class.output_class.pay_pubkey_hash(amount, pubkey_hash)]) \ .add_outputs([ledger_class.transaction_class.output_class.pay_pubkey_hash(amount, pubkey_hash)]) \
.outputs[0] .outputs[0]

View file

@ -140,6 +140,8 @@ class BaseLedger(metaclass=LedgerRegistry):
self._header_processing_lock = asyncio.Lock() self._header_processing_lock = asyncio.Lock()
self._address_update_locks: Dict[str, asyncio.Lock] = {} self._address_update_locks: Dict[str, asyncio.Lock] = {}
self.coin_selection_strategy = None
@classmethod @classmethod
def get_id(cls): def get_id(cls):
return '{}_{}'.format(cls.symbol.lower(), cls.network_name.lower()) return '{}_{}'.format(cls.symbol.lower(), cls.network_name.lower())
@ -212,7 +214,7 @@ class BaseLedger(metaclass=LedgerRegistry):
txos, amount, txos, amount,
self.transaction_class.output_class.pay_pubkey_hash(COIN, NULL_HASH32).get_fee(self) self.transaction_class.output_class.pay_pubkey_hash(COIN, NULL_HASH32).get_fee(self)
) )
spendables = selector.select() spendables = selector.select(self.coin_selection_strategy)
if spendables: if spendables:
await self.reserve_outputs(s.txo for s in spendables) await self.reserve_outputs(s.txo for s in spendables)
return spendables return spendables

View file

@ -5,6 +5,12 @@ from torba.client import basetransaction
MAXIMUM_TRIES = 100000 MAXIMUM_TRIES = 100000
STRATEGIES = []
def strategy(method):
STRATEGIES.append(method.__name__)
return method
class CoinSelector: class CoinSelector:
@ -20,17 +26,30 @@ class CoinSelector:
if seed is not None: if seed is not None:
self.random.seed(seed, version=1) self.random.seed(seed, version=1)
def select(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]: def select(self, strategy: str = None) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]:
if not self.txos: if not self.txos:
return [] return []
if self.target > self.available: if self.target > self.available:
return [] return []
if strategy is not None:
return getattr(self, strategy)()
return ( return (
self.branch_and_bound() or self.branch_and_bound() or
self.closest_match() or self.closest_match() or
self.random_draw() self.random_draw()
) )
@strategy
def confirmed_only(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]:
self.txos = [t for t in self.txos if t.txo.tx_ref.height > 0] or self.txos
self.available = sum(c.effective_amount for c in self.txos)
return (
self.branch_and_bound() or
self.closest_match() or
self.random_draw()
)
@strategy
def branch_and_bound(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]: def branch_and_bound(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]:
# see bitcoin implementation for more info: # see bitcoin implementation for more info:
# https://github.com/bitcoin/bitcoin/blob/master/src/wallet/coinselection.cpp # https://github.com/bitcoin/bitcoin/blob/master/src/wallet/coinselection.cpp
@ -89,6 +108,7 @@ class CoinSelector:
return [] return []
@strategy
def closest_match(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]: def closest_match(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]:
""" Pick one UTXOs that is larger than the target but with the smallest change. """ """ Pick one UTXOs that is larger than the target but with the smallest change. """
target = self.target + self.cost_of_change target = self.target + self.cost_of_change
@ -101,6 +121,7 @@ class CoinSelector:
smallest_change, best_match = change, txo smallest_change, best_match = change, txo
return [best_match] if best_match else [] return [best_match] if best_match else []
@strategy
def random_draw(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]: def random_draw(self) -> List[basetransaction.BaseOutputEffectiveAmountEstimator]:
""" Accumulate UTXOs at random until there is enough to cover the target. """ """ Accumulate UTXOs at random until there is enough to cover the target. """
target = self.target + self.cost_of_change target = self.target + self.cost_of_change