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()
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):

View file

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

View file

@ -140,6 +140,8 @@ class BaseLedger(metaclass=LedgerRegistry):
self._header_processing_lock = asyncio.Lock()
self._address_update_locks: Dict[str, asyncio.Lock] = {}
self.coin_selection_strategy = None
@classmethod
def get_id(cls):
return '{}_{}'.format(cls.symbol.lower(), cls.network_name.lower())
@ -212,7 +214,7 @@ class BaseLedger(metaclass=LedgerRegistry):
txos, amount,
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:
await self.reserve_outputs(s.txo for s in spendables)
return spendables

View file

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