Browse Source

Merge pull request #916 from Tafelpoot/optimize

Optimized transaction.py a bit more
283
ThomasV 10 years ago
parent
commit
e3cfc31abb
  1. 97
      lib/transaction.py
  2. 56
      lib/wallet.py

97
lib/transaction.py

@ -386,15 +386,13 @@ def parse_scriptSig(d, bytes):
return return
# p2sh transaction, 2 of n # p2sh transaction, 2 of n
match = [ opcodes.OP_0 ] match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1)
while len(match) < len(decoded):
match.append(opcodes.OP_PUSHDATA4)
if not match_decoded(decoded, match): if not match_decoded(decoded, match):
print_error("cannot find address in input script", bytes.encode('hex')) print_error("cannot find address in input script", bytes.encode('hex'))
return return
x_sig = map(lambda x:x[1].encode('hex'), decoded[1:-1]) x_sig = [x[1].encode('hex') for x in decoded[1:-1]]
d['signatures'] = parse_sig(x_sig) d['signatures'] = parse_sig(x_sig)
d['num_sig'] = 2 d['num_sig'] = 2
@ -410,7 +408,7 @@ def parse_scriptSig(d, bytes):
return return
d['x_pubkeys'] = x_pubkeys d['x_pubkeys'] = x_pubkeys
pubkeys = map(lambda x: parse_xpub(x)[0], x_pubkeys) pubkeys = [parse_xpub(x)[0] for x in x_pubkeys] # xpub, addr = parse_xpub()
d['pubkeys'] = pubkeys d['pubkeys'] = pubkeys
redeemScript = Transaction.multisig_script(pubkeys,2) redeemScript = Transaction.multisig_script(pubkeys,2)
d['redeemScript'] = redeemScript d['redeemScript'] = redeemScript
@ -495,7 +493,9 @@ def deserialize(raw):
return d return d
push_script = lambda x: op_push(len(x)/2) + x def push_script(x):
return op_push(len(x)/2) + x
class Transaction: class Transaction:
@ -520,7 +520,7 @@ class Transaction:
d = deserialize(raw) d = deserialize(raw)
self.raw = raw self.raw = raw
self.inputs = d['inputs'] self.inputs = d['inputs']
self.outputs = map(lambda x: (x['type'], x['address'], x['value']), d['outputs']) self.outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
self.locktime = d['lockTime'] self.locktime = d['lockTime']
@classmethod @classmethod
@ -546,7 +546,7 @@ class Transaction:
if not inputs: if not inputs:
return return
total = sum( map(lambda x:int(x.get('value')), inputs) ) - fee total = sum(i.get('value') for i in inputs) - fee
outputs = [('address', to_address, total)] outputs = [('address', to_address, total)]
self = klass(inputs, outputs) self = klass(inputs, outputs)
self.sign({ pubkey:privkey }) self.sign({ pubkey:privkey })
@ -567,8 +567,7 @@ class Transaction:
raise raise
for k in public_keys: for k in public_keys:
s += op_push(len(k)/2) s += op_push(len(k)/2) + k
s += k
if n==2: if n==2:
s += '52' s += '52'
elif n==3: elif n==3:
@ -612,8 +611,7 @@ class Transaction:
s = int_to_hex(1,4) # version s = int_to_hex(1,4) # version
s += var_int( len(inputs) ) # number of inputs s += var_int( len(inputs) ) # number of inputs
for i in range(len(inputs)): for i, txin in enumerate(inputs):
txin = inputs[i]
s += txin['prevout_hash'].decode('hex')[::-1].encode('hex') # prev hash s += txin['prevout_hash'].decode('hex')[::-1].encode('hex') # prev hash
s += int_to_hex(txin['prevout_n'],4) # prev index s += int_to_hex(txin['prevout_n'],4) # prev index
@ -623,37 +621,31 @@ class Transaction:
address = txin['address'] address = txin['address']
x_signatures = txin['signatures'] x_signatures = txin['signatures']
signatures = filter(lambda x: x is not None, x_signatures) signatures = filter(None, x_signatures)
is_complete = len(signatures) == num_sig is_complete = len(signatures) == num_sig
if for_sig in [-1, None]: if for_sig in [-1, None]:
# if we have enough signatures, we use the actual pubkeys # if we have enough signatures, we use the actual pubkeys
# use extended pubkeys (with bip32 derivation) # use extended pubkeys (with bip32 derivation)
sig_list = []
if for_sig == -1: if for_sig == -1:
# we assume that signature will be 0x48 bytes long # we assume that signature will be 0x48 bytes long
pubkeys = txin['pubkeys'] pubkeys = txin['pubkeys']
sig_list = [ "00"* 0x48 ] * num_sig sig_list = [ "00" * 0x48 ] * num_sig
elif is_complete: elif is_complete:
pubkeys = txin['pubkeys'] pubkeys = txin['pubkeys']
for signature in signatures: sig_list = ((sig + '01') for sig in signatures)
sig_list.append(signature + '01')
else: else:
pubkeys = txin['x_pubkeys'] pubkeys = txin['x_pubkeys']
for signature in x_signatures: sig_list = ((sig + '01') if sig else NO_SIGNATURE for sig in x_signatures)
sig_list.append((signature + '01') if signature is not None else NO_SIGNATURE) script = ''.join(push_script(x) for x in sig_list)
sig_list = ''.join( map( lambda x: push_script(x), sig_list))
if not p2sh: if not p2sh:
script = sig_list
x_pubkey = pubkeys[0] x_pubkey = pubkeys[0]
if x_pubkey is None: if x_pubkey is None:
addrtype, h160 = bc_address_to_hash_160(txin['address']) addrtype, h160 = bc_address_to_hash_160(txin['address'])
x_pubkey = 'fd' + (chr(addrtype) + h160).encode('hex') x_pubkey = 'fd' + (chr(addrtype) + h160).encode('hex')
script += push_script(x_pubkey) script += push_script(x_pubkey)
else: else:
script = '00' # op_0 script = '00' + script # put op_0 in front of script
script += sig_list
redeem_script = self.multisig_script(pubkeys,2) redeem_script = self.multisig_script(pubkeys,2)
script += push_script(redeem_script) script += push_script(redeem_script)
@ -689,10 +681,10 @@ class Transaction:
self.raw = None self.raw = None
def input_value(self): def input_value(self):
return sum([x['value'] for x in self.inputs]) return sum(x['value'] for x in self.inputs)
def output_value(self): def output_value(self):
return sum([ x[2] for x in self.outputs]) return sum( val for tp,addr,val in self.outputs)
def get_fee(self): def get_fee(self):
return self.input_value() - self.output_value() return self.input_value() - self.output_value()
@ -703,7 +695,7 @@ class Transaction:
for txin in self.inputs: for txin in self.inputs:
if txin.get('is_coinbase'): if txin.get('is_coinbase'):
continue continue
signatures = filter(lambda x: x is not None, txin['signatures']) signatures = filter(None, txin['signatures'])
s += len(signatures) s += len(signatures)
r += txin['num_sig'] r += txin['num_sig']
return s, r return s, r
@ -716,7 +708,7 @@ class Transaction:
out = set() out = set()
for txin in self.inputs: for txin in self.inputs:
x_signatures = txin['signatures'] x_signatures = txin['signatures']
signatures = filter(lambda x: x is not None, x_signatures) signatures = filter(None, x_signatures)
if len(signatures) == txin['num_sig']: if len(signatures) == txin['num_sig']:
# input is complete # input is complete
continue continue
@ -730,7 +722,7 @@ class Transaction:
def sign(self, keypairs): def sign(self, keypairs):
print_error("tx.sign(), keypairs:", keypairs) print_error("tx.sign(), keypairs:", keypairs)
for i, txin in enumerate(self.inputs): for i, txin in enumerate(self.inputs):
signatures = filter(lambda x: x is not None, txin['signatures']) signatures = filter(None, txin['signatures'])
num = txin['num_sig'] num = txin['num_sig']
if len(signatures) == num: if len(signatures) == num:
# continue if this txin is complete # continue if this txin is complete
@ -763,14 +755,14 @@ class Transaction:
self.raw = self.serialize() self.raw = self.serialize()
def add_pubkey_addresses(self, txlist): def add_pubkey_addresses(self, txdict):
for i in self.inputs: for txin in self.inputs:
if i.get("address") == "(pubkey)": if txin.get('address') == "(pubkey)":
prev_tx = txlist.get(i.get('prevout_hash')) prev_tx = txdict.get(txin.get('prevout_hash'))
if prev_tx: if prev_tx:
address, value = prev_tx.get_outputs()[i.get('prevout_n')] address, value = prev_tx.get_outputs()[txin.get('prevout_n')]
print_error("found pay-to-pubkey address:", address) print_error("found pay-to-pubkey address:", address)
i["address"] = address txin["address"] = address
def get_outputs(self): def get_outputs(self):
@ -788,23 +780,15 @@ class Transaction:
addr = 'OP_RETURN: "' + x.encode('hex') + '"' addr = 'OP_RETURN: "' + x.encode('hex') + '"'
else: else:
addr = "(None)" addr = "(None)"
o.append((addr,v)) o.append((addr,v)) # consider using yield (addr, v)
return o return o
def get_output_addresses(self): def get_output_addresses(self):
return map(lambda x:x[0], self.get_outputs()) return [addr for addr, val in self.get_outputs()]
def has_address(self, addr): def has_address(self, addr):
found = False return (addr in self.get_output_addresses()) or (addr in (tx.get("address") for tx in self.inputs))
for txin in self.inputs:
if addr == txin.get('address'):
found = True
break
if addr in self.get_output_addresses():
found = True
return found
def get_value(self, addresses, prevout_values): def get_value(self, addresses, prevout_values):
@ -871,19 +855,22 @@ class Transaction:
def requires_fee(self, verifier): def requires_fee(self, verifier):
# see https://en.bitcoin.it/wiki/Transaction_fees # see https://en.bitcoin.it/wiki/Transaction_fees
threshold = 57600000 #
# size must be smaller than 1 kbyte for free tx
size = len(self.serialize(-1))/2 size = len(self.serialize(-1))/2
if size >= 10000: if size >= 10000:
return True return True
# all outputs must be 0.01 BTC or larger for free tx
for o in self.get_outputs(): for addr, value in self.get_outputs():
value = o[1]
if value < 1000000: if value < 1000000:
return True return True
sum = 0 # priority must be large enough for free tx
for i in self.inputs: threshold = 57600000
age = verifier.get_confirmations(i["prevout_hash"])[0] weight = 0
sum += i["value"] * age for txin in self.inputs:
priority = sum / size age = verifier.get_confirmations(txin["prevout_hash"])[0]
weight += txin["value"] * age
priority = weight / size
print_error(priority, threshold) print_error(priority, threshold)
return priority < threshold return priority < threshold

