@ -45,7 +45,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler,
format_satoshis , format_fee_satoshis , NoDynamicFeeEstimates ,
WalletFileException , BitcoinException ,
InvalidPassword , format_time , timestamp_to_datetime , Satoshis ,
Fiat , bfh , bh2u , TxMinedInfo )
Fiat , bfh , bh2u , TxMinedInfo , quantize_feerate )
from . bitcoin import ( COIN , TYPE_ADDRESS , is_address , address_to_script ,
is_minikey , relayfee , dust_threshold )
from . crypto import sha256d
@ -393,6 +393,7 @@ class Abstract_Wallet(AddressSynchronizer):
else :
status = _ ( ' Local ' )
can_broadcast = self . network is not None
can_bump = is_mine and not tx . is_final ( )
else :
status = _ ( " Signed " )
can_broadcast = self . network is not None
@ -869,15 +870,88 @@ class Abstract_Wallet(AddressSynchronizer):
age = tx_age
return age > age_limit
def bump_fee ( self , tx , delta ) :
def bump_fee ( self , * , tx , new_fee_rate , config ) - > Transaction :
""" Increase the miner fee of ' tx ' .
' new_fee_rate ' is the target min rate in sat / vbyte
"""
if tx . is_final ( ) :
raise CannotBumpFee ( _ ( ' Cannot bump fee ' ) + ' : ' + _ ( ' transaction is final ' ) )
old_tx_size = tx . estimated_size ( )
old_fee = self . get_tx_fee ( tx )
if old_fee is None :
raise CannotBumpFee ( _ ( ' Cannot bump fee ' ) + ' : ' + _ ( ' current fee unknown ' ) )
old_fee_rate = old_fee / old_tx_size # sat/vbyte
if new_fee_rate < = old_fee_rate :
raise CannotBumpFee ( _ ( ' Cannot bump fee ' ) + ' : ' + _ ( " The new fee rate needs to be higher than the old fee rate. " ) )
try :
# method 1: keep all inputs, keep all not is_mine outputs,
# allow adding new inputs
tx_new = self . _bump_fee_through_coinchooser (
tx = tx , new_fee_rate = new_fee_rate , config = config )
method_used = 1
except CannotBumpFee :
# method 2: keep all inputs, no new inputs are added,
# allow decreasing and removing outputs (change is decreased first)
# This is less "safe" as it might end up decreasing e.g. a payment to a merchant;
# but e.g. if the user has sent "Max" previously, this is the only way to RBF.
tx_new = self . _bump_fee_through_decreasing_outputs (
tx = tx , new_fee_rate = new_fee_rate )
method_used = 2
actual_new_fee_rate = tx_new . get_fee ( ) / tx_new . estimated_size ( )
if actual_new_fee_rate < quantize_feerate ( new_fee_rate ) :
raise Exception ( f " bump_fee feerate target was not met (method: { method_used } ). "
f " got { actual_new_fee_rate } , expected >= { new_fee_rate } " )
tx_new . locktime = get_locktime_for_new_transaction ( self . network )
return tx_new
def _bump_fee_through_coinchooser ( self , * , tx , new_fee_rate , config ) :
tx = Transaction ( tx . serialize ( ) )
tx . deserialize ( force_full_parse = True ) # need to parse inputs
tx . remove_signatures ( )
tx . add_inputs_info ( self )
old_inputs = tx . inputs ( ) [ : ]
old_outputs = tx . outputs ( ) [ : ]
# change address
old_change_addrs = [ o . address for o in old_outputs if self . is_change ( o . address ) ]
change_addrs = self . get_change_addresses_for_new_transaction ( old_change_addrs )
# which outputs to keep?
if old_change_addrs :
fixed_outputs = list ( filter ( lambda o : not self . is_change ( o . address ) , old_outputs ) )
else :
if all ( self . is_mine ( o . address ) for o in old_outputs ) :
# all outputs are is_mine and none of them are change.
# we bail out as it's unclear what the user would want!
# the coinchooser bump fee method is probably not a good idea in this case
raise CannotBumpFee ( _ ( ' Cannot bump fee ' ) + ' : all outputs are non-change is_mine ' )
old_not_is_mine = list ( filter ( lambda o : not self . is_mine ( o . address ) , old_outputs ) )
if old_not_is_mine :
fixed_outputs = old_not_is_mine
else :
fixed_outputs = old_outputs
coins = self . get_spendable_coins ( None , config )
for item in coins :
self . add_input_info ( item )
def fee_estimator ( size ) :
return config . estimate_fee_for_feerate ( fee_per_kb = new_fee_rate * 1000 , size = size )
coin_chooser = coinchooser . get_coin_chooser ( config )
try :
return coin_chooser . make_tx ( coins , old_inputs , fixed_outputs , change_addrs ,
fee_estimator , self . dust_threshold ( ) )
except NotEnoughFunds as e :
raise CannotBumpFee ( e )
def _bump_fee_through_decreasing_outputs ( self , * , tx , new_fee_rate ) :
tx = Transaction ( tx . serialize ( ) )
tx . deserialize ( force_full_parse = True ) # need to parse inputs
tx . remove_signatures ( )
tx . add_inputs_info ( self )
inputs = tx . inputs ( )
outputs = tx . outputs ( )
# use own outputs
s = list ( filter ( lambda o : self . is_mine ( o . address ) , outputs ) )
# ... unless there is none
@ -887,13 +961,17 @@ class Abstract_Wallet(AddressSynchronizer):
if x_fee :
x_fee_address , x_fee_amount = x_fee
s = filter ( lambda o : o . address != x_fee_address , s )
if not s :
raise CannotBumpFee ( _ ( ' Cannot bump fee ' ) + ' : no outputs at all?? ' )
# prioritize low value outputs, to get rid of dust
s = sorted ( s , key = lambda o : o . value )
for o in s :
target_fee = tx . estimated_size ( ) * new_fee_rate
delta = target_fee - tx . get_fee ( )
i = outputs . index ( o )
if o . value - delta > = self . dust_threshold ( ) :
outputs [ i ] = o . _replace ( value = o . value - delta )
outputs [ i ] = o . _replace ( value = o . value - delta )
delta = 0
break
else :
@ -903,9 +981,8 @@ class Abstract_Wallet(AddressSynchronizer):
continue
if delta > 0 :
raise CannotBumpFee ( _ ( ' Cannot bump fee ' ) + ' : ' + _ ( ' could not find suitable outputs ' ) )
locktime = get_locktime_for_new_transaction ( self . network )
tx_new = Transaction . from_io ( inputs , outputs , locktime = locktime )
return tx_new
return Transaction . from_io ( inputs , outputs )
def cpfp ( self , tx , fee ) :
txid = tx . txid ( )