better fees estimates
This commit is contained in:
@@ -32,6 +32,7 @@ import struct
|
||||
import struct
|
||||
import StringIO
|
||||
import mmap
|
||||
import random
|
||||
|
||||
NO_SIGNATURE = 'ff'
|
||||
|
||||
@@ -497,7 +498,7 @@ class Transaction:
|
||||
|
||||
def __str__(self):
|
||||
if self.raw is None:
|
||||
self.raw = self.serialize(self.inputs, self.outputs, for_sig = None) # for_sig=-1 means do not sign
|
||||
self.raw = self.serialize()
|
||||
return self.raw
|
||||
|
||||
def __init__(self, inputs, outputs, locktime=0):
|
||||
@@ -519,7 +520,6 @@ class Transaction:
|
||||
self.outputs = map(lambda x: (x['type'], x['address'], x['value']), d['outputs'])
|
||||
self.locktime = d['lockTime']
|
||||
|
||||
|
||||
@classmethod
|
||||
def sweep(klass, privkeys, network, to_address, fee):
|
||||
inputs = []
|
||||
@@ -594,8 +594,14 @@ class Transaction:
|
||||
return script
|
||||
|
||||
|
||||
@classmethod
|
||||
def serialize(klass, inputs, outputs, for_sig = None ):
|
||||
def serialize(self, for_sig=None):
|
||||
# for_sig:
|
||||
# -1 : do not sign, estimate length
|
||||
# i>=0 : sign input i
|
||||
# None : add all signatures
|
||||
|
||||
inputs = self.inputs
|
||||
outputs = self.outputs
|
||||
|
||||
s = int_to_hex(1,4) # version
|
||||
s += var_int( len(inputs) ) # number of inputs
|
||||
@@ -613,11 +619,15 @@ class Transaction:
|
||||
signatures = filter(lambda x: x is not None, x_signatures)
|
||||
is_complete = len(signatures) == num_sig
|
||||
|
||||
if for_sig is None:
|
||||
if for_sig in [-1, None]:
|
||||
# if we have enough signatures, we use the actual pubkeys
|
||||
# use extended pubkeys (with bip32 derivation)
|
||||
sig_list = []
|
||||
if is_complete:
|
||||
if for_sig == -1:
|
||||
# we assume that signature will be 0x48 bytes long
|
||||
pubkeys = txin['pubkeys']
|
||||
sig_list = [ "00"* 0x48 ] * num_sig
|
||||
elif is_complete:
|
||||
pubkeys = txin['pubkeys']
|
||||
for signature in signatures:
|
||||
sig_list.append(signature + '01')
|
||||
@@ -633,13 +643,14 @@ class Transaction:
|
||||
else:
|
||||
script = '00' # op_0
|
||||
script += sig_list
|
||||
redeem_script = klass.multisig_script(pubkeys,2)
|
||||
redeem_script = self.multisig_script(pubkeys,2)
|
||||
script += push_script(redeem_script)
|
||||
|
||||
elif for_sig==i:
|
||||
script = txin['redeemScript'] if p2sh else klass.pay_script('address', address)
|
||||
script = txin['redeemScript'] if p2sh else self.pay_script('address', address)
|
||||
else:
|
||||
script = ''
|
||||
|
||||
s += var_int( len(script)/2 ) # script length
|
||||
s += script
|
||||
s += "ffffffff" # sequence
|
||||
@@ -648,7 +659,7 @@ class Transaction:
|
||||
for output in outputs:
|
||||
type, addr, amount = output
|
||||
s += int_to_hex( amount, 8) # amount
|
||||
script = klass.pay_script(type, addr)
|
||||
script = self.pay_script(type, addr)
|
||||
s += var_int( len(script)/2 ) # script length
|
||||
s += script # script
|
||||
s += int_to_hex(0,4) # lock time
|
||||
@@ -656,10 +667,8 @@ class Transaction:
|
||||
s += int_to_hex(1, 4) # hash type
|
||||
return s
|
||||
|
||||
|
||||
def tx_for_sig(self,i):
|
||||
return self.serialize(self.inputs, self.outputs, for_sig = i)
|
||||
|
||||
return self.serialize(for_sig = i)
|
||||
|
||||
def hash(self):
|
||||
return Hash(self.raw.decode('hex') )[::-1].encode('hex')
|
||||
@@ -672,8 +681,20 @@ class Transaction:
|
||||
txin['signatures'][ii] = sig
|
||||
txin['x_pubkeys'][ii] = pubkey
|
||||
self.inputs[i] = txin
|
||||
self.raw = self.serialize(self.inputs, self.outputs)
|
||||
self.raw = self.serialize()
|
||||
|
||||
def add_input(self, input):
|
||||
self.inputs.append(input)
|
||||
self.raw = None
|
||||
|
||||
def input_value(self):
|
||||
return sum([x['value'] for x in self.inputs])
|
||||
|
||||
def output_value(self):
|
||||
return sum([ x[2] for x in self.outputs])
|
||||
|
||||
def get_fee(self):
|
||||
return self.input_value() - self.output_value()
|
||||
|
||||
def signature_count(self):
|
||||
r = 0
|
||||
@@ -747,9 +768,8 @@ class Transaction:
|
||||
assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der)
|
||||
self.add_signature(i, pubkey, sig.encode('hex'))
|
||||
|
||||
|
||||
print_error("is_complete", self.is_complete())
|
||||
self.raw = self.serialize( self.inputs, self.outputs )
|
||||
self.raw = self.serialize()
|
||||
|
||||
|
||||
def add_pubkey_addresses(self, txlist):
|
||||
@@ -861,7 +881,7 @@ class Transaction:
|
||||
def requires_fee(self, verifier):
|
||||
# see https://en.bitcoin.it/wiki/Transaction_fees
|
||||
threshold = 57600000
|
||||
size = len(str(self))/2
|
||||
size = len(self.serialize(-1))/2
|
||||
if size >= 10000:
|
||||
return True
|
||||
|
||||
|
||||
131
lib/wallet.py
131
lib/wallet.py
@@ -42,6 +42,7 @@ from mnemonic import Mnemonic
|
||||
COINBASE_MATURITY = 100
|
||||
DUST_THRESHOLD = 5430
|
||||
|
||||
|
||||
# internal ID for imported account
|
||||
IMPORTED_ACCOUNT = '/x'
|
||||
|
||||
@@ -163,7 +164,7 @@ class Abstract_Wallet(object):
|
||||
|
||||
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
|
||||
|
||||
self.fee = int(storage.get('fee_per_kb', 10000))
|
||||
self.fee_per_kb = int(storage.get('fee_per_kb', 10000))
|
||||
|
||||
self.next_addresses = storage.get('next_addresses',{})
|
||||
|
||||
@@ -579,60 +580,13 @@ class Abstract_Wallet(object):
|
||||
coins = coins[1:] + [ coins[0] ]
|
||||
return [x[1] for x in coins]
|
||||
|
||||
def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None, coins = None ):
|
||||
""" todo: minimize tx size """
|
||||
total = 0
|
||||
fee = self.fee if fixed_fee is None else fixed_fee
|
||||
|
||||
if not coins:
|
||||
if domain is None:
|
||||
domain = self.addresses(True)
|
||||
for i in self.frozen_addresses:
|
||||
if i in domain: domain.remove(i)
|
||||
coins = self.get_unspent_coins(domain)
|
||||
|
||||
inputs = []
|
||||
for item in coins:
|
||||
if item.get('coinbase') and item.get('height') + COINBASE_MATURITY > self.network.get_local_height():
|
||||
continue
|
||||
v = item.get('value')
|
||||
total += v
|
||||
inputs.append(item)
|
||||
fee = self.estimated_fee(inputs, num_outputs) if fixed_fee is None else fixed_fee
|
||||
if total >= amount + fee: break
|
||||
else:
|
||||
inputs = []
|
||||
return inputs, total, fee
|
||||
|
||||
|
||||
def set_fee(self, fee):
|
||||
if self.fee != fee:
|
||||
self.fee = fee
|
||||
self.storage.put('fee_per_kb', self.fee, True)
|
||||
if self.fee_per_kb != fee:
|
||||
self.fee_per_kb = fee
|
||||
self.storage.put('fee_per_kb', self.fee_per_kb, True)
|
||||
|
||||
def estimated_fee(self, inputs, num_outputs):
|
||||
estimated_size = len(inputs) * 180 + num_outputs * 34 # this assumes non-compressed keys
|
||||
fee = self.fee * int(math.ceil(estimated_size/1000.))
|
||||
return fee
|
||||
|
||||
def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None):
|
||||
"add change to a transaction"
|
||||
change_amount = total - ( amount + fee )
|
||||
if change_amount > DUST_THRESHOLD:
|
||||
if not change_addr:
|
||||
|
||||
# send change to one of the accounts involved in the tx
|
||||
address = inputs[0].get('address')
|
||||
account, _ = self.get_address_index(address)
|
||||
|
||||
if not self.use_change or account == IMPORTED_ACCOUNT:
|
||||
change_addr = address
|
||||
else:
|
||||
change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
|
||||
|
||||
# Insert the change output at a random position in the outputs
|
||||
posn = random.randint(0, len(outputs))
|
||||
outputs[posn:posn] = [( 'address', change_addr, change_amount)]
|
||||
|
||||
def get_history(self, address):
|
||||
with self.lock:
|
||||
@@ -754,22 +708,77 @@ class Abstract_Wallet(object):
|
||||
|
||||
return default_label
|
||||
|
||||
def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None, coins=None ):
|
||||
def estimated_fee(self, tx):
|
||||
estimated_size = len(tx.serialize(-1))/2
|
||||
#print_error('estimated_size', estimated_size)
|
||||
return int(self.fee_per_kb*estimated_size/1024.)
|
||||
|
||||
def make_unsigned_transaction(self, outputs, fixed_fee=None, change_addr=None, domain=None, coins=None ):
|
||||
# check outputs
|
||||
for type, data, value in outputs:
|
||||
if type == 'op_return':
|
||||
assert len(data) < 41, "string too long"
|
||||
assert value == 0
|
||||
#assert value == 0
|
||||
if type == 'address':
|
||||
assert is_address(data), "Address " + data + " is invalid!"
|
||||
|
||||
# get coins
|
||||
if not coins:
|
||||
if domain is None:
|
||||
domain = self.addresses(True)
|
||||
for i in self.frozen_addresses:
|
||||
if i in domain: domain.remove(i)
|
||||
coins = self.get_unspent_coins(domain)
|
||||
|
||||
amount = sum( map(lambda x:x[2], outputs) )
|
||||
inputs, total, fee = self.choose_tx_inputs( amount, fee, len(outputs), domain, coins )
|
||||
if not inputs:
|
||||
raise ValueError("Not enough funds")
|
||||
for txin in inputs:
|
||||
self.add_input_info(txin)
|
||||
self.add_tx_change(inputs, outputs, amount, fee, total, change_addr)
|
||||
run_hook('make_unsigned_transaction', inputs, outputs)
|
||||
return Transaction(inputs, outputs)
|
||||
total = 0
|
||||
inputs = []
|
||||
tx = Transaction(inputs, outputs)
|
||||
for item in coins:
|
||||
if item.get('coinbase') and item.get('height') + COINBASE_MATURITY > self.network.get_local_height():
|
||||
continue
|
||||
v = item.get('value')
|
||||
total += v
|
||||
self.add_input_info(item)
|
||||
tx.add_input(item)
|
||||
fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx)
|
||||
if total >= amount + fee: break
|
||||
else:
|
||||
print_error("Not enough funds", total, amount, fee)
|
||||
return None
|
||||
|
||||
# change address
|
||||
if not change_addr:
|
||||
# send change to one of the accounts involved in the tx
|
||||
address = inputs[0].get('address')
|
||||
account, _ = self.get_address_index(address)
|
||||
if not self.use_change or account == IMPORTED_ACCOUNT:
|
||||
change_addr = address
|
||||
else:
|
||||
change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
|
||||
|
||||
# if change is above dust threshold, add a change output.
|
||||
change_amount = total - ( amount + fee )
|
||||
if change_amount > DUST_THRESHOLD:
|
||||
# Insert the change output at a random position in the outputs
|
||||
posn = random.randint(0, len(tx.outputs))
|
||||
tx.outputs[posn:posn] = [( 'address', change_addr, change_amount)]
|
||||
# recompute fee including change output
|
||||
fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx)
|
||||
# remove change output
|
||||
tx.outputs.pop(posn)
|
||||
# if change is still above dust threshold, re-add change output.
|
||||
change_amount = total - ( amount + fee )
|
||||
if change_amount > DUST_THRESHOLD:
|
||||
tx.outputs[posn:posn] = [( 'address', change_addr, change_amount)]
|
||||
print_error('change', change_amount)
|
||||
else:
|
||||
print_error('not keeping dust', change_amount)
|
||||
else:
|
||||
print_error('not keeping dust', change_amount)
|
||||
|
||||
run_hook('make_unsigned_transaction', tx)
|
||||
return tx
|
||||
|
||||
def mktx(self, outputs, password, fee=None, change_addr=None, domain= None, coins = None ):
|
||||
tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain, coins)
|
||||
|
||||
Reference in New Issue
Block a user