mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
RBF batching: some fixes
This commit is contained in:
parent
f55db2f90b
commit
71ac3bb305
5 changed files with 35 additions and 21 deletions
|
@ -25,7 +25,7 @@ import threading
|
||||||
import asyncio
|
import asyncio
|
||||||
import itertools
|
import itertools
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
|
from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
|
||||||
|
@ -457,7 +457,7 @@ class AddressSynchronizer(PrintError):
|
||||||
self.spent_outpoints = defaultdict(dict)
|
self.spent_outpoints = defaultdict(dict)
|
||||||
self.history = {}
|
self.history = {}
|
||||||
self.verified_tx = {}
|
self.verified_tx = {}
|
||||||
self.transactions = {}
|
self.transactions = {} # type: Dict[str, Transaction]
|
||||||
self.save_transactions()
|
self.save_transactions()
|
||||||
|
|
||||||
def get_txpos(self, tx_hash):
|
def get_txpos(self, tx_hash):
|
||||||
|
@ -484,12 +484,6 @@ class AddressSynchronizer(PrintError):
|
||||||
self.threadlocal_cache.local_height = orig_val
|
self.threadlocal_cache.local_height = orig_val
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def get_unconfirmed_tx(self):
|
|
||||||
for tx_hash, tx_mined_status, delta, balance in self.get_history():
|
|
||||||
if tx_mined_status.conf <= 0 and delta < 0:
|
|
||||||
tx = self.transactions.get(tx_hash)
|
|
||||||
return tx
|
|
||||||
|
|
||||||
@with_local_height_cached
|
@with_local_height_cached
|
||||||
def get_history(self, domain=None):
|
def get_history(self, domain=None):
|
||||||
# get domain
|
# get domain
|
||||||
|
|
|
@ -84,8 +84,8 @@ def strip_unneeded(bkts, sufficient_funds):
|
||||||
for i in range(len(bkts)):
|
for i in range(len(bkts)):
|
||||||
if not sufficient_funds(bkts[i + 1:]):
|
if not sufficient_funds(bkts[i + 1:]):
|
||||||
return bkts[i:]
|
return bkts[i:]
|
||||||
# Shouldn't get here
|
# none of the buckets are needed
|
||||||
return bkts
|
return []
|
||||||
|
|
||||||
class CoinChooserBase(PrintError):
|
class CoinChooserBase(PrintError):
|
||||||
|
|
||||||
|
@ -203,12 +203,13 @@ class CoinChooserBase(PrintError):
|
||||||
|
|
||||||
# Copy the outputs so when adding change we don't modify "outputs"
|
# Copy the outputs so when adding change we don't modify "outputs"
|
||||||
tx = Transaction.from_io(inputs[:], outputs[:])
|
tx = Transaction.from_io(inputs[:], outputs[:])
|
||||||
v = tx.input_value()
|
input_value = tx.input_value()
|
||||||
|
|
||||||
# Weight of the transaction with no inputs and no change
|
# Weight of the transaction with no inputs and no change
|
||||||
# Note: this will use legacy tx serialization as the need for "segwit"
|
# Note: this will use legacy tx serialization as the need for "segwit"
|
||||||
# would be detected from inputs. The only side effect should be that the
|
# would be detected from inputs. The only side effect should be that the
|
||||||
# marker and flag are excluded, which is compensated in get_tx_weight()
|
# marker and flag are excluded, which is compensated in get_tx_weight()
|
||||||
|
# FIXME calculation will be off by this (2 wu) in case of RBF batching
|
||||||
base_weight = tx.estimated_weight()
|
base_weight = tx.estimated_weight()
|
||||||
spent_amount = tx.output_value()
|
spent_amount = tx.output_value()
|
||||||
|
|
||||||
|
@ -232,7 +233,7 @@ class CoinChooserBase(PrintError):
|
||||||
def sufficient_funds(buckets):
|
def sufficient_funds(buckets):
|
||||||
'''Given a list of buckets, return True if it has enough
|
'''Given a list of buckets, return True if it has enough
|
||||||
value to pay for the transaction'''
|
value to pay for the transaction'''
|
||||||
total_input = v + sum(bucket.value for bucket in buckets)
|
total_input = input_value + sum(bucket.value for bucket in buckets)
|
||||||
total_weight = get_tx_weight(buckets)
|
total_weight = get_tx_weight(buckets)
|
||||||
return total_input >= spent_amount + fee_estimator_w(total_weight)
|
return total_input >= spent_amount + fee_estimator_w(total_weight)
|
||||||
|
|
||||||
|
|
|
@ -2763,7 +2763,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
||||||
batch_rbf_cb.setChecked(self.config.get('batch_rbf', False))
|
batch_rbf_cb.setChecked(self.config.get('batch_rbf', False))
|
||||||
batch_rbf_cb.setEnabled(use_rbf)
|
batch_rbf_cb.setEnabled(use_rbf)
|
||||||
batch_rbf_cb.setToolTip(
|
batch_rbf_cb.setToolTip(
|
||||||
_('If you check this box, your unconfirmed transactios will be consolidated in a single transaction') + '\n' + \
|
_('If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' + \
|
||||||
_('This will save fees.'))
|
_('This will save fees.'))
|
||||||
def on_batch_rbf(x):
|
def on_batch_rbf(x):
|
||||||
self.config.set_key('batch_rbf', bool(x))
|
self.config.set_key('batch_rbf', bool(x))
|
||||||
|
|
|
@ -864,7 +864,7 @@ class Transaction:
|
||||||
@classmethod
|
@classmethod
|
||||||
def serialize_witness(self, txin, estimate_size=False):
|
def serialize_witness(self, txin, estimate_size=False):
|
||||||
_type = txin['type']
|
_type = txin['type']
|
||||||
if not self.is_segwit_input(txin) and not self.is_input_value_needed(txin):
|
if not self.is_segwit_input(txin) and not txin['type'] == 'address':
|
||||||
return '00'
|
return '00'
|
||||||
if _type == 'coinbase':
|
if _type == 'coinbase':
|
||||||
return txin['witness']
|
return txin['witness']
|
||||||
|
@ -902,10 +902,6 @@ class Transaction:
|
||||||
def is_segwit_inputtype(cls, txin_type):
|
def is_segwit_inputtype(cls, txin_type):
|
||||||
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
|
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def is_input_value_needed(cls, txin):
|
|
||||||
return cls.is_segwit_input(txin) or txin['type'] == 'address'
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def guess_txintype_from_address(cls, addr):
|
def guess_txintype_from_address(cls, addr):
|
||||||
# It's not possible to tell the script type in general
|
# It's not possible to tell the script type in general
|
||||||
|
|
|
@ -536,6 +536,29 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
def dust_threshold(self):
|
def dust_threshold(self):
|
||||||
return dust_threshold(self.network)
|
return dust_threshold(self.network)
|
||||||
|
|
||||||
|
def get_unconfirmed_base_tx_for_batching(self) -> Optional[Transaction]:
|
||||||
|
candidate = None
|
||||||
|
for tx_hash, tx_mined_status, delta, balance in self.get_history():
|
||||||
|
# tx should not be mined yet
|
||||||
|
if tx_mined_status.conf > 0: continue
|
||||||
|
# tx should be "outgoing" from wallet
|
||||||
|
if delta >= 0: continue
|
||||||
|
tx = self.transactions.get(tx_hash)
|
||||||
|
if not tx: continue
|
||||||
|
# is_mine outputs should not be spent yet
|
||||||
|
# to avoid cancelling our own dependent transactions
|
||||||
|
for output_idx, o in enumerate(tx.outputs()):
|
||||||
|
if self.is_mine(o.address) and self.spent_outpoints[tx.txid()].get(output_idx):
|
||||||
|
continue
|
||||||
|
# prefer txns already in mempool (vs local)
|
||||||
|
if tx_mined_status.height == TX_HEIGHT_LOCAL:
|
||||||
|
candidate = tx
|
||||||
|
continue
|
||||||
|
# tx must have opted-in for RBF
|
||||||
|
if tx.is_final(): continue
|
||||||
|
return tx
|
||||||
|
return candidate
|
||||||
|
|
||||||
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
|
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
|
||||||
change_addr=None, is_sweep=False):
|
change_addr=None, is_sweep=False):
|
||||||
# check outputs
|
# check outputs
|
||||||
|
@ -592,8 +615,8 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
max_change = self.max_change_outputs if self.multiple_change else 1
|
max_change = self.max_change_outputs if self.multiple_change else 1
|
||||||
coin_chooser = coinchooser.get_coin_chooser(config)
|
coin_chooser = coinchooser.get_coin_chooser(config)
|
||||||
# If there is an unconfirmed RBF tx, merge with it
|
# If there is an unconfirmed RBF tx, merge with it
|
||||||
base_tx = self.get_unconfirmed_tx()
|
base_tx = self.get_unconfirmed_base_tx_for_batching()
|
||||||
if config.get('batch_rbf', False) and base_tx and not base_tx.is_final():
|
if config.get('batch_rbf', False) and base_tx:
|
||||||
base_tx = Transaction(base_tx.serialize())
|
base_tx = Transaction(base_tx.serialize())
|
||||||
base_tx.deserialize(force_full_parse=True)
|
base_tx.deserialize(force_full_parse=True)
|
||||||
base_tx.remove_signatures()
|
base_tx.remove_signatures()
|
||||||
|
@ -602,7 +625,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
||||||
fee_per_byte = Decimal(base_fee) / base_tx.estimated_size()
|
fee_per_byte = Decimal(base_fee) / base_tx.estimated_size()
|
||||||
fee_estimator = lambda size: base_fee + round(fee_per_byte * size)
|
fee_estimator = lambda size: base_fee + round(fee_per_byte * size)
|
||||||
txi = base_tx.inputs()
|
txi = base_tx.inputs()
|
||||||
txo = list(filter(lambda x: not self.is_change(x[1]), base_tx.outputs()))
|
txo = list(filter(lambda o: not self.is_change(o.address), base_tx.outputs()))
|
||||||
else:
|
else:
|
||||||
txi = []
|
txi = []
|
||||||
txo = []
|
txo = []
|
||||||
|
|
Loading…
Add table
Reference in a new issue