56
lib/wallet.py

@ -329,10 +329,7 @@ class Abstract_Wallet(object):
return changed return changed
def addresses(self, include_change = True): def addresses(self, include_change = True):
o = [] return list(addr for acc in self.accounts for addr in self.get_account_addresses(acc, include_change))
for a in self.accounts.keys():
o += self.get_account_addresses(a, include_change)
return o
def is_mine(self, address): def is_mine(self, address):
return address in self.addresses(True) return address in self.addresses(True)
@ -344,12 +341,11 @@ class Abstract_Wallet(object):
return s[0] == 1 return s[0] == 1
def get_address_index(self, address): def get_address_index(self, address):
for account in self.accounts.keys(): for acc_id in self.accounts:
for for_change in [0,1]: for for_change in [0,1]:
addresses = self.accounts[account].get_addresses(for_change) addresses = self.accounts[acc_id].get_addresses(for_change)
for addr in addresses: if address in addresses:
if address == addr: return acc_id, (for_change, addresses.index(address))
return account, (for_change, addresses.index(addr))
raise Exception("Address not found", address) raise Exception("Address not found", address)
def get_private_key(self, address, password): def get_private_key(self, address, password):
@ -394,7 +390,8 @@ class Abstract_Wallet(object):
self.storage.put('addressbook', self.addressbook, True) self.storage.put('addressbook', self.addressbook, True)
def fill_addressbook(self): def fill_addressbook(self):
for tx_hash, tx in self.transactions.items(): # todo: optimize this
for tx_hash, tx in self.transactions.viewitems():
is_relevant, is_send, _, _ = self.get_tx_value(tx) is_relevant, is_send, _, _ = self.get_tx_value(tx)
if is_send: if is_send:
for addr in tx.get_output_addresses(): for addr in tx.get_output_addresses():
@ -426,12 +423,13 @@ class Abstract_Wallet(object):
self.spent_outputs.append(key) self.spent_outputs.append(key)
def get_addr_balance(self, address): def get_addr_balance(self, address):
'returns the confirmed balance and pending (unconfirmed) balance change of this bitcoin address'
#assert self.is_mine(address) #assert self.is_mine(address)
h = self.history.get(address,[]) h = self.history.get(address,[])
if h == ['*']: return 0,0 if h == ['*']: return 0,0
c = u = 0 c = u = 0
received_coins = [] # list of coins received at address received_coins = [] # list of coins received at address
# go through all tx in history of this address and collect the coins arriving on this address
for tx_hash, tx_height in h: for tx_hash, tx_height in h:
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
if not tx: continue if not tx: continue
@ -440,12 +438,12 @@ class Abstract_Wallet(object):
if addr == address: if addr == address:
key = tx_hash + ':%d'%i key = tx_hash + ':%d'%i
received_coins.append(key) received_coins.append(key)
# go through all tx in history of this address again
for tx_hash, tx_height in h: for tx_hash, tx_height in h:
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
if not tx: continue if not tx: continue
v = 0 v = 0
# substract the value of coins leaving from this address
for item in tx.inputs: for item in tx.inputs:
addr = item.get('address') addr = item.get('address')
if addr == address: if addr == address:
@ -453,16 +451,16 @@ class Abstract_Wallet(object):
value = self.prevout_values.get( key ) value = self.prevout_values.get( key )
if key in received_coins: if key in received_coins:
v -= value v -= value
# add the value of the coins arriving in this address
for i, (addr, value) in enumerate(tx.get_outputs()): for i, (addr, value) in enumerate(tx.get_outputs()):
key = tx_hash + ':%d'%i key = tx_hash + ':%d'%i
if addr == address: if addr == address:
v += value v += value
if tx_height: if tx_height:
c += v c += v # confirmed coins value
else: else:
u += v u += v # unconfirmed coins value
return c, u return c, u
def get_account_name(self, k): def get_account_name(self, k):
@ -474,14 +472,22 @@ class Abstract_Wallet(object):
account_names[k] = self.get_account_name(k) account_names[k] = self.get_account_name(k)
return account_names return account_names
def get_account_addresses(self, a, include_change=True): def get_account_addresses(self, acc_id, include_change=True):
if a is None: if acc_id is None:
o = self.addresses(include_change) addr_list = self.addresses(include_change)
elif a in self.accounts: elif acc_id in self.accounts:
ac = self.accounts[a] acc = self.accounts[acc_id]
o = ac.get_addresses(0) addr_list = acc.get_addresses(0)
if include_change: o += ac.get_addresses(1) if include_change:
return o addr_list += acc.get_addresses(1)
return addr_list
def get_account_from_address(self, addr):
"Returns the account that contains this address, or None"
for acc_id in self.accounts: # similar to get_address_index but simpler
if addr in self.get_account_addresses(acc_id):
return self.accounts[acc_id]
return None
def get_account_balance(self, account): def get_account_balance(self, account):
return self.get_balance(self.get_account_addresses(account)) return self.get_balance(self.get_account_addresses(account))
@ -524,7 +530,7 @@ class Abstract_Wallet(object):
if coins[-1][0] != 0: if coins[-1][0] != 0:
while coins[0][0] == 0: while coins[0][0] == 0:
coins = coins[1:] + [ coins[0] ] coins = coins[1:] + [ coins[0] ]
return [x[1] for x in coins] return [value for height, value in coins]

Loading…
Cancel
Save