Merge bea20892f7
into eb5da9511e
This commit is contained in:
commit
5f16fa505f
3 changed files with 68 additions and 24 deletions
lbry/wallet
tests
|
@ -808,25 +808,34 @@ class Transaction:
|
|||
tx.get_base_fee(ledger) +
|
||||
tx.get_total_output_sum(ledger)
|
||||
)
|
||||
cost_of_change = (
|
||||
tx.get_base_fee(ledger) +
|
||||
Output.pay_pubkey_hash(COIN, NULL_HASH32).get_fee(ledger)
|
||||
)
|
||||
# value of the inputs less the cost to spend those inputs
|
||||
payment = tx.get_effective_input_sum(ledger)
|
||||
|
||||
try:
|
||||
|
||||
for _ in range(5):
|
||||
for i in range(2):
|
||||
|
||||
if payment < cost:
|
||||
if payment < cost or (i > 0 and not tx._outputs):
|
||||
deficit = cost - payment
|
||||
# this condition and the outer range(2) loop cover an edge case
|
||||
# whereby a single input is just enough to cover the fee and
|
||||
# has some change left over, but the change left over is less
|
||||
# than the cost_of_change: thus the input is completely
|
||||
# consumed and no output is added, which is an invalid tx.
|
||||
# to be able to spend this input we must increase the cost
|
||||
# in order to make a change output > DUST.
|
||||
if i > 0 and not tx._outputs:
|
||||
deficit += (cost_of_change + DUST + 1)
|
||||
spendables = await ledger.get_spendable_utxos(deficit, funding_accounts)
|
||||
if not spendables:
|
||||
raise InsufficientFundsError()
|
||||
payment += sum(s.effective_amount for s in spendables)
|
||||
tx.add_inputs(s.txi for s in spendables)
|
||||
|
||||
cost_of_change = (
|
||||
tx.get_base_fee(ledger) +
|
||||
Output.pay_pubkey_hash(COIN, NULL_HASH32).get_fee(ledger)
|
||||
)
|
||||
if payment > cost:
|
||||
change = payment - cost
|
||||
change_amount = change - cost_of_change
|
||||
|
@ -839,17 +848,11 @@ class Transaction:
|
|||
|
||||
if tx._outputs:
|
||||
break
|
||||
# this condition and the outer range(5) loop cover an edge case
|
||||
# whereby a single input is just enough to cover the fee and
|
||||
# has some change left over, but the change left over is less
|
||||
# than the cost_of_change: thus the input is completely
|
||||
# consumed and no output is added, which is an invalid tx.
|
||||
# to be able to spend this input we must increase the cost
|
||||
# of the TX and run through the balance algorithm a second time
|
||||
# adding an extra input and change output, making tx valid.
|
||||
# we do this 5 times in case the other UTXOs added are also
|
||||
# less than the fee, after 5 attempts we give up and go home
|
||||
cost += cost_of_change + 1
|
||||
# We need to run through the balance algorithm a second time
|
||||
# adding extra inputs and change output, making tx valid.
|
||||
|
||||
if not tx._outputs:
|
||||
raise InsufficientFundsError()
|
||||
|
||||
if sign:
|
||||
await tx.sign(funding_accounts)
|
||||
|
|
|
@ -2119,7 +2119,7 @@ class StreamCommands(ClaimTestCase):
|
|||
tx = await self.stream_create(bid='0.0001')
|
||||
await self.assertBalance(self.account, '9.979793')
|
||||
await self.stream_abandon(self.get_claim_id(tx))
|
||||
await self.assertBalance(self.account, '9.97968399')
|
||||
await self.assertBalance(self.account, '9.979712')
|
||||
|
||||
async def test_publish(self):
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ import shutil
|
|||
from binascii import hexlify, unhexlify
|
||||
from itertools import cycle
|
||||
|
||||
from lbry.error import InsufficientFundsError
|
||||
from lbry.testcase import AsyncioTestCase
|
||||
from lbry.wallet.constants import CENT, COIN, NULL_HASH32
|
||||
from lbry.wallet.constants import CENT, COIN, DUST, NULL_HASH32
|
||||
from lbry.wallet import Wallet, Account, Ledger, Database, Headers, Transaction, Output, Input
|
||||
|
||||
|
||||
NULL_HASH = b'\x00'*32
|
||||
FEE_PER_BYTE = 50
|
||||
FEE_PER_CHAR = 200000
|
||||
|
@ -395,12 +395,12 @@ class TransactionIOBalancing(AsyncioTestCase):
|
|||
return utxos
|
||||
|
||||
@staticmethod
|
||||
def inputs(tx):
|
||||
return [round(i.amount/COIN, 2) for i in tx.inputs]
|
||||
def inputs(tx, precision=2):
|
||||
return [round(i.amount/COIN, precision) for i in tx.inputs]
|
||||
|
||||
@staticmethod
|
||||
def outputs(tx):
|
||||
return [round(o.amount/COIN, 2) for o in tx.outputs]
|
||||
def outputs(tx, precision=2):
|
||||
return [round(o.amount/COIN, precision) for o in tx.outputs]
|
||||
|
||||
async def test_basic_use_cases(self):
|
||||
self.ledger.fee_per_byte = int(.01*CENT)
|
||||
|
@ -532,3 +532,44 @@ class TransactionIOBalancing(AsyncioTestCase):
|
|||
self.assertListEqual([0.01, 1], self.inputs(tx))
|
||||
# change is now needed to consume extra input
|
||||
self.assertListEqual([0.97], self.outputs(tx))
|
||||
|
||||
async def test_liquidate_at_loss(self):
|
||||
#self.ledger.coin_selection_strategy = 'sqlite'
|
||||
self.ledger.fee_per_byte = int(0.01*CENT)
|
||||
|
||||
# Create UTXOs with values large enough that they can be spent.
|
||||
utxos = await self.create_utxos([a/COIN for a in range(1490*DUST, 1510*DUST, int(DUST/10))])
|
||||
|
||||
tx = await self.tx(
|
||||
[self.txi(self.txo(0.01))], # inputs
|
||||
[] # outputs
|
||||
)
|
||||
|
||||
# A very tiny amount of change is generated as the only output.
|
||||
self.assertListEqual([1100], [o.amount for o in tx.outputs])
|
||||
# A large number of additional UTXOs are added to cover fees.
|
||||
self.assertListEqual([
|
||||
1000000, 1509900, 1509800, 1509700, 1509600, 1509500, 1509400, 1509300, 1509200, 1509100,
|
||||
1509000, 1508900, 1508800, 1508700, 1508600, 1508500, 1508400, 1508300, 1508200, 1508100,
|
||||
1494600, 1494400, 1508000, 1507900, 1507800, 1507700, 1507600, 1507500, 1507400, 1507300,
|
||||
1507200, 1507100, 1507000, 1506900, 1506800, 1506700, 1506600, 1506500, 1506400, 1506300,
|
||||
1506200, 1505200, 1501000],
|
||||
[i.amount for i in tx.inputs])
|
||||
self.assertIn(tx.size, range(6350, 6430))
|
||||
self.assertEqual(64300000, tx.fee)
|
||||
|
||||
await self.ledger.release_outputs(utxos)
|
||||
|
||||
async def test_liquidate_at_loss_tiny_utxos(self):
|
||||
#self.ledger.coin_selection_strategy = 'sqlite'
|
||||
self.ledger.fee_per_byte = int(0.01*CENT)
|
||||
|
||||
# Create UTXOs with values so tiny that they cannot be spent.
|
||||
utxos = await self.create_utxos([a/COIN for a in range(1460*DUST, 1490*DUST, int(DUST/10))])
|
||||
|
||||
with self.assertRaises(InsufficientFundsError):
|
||||
tx = await self.tx(
|
||||
[self.txi(self.txo(0.01))], # inputs
|
||||
[] # outputs
|
||||
)
|
||||
self.assertFalse([i.amount for i in tx.inputs])
|
Loading…
Add table
Reference in a new issue