replace tx.input, tx.output by methods, so that deserialize calls are encapsulated

This commit is contained in:
ThomasV 2016-01-17 14:12:57 +01:00
parent 321ab10742
commit d200b236ae
7 changed files with 63 additions and 55 deletions

View File

@ -235,7 +235,7 @@ class TxDialog(QDialog, MessageBoxMixin):
if self.tx.locktime > 0:
vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))
vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs)))
vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
ext = QTextCharFormat()
rec = QTextCharFormat()
@ -258,7 +258,7 @@ class TxDialog(QDialog, MessageBoxMixin):
i_text.setReadOnly(True)
i_text.setMaximumHeight(100)
cursor = i_text.textCursor()
for x in self.tx.inputs:
for x in self.tx.inputs():
if x.get('is_coinbase'):
cursor.insertText('coinbase')
else:
@ -279,7 +279,7 @@ class TxDialog(QDialog, MessageBoxMixin):
cursor.insertBlock()
vbox.addWidget(i_text)
vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs)))
vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
o_text = QTextEdit()
o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True)

View File

@ -99,7 +99,7 @@ class CoinChooserBase(PrintError):
def change_amounts(self, tx, count, fee_estimator, dust_threshold):
# Break change up if bigger than max_change
output_amounts = [o[2] for o in tx.outputs]
output_amounts = [o[2] for o in tx.outputs()]
max_change = max(max(output_amounts) * 1.25, dust_threshold * 10)
# Use N change outputs
@ -187,16 +187,16 @@ class CoinChooserBase(PrintError):
buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx))
tx.inputs = [coin for b in buckets for coin in b.coins]
tx.add_inputs([coin for b in buckets for coin in b.coins])
tx_size = base_size + sum(bucket.size for bucket in buckets)
# This takes a count of change outputs and returns a tx fee;
# each pay-to-bitcoin-address output serializes as 34 bytes
fee = lambda count: fee_estimator(tx_size + count * 34)
change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
tx.outputs.extend(change)
tx.add_outputs(change)
self.print_error("using %d inputs" % len(tx.inputs))
self.print_error("using %d inputs" % len(tx.inputs()))
self.print_error("using buckets:", [bucket.desc for bucket in buckets])
return tx
@ -282,9 +282,9 @@ class CoinChooserPrivacy(CoinChooserRandom):
raise NotImplementedError
def penalty_func(self, tx):
min_change = min(o[2] for o in tx.outputs) * 0.75
max_change = max(o[2] for o in tx.outputs) * 1.33
spent_amount = sum(o[2] for o in tx.outputs)
min_change = min(o[2] for o in tx.outputs()) * 0.75
max_change = max(o[2] for o in tx.outputs()) * 1.33
spent_amount = sum(o[2] for o in tx.outputs())
def penalty(buckets):
badness = len(buckets) - 1

View File

@ -210,7 +210,6 @@ class Commands:
def signtransaction(self, tx, privkey=None):
"""Sign a transaction. The wallet keys will be used unless a private key is provided."""
t = Transaction(tx)
t.deserialize()
if privkey:
pubkey = bitcoin.public_key_from_private_key(privkey)
t.sign({pubkey:privkey})
@ -221,8 +220,7 @@ class Commands:
@command('')
def deserialize(self, tx):
"""Deserialize a serialized transaction"""
t = Transaction(tx)
return t.deserialize()
return Transaction(tx).deserialize()
@command('n')
def broadcast(self, tx):

View File

@ -479,17 +479,27 @@ class Transaction:
self.raw = raw['hex']
else:
raise BaseException("cannot initialize transaction", raw)
self.inputs = None
self._inputs = None
def update(self, raw):
self.raw = raw
self.inputs = None
self._inputs = None
self.deserialize()
def inputs(self):
if self._inputs is None:
self.deserialize()
return self._inputs
def outputs(self):
if self._outputs is None:
self.deserialize()
return self._outputs
def update_signatures(self, raw):
"""Add new signatures to a transaction"""
d = deserialize(raw)
for i, txin in enumerate(self.inputs):
for i, txin in enumerate(self.inputs()):
sigs1 = txin.get('signatures')
sigs2 = d['inputs'][i].get('signatures')
for sig in sigs2:
@ -509,8 +519,8 @@ class Transaction:
public_key.verify_digest(sig_string, for_sig, sigdecode = ecdsa.util.sigdecode_string)
j = pubkeys.index(pubkey)
print_error("adding sig", i, j, pubkey, sig)
self.inputs[i]['signatures'][j] = sig
self.inputs[i]['x_pubkeys'][j] = pubkey
self._inputs[i]['signatures'][j] = sig
self._inputs[i]['x_pubkeys'][j] = pubkey
break
# redo raw
self.raw = self.serialize()
@ -519,19 +529,19 @@ class Transaction:
def deserialize(self):
if self.raw is None:
self.raw = self.serialize()
if self.inputs is not None:
if self._inputs is not None:
return
d = deserialize(self.raw)
self.inputs = d['inputs']
self.outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
self._inputs = d['inputs']
self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
self.locktime = d['lockTime']
return d
@classmethod
def from_io(klass, inputs, outputs, locktime=0):
self = klass(None)
self.inputs = inputs
self.outputs = outputs
self._inputs = inputs
self._outputs = outputs
self.locktime = locktime
return self
@ -657,12 +667,12 @@ class Transaction:
def BIP_LI01_sort(self):
# See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki
self.inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n']))
self.outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))
self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n']))
self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))
def serialize(self, for_sig=None):
inputs = self.inputs
outputs = self.outputs
inputs = self.inputs()
outputs = self.outputs()
s = int_to_hex(1,4) # version
s += var_int( len(inputs) ) # number of inputs
for i, txin in enumerate(inputs):
@ -685,21 +695,25 @@ class Transaction:
def hash(self):
return Hash(self.raw.decode('hex') )[::-1].encode('hex')
def add_input(self, input):
self.inputs.append(input)
def add_inputs(self, inputs):
self._inputs.extend(inputs)
self.raw = None
def add_outputs(self, outputs):
self._outputs.extend(outputs)
self.raw = None
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):
return sum( val for tp,addr,val in self.outputs)
return sum( val for tp,addr,val in self.outputs())
def get_fee(self):
return self.input_value() - self.output_value()
def is_final(self):
return not any([x.get('sequence') < 0xffffffff - 1 for x in self.inputs])
return not any([x.get('sequence') < 0xffffffff - 1 for x in self.inputs()])
@classmethod
def fee_for_size(self, relay_fee, fee_per_kb, size):
@ -727,7 +741,7 @@ class Transaction:
def signature_count(self):
r = 0
s = 0
for txin in self.inputs:
for txin in self.inputs():
if txin.get('is_coinbase'):
continue
signatures = filter(None, txin.get('signatures',[]))
@ -741,14 +755,14 @@ class Transaction:
def inputs_without_script(self):
out = set()
for i, txin in enumerate(self.inputs):
for i, txin in enumerate(self.inputs()):
if txin.get('scriptSig') == '':
out.add(i)
return out
def inputs_to_sign(self):
out = set()
for txin in self.inputs:
for txin in self.inputs():
num_sig = txin.get('num_sig')
if num_sig is None:
continue
@ -765,7 +779,7 @@ class Transaction:
return out
def sign(self, keypairs):
for i, txin in enumerate(self.inputs):
for i, txin in enumerate(self.inputs()):
num = txin['num_sig']
for x_pubkey in txin['x_pubkeys']:
signatures = filter(None, txin['signatures'])
@ -775,14 +789,14 @@ class Transaction:
if x_pubkey in keypairs.keys():
print_error("adding signature for", x_pubkey)
# add pubkey to txin
txin = self.inputs[i]
txin = self._inputs[i]
x_pubkeys = txin['x_pubkeys']
ii = x_pubkeys.index(x_pubkey)
sec = keypairs[x_pubkey]
pubkey = public_key_from_private_key(sec)
txin['x_pubkeys'][ii] = pubkey
txin['pubkeys'][ii] = pubkey
self.inputs[i] = txin
self._inputs[i] = txin
# add signature
for_sig = Hash(self.tx_for_sig(i).decode('hex'))
pkey = regenerate_key(sec)
@ -792,7 +806,7 @@ class Transaction:
sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der )
assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der)
txin['signatures'][ii] = sig.encode('hex')
self.inputs[i] = txin
self._inputs[i] = txin
print_error("is_complete", self.is_complete())
self.raw = self.serialize()
@ -800,7 +814,7 @@ class Transaction:
def get_outputs(self):
"""convert pubkeys to addresses"""
o = []
for type, x, v in self.outputs:
for type, x, v in self.outputs():
if type == TYPE_ADDRESS:
addr = x
elif type == TYPE_PUBKEY:
@ -815,7 +829,7 @@ class Transaction:
def has_address(self, addr):
return (addr in self.get_output_addresses()) or (addr in (tx.get("address") for tx in self.inputs))
return (addr in self.get_output_addresses()) or (addr in (tx.get("address") for tx in self.inputs()))
def as_dict(self):
if self.raw is None:
@ -842,7 +856,7 @@ class Transaction:
# priority must be large enough for free tx
threshold = 57600000
weight = 0
for txin in self.inputs:
for txin in self.inputs():
age = wallet.get_confirmations(txin["prevout_hash"])[0]
weight += txin["value"] * age
priority = weight / size

