add option to only use confirmed utxos
This commit is contained in:
parent
c30e905e19
commit
f2bd0edc51
4 changed files with 37 additions and 4 deletions
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue