commit d200b236ae8505f9cfbfec03628943f5109ce790
parent 321ab1074232e91594aa8564d9b6a18170e11c3d
Author: ThomasV <thomasv@electrum.org>
Date: Sun, 17 Jan 2016 14:12:57 +0100
replace tx.input, tx.output by methods, so that deserialize calls are encapsulated
Diffstat:
7 files changed, 63 insertions(+), 55 deletions(-)
diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py
@@ -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)
diff --git a/lib/coinchooser.py b/lib/coinchooser.py
@@ -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
diff --git a/lib/commands.py b/lib/commands.py
@@ -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):
diff --git a/lib/transaction.py b/lib/transaction.py
@@ -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
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -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
diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
@@ -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)
diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
@@ -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