View File

@ -274,7 +274,6 @@ class Abstract_Wallet(PrintError):
continue
tx = self.transactions.get(tx_hash)
if tx is not None:
tx.deserialize()
self.add_transaction(tx_hash, tx)
save = True
if save:
@ -541,7 +540,7 @@ class Abstract_Wallet(PrintError):
is_pruned = False
is_partial = False
v_in = v_out = v_out_mine = 0
for item in tx.inputs:
for item in tx.inputs():
addr = item.get('address')
if addr in addresses:
is_send = True
@ -722,11 +721,11 @@ class Abstract_Wallet(PrintError):
return addr
def add_transaction(self, tx_hash, tx):
is_coinbase = tx.inputs[0].get('is_coinbase') == True
is_coinbase = tx.inputs()[0].get('is_coinbase') == True
with self.transaction_lock:
# add inputs
self.txi[tx_hash] = d = {}
for txi in tx.inputs:
for txi in tx.inputs():
addr = txi.get('address')
if not txi.get('is_coinbase'):
prevout_hash = txi['prevout_hash']
@ -748,7 +747,7 @@ class Abstract_Wallet(PrintError):
# add outputs
self.txo[tx_hash] = d = {}
for n, txo in enumerate(tx.outputs):
for n, txo in enumerate(tx.outputs()):
ser = tx_hash + ':%d'%n
_type, x, v = txo
if _type == TYPE_ADDRESS:
@ -827,7 +826,6 @@ class Abstract_Wallet(PrintError):
# if addr is new, we have to recompute txi and txo
tx = self.transactions.get(tx_hash)
if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get(tx_hash, {}).get(addr) is None:
tx.deserialize()
self.add_transaction(tx_hash, tx)
# Write updated TXI, TXO etc.
@ -1010,7 +1008,7 @@ class Abstract_Wallet(PrintError):
self.check_password(password)
# Add derivation for utxo in wallets
for i, addr in self.utxo_can_sign(tx):
txin = tx.inputs[i]
txin = tx.inputs()[i]
txin['address'] = addr
self.add_input_info(txin)
# Add private keys

View File

@ -200,9 +200,9 @@ class BTChipWallet(BIP44_Wallet):
pubKeys.append(self.get_public_keys(address))
# Recognize outputs - only one output and one change is authorized
if len(tx.outputs) > 2: # should never happen
if len(tx.outputs()) > 2: # should never happen
self.give_error("Transaction with more than 2 outputs not supported")
for type, address, amount in tx.outputs:
for type, address, amount in tx.outputs():
assert type == TYPE_ADDRESS
if self.is_change(address):
changePath = self.address_id(address)

View File

@ -365,7 +365,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
def tx_inputs(self, tx, for_sig=False):
inputs = []
for txin in tx.inputs:
for txin in tx.inputs():
txinputtype = self.types.TxInputType()
if txin.get('is_coinbase'):
prev_hash = "\0"*32
@ -426,8 +426,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
def tx_outputs(self, wallet, tx):
outputs = []
for type, address, amount in tx.outputs:
for type, address, amount in tx.outputs():
assert type == TYPE_ADDRESS
txoutputtype = self.types.TxOutputType()
if wallet.is_change(address):
@ -464,7 +463,6 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
# This function is called from the trezor libraries (via tx_api)
def get_tx(self, tx_hash):
tx = self.prev_tx[tx_hash]
tx.deserialize()
return self.electrum_tx_to_txtype(tx)
@staticmethod