Browse Source

transaction: introduce TxOutput namedtuple

3.2.x
SomberNight 7 years ago
parent
commit
2eb72d496f
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 13
      electrum/address_synchronizer.py
  2. 4
      electrum/coinchooser.py
  3. 6
      electrum/commands.py
  4. 3
      electrum/gui/kivy/main_window.py
  5. 6
      electrum/gui/kivy/uix/dialogs/__init__.py
  6. 3
      electrum/gui/kivy/uix/screens.py
  7. 12
      electrum/gui/qt/main_window.py
  8. 13
      electrum/gui/qt/paytoedit.py
  9. 4
      electrum/gui/stdio.py
  10. 4
      electrum/gui/text.py
  11. 7
      electrum/paymentrequest.py
  12. 6
      electrum/plugins/digitalbitbox/digitalbitbox.py
  13. 12
      electrum/plugins/hw_wallet/plugin.py
  14. 5
      electrum/plugins/keepkey/keepkey.py
  15. 10
      electrum/plugins/ledger/ledger.py
  16. 5
      electrum/plugins/safe_t/safe_t.py
  17. 5
      electrum/plugins/trezor/trezor.py
  18. 9
      electrum/plugins/trustedcoin/trustedcoin.py
  19. 45
      electrum/tests/test_wallet_vertical.py
  20. 26
      electrum/transaction.py
  21. 33
      electrum/wallet.py

13
electrum/address_synchronizer.py

@ -28,7 +28,7 @@ from collections import defaultdict
from . import bitcoin from . import bitcoin
from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus
from .transaction import Transaction from .transaction import Transaction, TxOutput
from .synchronizer import Synchronizer from .synchronizer import Synchronizer
from .verifier import SPV from .verifier import SPV
from .blockchain import hash_header from .blockchain import hash_header
@ -112,12 +112,11 @@ class AddressSynchronizer(PrintError):
return addr return addr
return None return None
def get_txout_address(self, txo): def get_txout_address(self, txo: TxOutput):
_type, x, v = txo if txo.type == TYPE_ADDRESS:
if _type == TYPE_ADDRESS: addr = txo.address
addr = x elif txo.type == TYPE_PUBKEY:
elif _type == TYPE_PUBKEY: addr = bitcoin.public_key_to_p2pkh(bfh(txo.address))
addr = bitcoin.public_key_to_p2pkh(bfh(x))
else: else:
addr = None addr = None
return addr return addr

4
electrum/coinchooser.py

@ -26,7 +26,7 @@ from collections import defaultdict, namedtuple
from math import floor, log10 from math import floor, log10
from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
from .transaction import Transaction from .transaction import Transaction, TxOutput
from .util import NotEnoughFunds, PrintError from .util import NotEnoughFunds, PrintError
@ -178,7 +178,7 @@ class CoinChooserBase(PrintError):
# size of the change output, add it to the transaction. # size of the change output, add it to the transaction.
dust = sum(amount for amount in amounts if amount < dust_threshold) dust = sum(amount for amount in amounts if amount < dust_threshold)
amounts = [amount for amount in amounts if amount >= dust_threshold] amounts = [amount for amount in amounts if amount >= dust_threshold]
change = [(TYPE_ADDRESS, addr, amount) change = [TxOutput(TYPE_ADDRESS, addr, amount)
for addr, amount in zip(change_addrs, amounts)] for addr, amount in zip(change_addrs, amounts)]
self.print_error('change:', change) self.print_error('change:', change)
if dust: if dust:

6
electrum/commands.py

@ -38,7 +38,7 @@ from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_enc
from . import bitcoin from . import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from .i18n import _ from .i18n import _
from .transaction import Transaction, multisig_script from .transaction import Transaction, multisig_script, TxOutput
from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
from .plugin import run_hook from .plugin import run_hook
@ -226,7 +226,7 @@ class Commands:
txin['signatures'] = [None] txin['signatures'] = [None]
txin['num_sig'] = 1 txin['num_sig'] = 1
outputs = [(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs] outputs = [TxOutput(TYPE_ADDRESS, x['address'], int(x['value'])) for x in outputs]
tx = Transaction.from_io(inputs, outputs, locktime=locktime) tx = Transaction.from_io(inputs, outputs, locktime=locktime)
tx.sign(keypairs) tx.sign(keypairs)
return tx.as_dict() return tx.as_dict()
@ -415,7 +415,7 @@ class Commands:
for address, amount in outputs: for address, amount in outputs:
address = self._resolver(address) address = self._resolver(address)
amount = satoshis(amount) amount = satoshis(amount)
final_outputs.append((TYPE_ADDRESS, address, amount)) final_outputs.append(TxOutput(TYPE_ADDRESS, address, amount))
coins = self.wallet.get_spendable_coins(domain, self.config) coins = self.wallet.get_spendable_coins(domain, self.config)
tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr) tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)

3
electrum/gui/kivy/main_window.py

@ -713,13 +713,14 @@ class ElectrumWindow(App):
self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy
def get_max_amount(self): def get_max_amount(self):
from electrum.transaction import TxOutput
if run_hook('abort_send', self): if run_hook('abort_send', self):
return '' return ''
inputs = self.wallet.get_spendable_coins(None, self.electrum_config) inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
if not inputs: if not inputs:
return '' return ''
addr = str(self.send_screen.screen.address) or self.wallet.dummy_address() addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
outputs = [(TYPE_ADDRESS, addr, '!')] outputs = [TxOutput(TYPE_ADDRESS, addr, '!')]
try: try:
tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config) tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
except NoDynamicFeeEstimates as e: except NoDynamicFeeEstimates as e:

6
electrum/gui/kivy/uix/dialogs/__init__.py

@ -206,9 +206,9 @@ class OutputList(RecycleView):
def update(self, outputs): def update(self, outputs):
res = [] res = []
for (type, address, amount) in outputs: for o in outputs:
value = self.app.format_amount_and_units(amount) value = self.app.format_amount_and_units(o.value)
res.append({'address': address, 'value': value}) res.append({'address': o.address, 'value': value})
self.data = res self.data = res

3
electrum/gui/kivy/uix/screens.py

@ -20,6 +20,7 @@ from kivy.utils import platform
from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
from electrum import bitcoin from electrum import bitcoin
from electrum.transaction import TxOutput
from electrum.util import timestamp_to_datetime from electrum.util import timestamp_to_datetime
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from electrum.plugin import run_hook from electrum.plugin import run_hook
@ -256,7 +257,7 @@ class SendScreen(CScreen):
except: except:
self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount) self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
return return
outputs = [(bitcoin.TYPE_ADDRESS, address, amount)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
message = self.screen.message message = self.screen.message
amount = sum(map(lambda x:x[2], outputs)) amount = sum(map(lambda x:x[2], outputs))
if self.app.electrum_config.get('use_rbf'): if self.app.electrum_config.get('use_rbf'):

12
electrum/gui/qt/main_window.py

@ -50,7 +50,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
export_meta, import_meta, bh2u, bfh, InvalidPassword, export_meta, import_meta, bh2u, bfh, InvalidPassword,
base_units, base_units_list, base_unit_name_to_decimal_point, base_units, base_units_list, base_unit_name_to_decimal_point,
decimal_point_to_base_unit_name, quantize_feerate) decimal_point_to_base_unit_name, quantize_feerate)
from electrum.transaction import Transaction from electrum.transaction import Transaction, TxOutput
from electrum.address_synchronizer import AddTransactionException from electrum.address_synchronizer import AddTransactionException
from electrum.wallet import Multisig_Wallet, CannotBumpFee from electrum.wallet import Multisig_Wallet, CannotBumpFee
@ -1306,7 +1306,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
outputs = self.payto_e.get_outputs(self.is_max) outputs = self.payto_e.get_outputs(self.is_max)
if not outputs: if not outputs:
_type, addr = self.get_payto_or_dummy() _type, addr = self.get_payto_or_dummy()
outputs = [(_type, addr, amount)] outputs = [TxOutput(_type, addr, amount)]
is_sweep = bool(self.tx_external_keypairs) is_sweep = bool(self.tx_external_keypairs)
make_tx = lambda fee_est: \ make_tx = lambda fee_est: \
self.wallet.make_unsigned_transaction( self.wallet.make_unsigned_transaction(
@ -1485,14 +1485,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_error(_('No outputs')) self.show_error(_('No outputs'))
return return
for _type, addr, amount in outputs: for o in outputs:
if addr is None: if o.address is None:
self.show_error(_('Bitcoin Address is None')) self.show_error(_('Bitcoin Address is None'))
return return
if _type == TYPE_ADDRESS and not bitcoin.is_address(addr): if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
self.show_error(_('Invalid Bitcoin Address')) self.show_error(_('Invalid Bitcoin Address'))
return return
if amount is None: if o.value is None:
self.show_error(_('Invalid Amount')) self.show_error(_('Invalid Amount'))
return return

13
electrum/gui/qt/paytoedit.py

@ -29,6 +29,7 @@ from decimal import Decimal
from electrum import bitcoin from electrum import bitcoin
from electrum.util import bfh from electrum.util import bfh
from electrum.transaction import TxOutput
from .qrtextedit import ScanQRTextEdit from .qrtextedit import ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
@ -77,7 +78,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
x, y = line.split(',') x, y = line.split(',')
out_type, out = self.parse_output(x) out_type, out = self.parse_output(x)
amount = self.parse_amount(y) amount = self.parse_amount(y)
return out_type, out, amount return TxOutput(out_type, out, amount)
def parse_output(self, x): def parse_output(self, x):
try: try:
@ -139,16 +140,16 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
is_max = False is_max = False
for i, line in enumerate(lines): for i, line in enumerate(lines):
try: try:
_type, to_address, amount = self.parse_address_and_amount(line) output = self.parse_address_and_amount(line)
except: except:
self.errors.append((i, line.strip())) self.errors.append((i, line.strip()))
continue continue
outputs.append((_type, to_address, amount)) outputs.append(output)
if amount == '!': if output.value == '!':
is_max = True is_max = True
else: else:
total += amount total += output.value
self.win.is_max = is_max self.win.is_max = is_max
self.outputs = outputs self.outputs = outputs
@ -174,7 +175,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
amount = self.amount_edit.get_amount() amount = self.amount_edit.get_amount()
_type, addr = self.payto_address _type, addr = self.payto_address
self.outputs = [(_type, addr, amount)] self.outputs = [TxOutput(_type, addr, amount)]
return self.outputs[:] return self.outputs[:]

4
electrum/gui/stdio.py

@ -4,6 +4,7 @@ _ = lambda x:x
from electrum import WalletStorage, Wallet from electrum import WalletStorage, Wallet
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis, set_verbosity
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
from electrum.transaction import TxOutput
import getpass, datetime import getpass, datetime
# minimal fdisk like gui for console usage # minimal fdisk like gui for console usage
@ -189,7 +190,8 @@ class ElectrumGui:
if c == "n": return if c == "n": return
try: try:
tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee) tx = self.wallet.mktx([TxOutput(TYPE_ADDRESS, self.str_recipient, amount)],
password, self.config, fee)
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
return return

4
electrum/gui/text.py

@ -6,6 +6,7 @@ import getpass
import electrum import electrum
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis, set_verbosity
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
from electrum.transaction import TxOutput
from .. import Wallet, WalletStorage from .. import Wallet, WalletStorage
_ = lambda x:x _ = lambda x:x
@ -340,7 +341,8 @@ class ElectrumGui:
else: else:
password = None password = None
try: try:
tx = self.wallet.mktx([(TYPE_ADDRESS, self.str_recipient, amount)], password, self.config, fee) tx = self.wallet.mktx([TxOutput(TYPE_ADDRESS, self.str_recipient, amount)],
password, self.config, fee)
except Exception as e: except Exception as e:
self.show_message(str(e)) self.show_message(str(e))
return return

7
electrum/paymentrequest.py

@ -42,6 +42,7 @@ from .util import print_error, bh2u, bfh
from .util import export_meta, import_meta from .util import export_meta, import_meta
from .bitcoin import TYPE_ADDRESS from .bitcoin import TYPE_ADDRESS
from .transaction import TxOutput
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'} REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'} ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'}
@ -123,7 +124,7 @@ class PaymentRequest:
self.outputs = [] self.outputs = []
for o in self.details.outputs: for o in self.details.outputs:
addr = transaction.get_address_from_output_script(o.script)[1] addr = transaction.get_address_from_output_script(o.script)[1]
self.outputs.append((TYPE_ADDRESS, addr, o.amount)) self.outputs.append(TxOutput(TYPE_ADDRESS, addr, o.amount))
self.memo = self.details.memo self.memo = self.details.memo
self.payment_url = self.details.payment_url self.payment_url = self.details.payment_url
@ -225,8 +226,8 @@ class PaymentRequest:
def get_address(self): def get_address(self):
o = self.outputs[0] o = self.outputs[0]
assert o[0] == TYPE_ADDRESS assert o.type == TYPE_ADDRESS
return o[1] return o.address
def get_requestor(self): def get_requestor(self):
return self.requestor if self.requestor else self.get_address() return self.requestor if self.requestor else self.get_address()

6
electrum/plugins/digitalbitbox/digitalbitbox.py

@ -534,9 +534,9 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
self.give_error("No matching x_key for sign_transaction") # should never happen self.give_error("No matching x_key for sign_transaction") # should never happen
# Build pubkeyarray from outputs # Build pubkeyarray from outputs
for _type, address, amount in tx.outputs(): for o in tx.outputs():
assert _type == TYPE_ADDRESS assert o.type == TYPE_ADDRESS
info = tx.output_info.get(address) info = tx.output_info.get(o.address)
if info is not None: if info is not None:
index, xpubs, m = info index, xpubs, m = info
changePath = self.get_derivation() + "/%d/%d" % index changePath = self.get_derivation() + "/%d/%d" % index

12
electrum/plugins/hw_wallet/plugin.py

@ -28,7 +28,7 @@ from electrum.plugin import BasePlugin, hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.bitcoin import is_address, TYPE_SCRIPT from electrum.bitcoin import is_address, TYPE_SCRIPT
from electrum.util import bfh from electrum.util import bfh
from electrum.transaction import opcodes from electrum.transaction import opcodes, TxOutput
class HW_PluginBase(BasePlugin): class HW_PluginBase(BasePlugin):
@ -91,13 +91,13 @@ def is_any_tx_output_on_change_branch(tx):
return False return False
def trezor_validate_op_return_output_and_get_data(_type, address, amount): def trezor_validate_op_return_output_and_get_data(output: TxOutput) -> bytes:
if _type != TYPE_SCRIPT: if output.type != TYPE_SCRIPT:
raise Exception("Unexpected output type: {}".format(_type)) raise Exception("Unexpected output type: {}".format(output.type))
script = bfh(address) script = bfh(output.address)
if not (script[0] == opcodes.OP_RETURN and if not (script[0] == opcodes.OP_RETURN and
script[1] == len(script) - 2 and script[1] <= 75): script[1] == len(script) - 2 and script[1] <= 75):
raise Exception(_("Only OP_RETURN scripts, with one constant push, are supported.")) raise Exception(_("Only OP_RETURN scripts, with one constant push, are supported."))
if amount != 0: if output.value != 0:
raise Exception(_("Amount for OP_RETURN output must be zero.")) raise Exception(_("Amount for OP_RETURN output must be zero."))
return script[2:] return script[2:]

5
electrum/plugins/keepkey/keepkey.py

@ -382,7 +382,7 @@ class KeepKeyPlugin(HW_PluginBase):
txoutputtype.amount = amount txoutputtype.amount = amount
if _type == TYPE_SCRIPT: if _type == TYPE_SCRIPT:
txoutputtype.script_type = self.types.PAYTOOPRETURN txoutputtype.script_type = self.types.PAYTOOPRETURN
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(_type, address, amount) txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
elif _type == TYPE_ADDRESS: elif _type == TYPE_ADDRESS:
if is_segwit_address(address): if is_segwit_address(address):
txoutputtype.script_type = self.types.PAYTOWITNESS txoutputtype.script_type = self.types.PAYTOWITNESS
@ -401,7 +401,8 @@ class KeepKeyPlugin(HW_PluginBase):
has_change = False has_change = False
any_output_on_change_branch = is_any_tx_output_on_change_branch(tx) any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
for _type, address, amount in tx.outputs(): for o in tx.outputs():
_type, address, amount = o.type, o.address, o.value
use_create_by_derivation = False use_create_by_derivation = False
info = tx.output_info.get(address) info = tx.output_info.get(address)

10
electrum/plugins/ledger/ledger.py

@ -394,9 +394,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.give_error("Transaction with more than 2 outputs not supported") self.give_error("Transaction with more than 2 outputs not supported")
has_change = False has_change = False
any_output_on_change_branch = is_any_tx_output_on_change_branch(tx) any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
for _type, address, amount in tx.outputs(): for o in tx.outputs():
assert _type == TYPE_ADDRESS assert o.type == TYPE_ADDRESS
info = tx.output_info.get(address) info = tx.output_info.get(o.address)
if (info is not None) and len(tx.outputs()) > 1 \ if (info is not None) and len(tx.outputs()) > 1 \
and not has_change: and not has_change:
index, xpubs, m = info index, xpubs, m = info
@ -407,9 +407,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
changePath = self.get_derivation()[2:] + "/%d/%d"%index changePath = self.get_derivation()[2:] + "/%d/%d"%index
has_change = True has_change = True
else: else:
output = address output = o.address
else: else:
output = address output = o.address
self.handler.show_message(_("Confirm Transaction on your Ledger device...")) self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
try: try:

5
electrum/plugins/safe_t/safe_t.py

@ -453,7 +453,7 @@ class SafeTPlugin(HW_PluginBase):
txoutputtype.amount = amount txoutputtype.amount = amount
if _type == TYPE_SCRIPT: if _type == TYPE_SCRIPT:
txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(_type, address, amount) txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
elif _type == TYPE_ADDRESS: elif _type == TYPE_ADDRESS:
txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
txoutputtype.address = address txoutputtype.address = address
@ -463,7 +463,8 @@ class SafeTPlugin(HW_PluginBase):
has_change = False has_change = False
any_output_on_change_branch = is_any_tx_output_on_change_branch(tx) any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
for _type, address, amount in tx.outputs(): for o in tx.outputs():
_type, address, amount = o.type, o.address, o.value
use_create_by_derivation = False use_create_by_derivation = False
info = tx.output_info.get(address) info = tx.output_info.get(address)

5
electrum/plugins/trezor/trezor.py

@ -464,7 +464,7 @@ class TrezorPlugin(HW_PluginBase):
txoutputtype.amount = amount txoutputtype.amount = amount
if _type == TYPE_SCRIPT: if _type == TYPE_SCRIPT:
txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN txoutputtype.script_type = self.types.OutputScriptType.PAYTOOPRETURN
txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(_type, address, amount) txoutputtype.op_return_data = trezor_validate_op_return_output_and_get_data(o)
elif _type == TYPE_ADDRESS: elif _type == TYPE_ADDRESS:
txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
txoutputtype.address = address txoutputtype.address = address
@ -474,7 +474,8 @@ class TrezorPlugin(HW_PluginBase):
has_change = False has_change = False
any_output_on_change_branch = is_any_tx_output_on_change_branch(tx) any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
for _type, address, amount in tx.outputs(): for o in tx.outputs():
_type, address, amount = o.type, o.address, o.value
use_create_by_derivation = False use_create_by_derivation = False
info = tx.output_info.get(address) info = tx.output_info.get(address)

9
electrum/plugins/trustedcoin/trustedcoin.py

@ -33,6 +33,7 @@ from urllib.parse import quote
from electrum import bitcoin, ecc, constants, keystore, version from electrum import bitcoin, ecc, constants, keystore, version
from electrum.bitcoin import * from electrum.bitcoin import *
from electrum.transaction import TxOutput
from electrum.mnemonic import Mnemonic from electrum.mnemonic import Mnemonic
from electrum.wallet import Multisig_Wallet, Deterministic_Wallet from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
from electrum.i18n import _ from electrum.i18n import _
@ -273,7 +274,7 @@ class Wallet_2fa(Multisig_Wallet):
fee = self.extra_fee(config) if not is_sweep else 0 fee = self.extra_fee(config) if not is_sweep else 0
if fee: if fee:
address = self.billing_info['billing_address'] address = self.billing_info['billing_address']
fee_output = (TYPE_ADDRESS, address, fee) fee_output = TxOutput(TYPE_ADDRESS, address, fee)
try: try:
tx = mk_tx(outputs + [fee_output]) tx = mk_tx(outputs + [fee_output])
except NotEnoughFunds: except NotEnoughFunds:
@ -395,9 +396,9 @@ class TrustedCoinPlugin(BasePlugin):
def get_tx_extra_fee(self, wallet, tx): def get_tx_extra_fee(self, wallet, tx):
if type(wallet) != Wallet_2fa: if type(wallet) != Wallet_2fa:
return return
for _type, addr, amount in tx.outputs(): for o in tx.outputs():
if _type == TYPE_ADDRESS and wallet.is_billing_address(addr): if o.type == TYPE_ADDRESS and wallet.is_billing_address(o.address):
return addr, amount return o.address, o.value
def finish_requesting(func): def finish_requesting(func):
def f(self, *args, **kwargs): def f(self, *args, **kwargs):

45
electrum/tests/test_wallet_vertical.py

@ -10,6 +10,7 @@ from electrum import SimpleConfig
from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT
from electrum.wallet import sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet from electrum.wallet import sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet
from electrum.util import bfh, bh2u from electrum.util import bfh, bh2u
from electrum.transaction import TxOutput
from electrum.plugins.trustedcoin import trustedcoin from electrum.plugins.trustedcoin import trustedcoin
@ -532,7 +533,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet1.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet1.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# wallet1 -> wallet2 # wallet1 -> wallet2
outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 250000)]
tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet1.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_complete())
@ -552,7 +553,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
# wallet2 -> wallet1 # wallet2 -> wallet1
outputs = [(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1.get_receiving_address(), 100000)]
tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_complete())
@ -605,7 +606,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# wallet1 -> wallet2 # wallet1 -> wallet2
outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 370000)]
tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
self.assertFalse(tx.is_complete()) self.assertFalse(tx.is_complete())
@ -628,7 +629,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
# wallet2 -> wallet1 # wallet2 -> wallet1
outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_complete())
@ -696,7 +697,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# wallet1 -> wallet2 # wallet1 -> wallet2
outputs = [(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2a.get_receiving_address(), 165000)]
tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
txid = tx.txid() txid = tx.txid()
tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
@ -722,7 +723,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) wallet2a.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
# wallet2 -> wallet1 # wallet2 -> wallet1
outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 100000)]
tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet2a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
txid = tx.txid() txid = tx.txid()
tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners tx = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
@ -776,7 +777,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# wallet1 -> wallet2 # wallet1 -> wallet2
outputs = [(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 1000000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet2.get_receiving_address(), 1000000)]
tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet1a.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_complete())
@ -796,7 +797,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED) wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
# wallet2 -> wallet1 # wallet2 -> wallet1
outputs = [(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 300000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, wallet1a.get_receiving_address(), 300000)]
tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet2.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
self.assertTrue(tx.is_complete()) self.assertTrue(tx.is_complete())
@ -832,7 +833,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create tx # create tx
outputs = [(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
coins = wallet.get_spendable_coins(domain=None, config=self.config) coins = wallet.get_spendable_coins(domain=None, config=self.config)
tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000) tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
@ -918,7 +919,7 @@ class TestWalletSending(TestCaseForTestnet):
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create tx # create tx
outputs = [(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N1VTMMFb91SH9SNRAkT7z8otP5eZEct4KL', 2500000)]
coins = wallet.get_spendable_coins(domain=None, config=self.config) coins = wallet.get_spendable_coins(domain=None, config=self.config)
tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000) tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
@ -1048,7 +1049,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1088,7 +1089,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325341 tx.locktime = 1325341
@ -1129,7 +1130,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1qp0mv2sxsyxxfj5gl0332f9uyez93su9cf26757', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325341 tx.locktime = 1325341
@ -1165,7 +1166,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1199,7 +1200,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1233,7 +1234,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1270,7 +1271,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1307,7 +1308,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1344,7 +1345,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1quk7ahlhr3qmjndy0uvu9y9hxfesrtahtta9ghm', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325340 tx.locktime = 1325340
@ -1393,7 +1394,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, '2MuCQQHJNnrXzQzuqfUCfAwAjPqpyEHbgue', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2MuCQQHJNnrXzQzuqfUCfAwAjPqpyEHbgue', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325503 tx.locktime = 1325503
@ -1450,7 +1451,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, '2N8CtJRwxb2GCaiWWdSHLZHHLoZy53CCyxf', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2N8CtJRwxb2GCaiWWdSHLZHHLoZy53CCyxf', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325504 tx.locktime = 1325504
@ -1509,7 +1510,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED) wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
# create unsigned tx # create unsigned tx
outputs = [(bitcoin.TYPE_ADDRESS, '2MyoZVy8T1t94yLmyKu8DP1SmbWvnxbkwRA', 2500000)] outputs = [TxOutput(bitcoin.TYPE_ADDRESS, '2MyoZVy8T1t94yLmyKu8DP1SmbWvnxbkwRA', 2500000)]
tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000) tx = wallet_online.mktx(outputs=outputs, password=None, config=self.config, fee=5000)
tx.set_rbf(True) tx.set_rbf(True)
tx.locktime = 1325505 tx.locktime = 1325505

26
electrum/transaction.py

@ -27,7 +27,7 @@
# Note: The deserialization code originally comes from ABE. # Note: The deserialization code originally comes from ABE.
from typing import Sequence, Union from typing import Sequence, Union, NamedTuple
from .util import print_error, profiler from .util import print_error, profiler
@ -59,6 +59,10 @@ class NotRecognizedRedeemScript(Exception):
pass pass
TxOutput = NamedTuple("TxOutput", [('type', int), ('address', str), ('value', Union[int, str])])
# ^ value is str when the output is set to max: '!'
class BCDataStream(object): class BCDataStream(object):
def __init__(self): def __init__(self):
self.input = None self.input = None
@ -721,7 +725,7 @@ class Transaction:
return return
d = deserialize(self.raw, force_full_parse) d = deserialize(self.raw, force_full_parse)
self._inputs = d['inputs'] self._inputs = d['inputs']
self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']] self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
self.locktime = d['lockTime'] self.locktime = d['lockTime']
self.version = d['version'] self.version = d['version']
self.is_partial_originally = d['partial'] self.is_partial_originally = d['partial']
@ -1180,17 +1184,17 @@ class Transaction:
def get_outputs(self): def get_outputs(self):
"""convert pubkeys to addresses""" """convert pubkeys to addresses"""
o = [] outputs = []
for type, x, v in self.outputs(): for o in self.outputs():
if type == TYPE_ADDRESS: if o.type == TYPE_ADDRESS:
addr = x addr = o.address
elif type == TYPE_PUBKEY: elif o.type == TYPE_PUBKEY:
# TODO do we really want this conversion? it's not really that address after all # TODO do we really want this conversion? it's not really that address after all
addr = bitcoin.public_key_to_p2pkh(bfh(x)) addr = bitcoin.public_key_to_p2pkh(bfh(o.address))
else: else:
addr = 'SCRIPT ' + x addr = 'SCRIPT ' + o.address
o.append((addr,v)) # consider using yield (addr, v) outputs.append((addr, o.value)) # consider using yield (addr, v)
return o return outputs
def get_output_addresses(self): def get_output_addresses(self):
return [addr for addr, val in self.get_outputs()] return [addr for addr, val in self.get_outputs()]

33
electrum/wallet.py

@ -51,7 +51,7 @@ from .keystore import load_keystore, Hardware_KeyStore
from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW from .storage import multisig_type, STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW
from . import transaction, bitcoin, coinchooser, paymentrequest, contacts from . import transaction, bitcoin, coinchooser, paymentrequest, contacts
from .transaction import Transaction from .transaction import Transaction, TxOutput
from .plugin import run_hook from .plugin import run_hook
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL, from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED)
@ -133,7 +133,7 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100):
inputs, keypairs = sweep_preparations(privkeys, network, imax) inputs, keypairs = sweep_preparations(privkeys, network, imax)
total = sum(i.get('value') for i in inputs) total = sum(i.get('value') for i in inputs)
if fee is None: if fee is None:
outputs = [(TYPE_ADDRESS, recipient, total)] outputs = [TxOutput(TYPE_ADDRESS, recipient, total)]
tx = Transaction.from_io(inputs, outputs) tx = Transaction.from_io(inputs, outputs)
fee = config.estimate_fee(tx.estimated_size()) fee = config.estimate_fee(tx.estimated_size())
if total - fee < 0: if total - fee < 0:
@ -141,7 +141,7 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100):
if total - fee < dust_threshold(network): if total - fee < dust_threshold(network):
raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network))) raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
outputs = [(TYPE_ADDRESS, recipient, total - fee)] outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)]
locktime = network.get_local_height() locktime = network.get_local_height()
tx = Transaction.from_io(inputs, outputs, locktime=locktime) tx = Transaction.from_io(inputs, outputs, locktime=locktime)
@ -538,11 +538,10 @@ class Abstract_Wallet(AddressSynchronizer):
# check outputs # check outputs
i_max = None i_max = None
for i, o in enumerate(outputs): for i, o in enumerate(outputs):
_type, data, value = o if o.type == TYPE_ADDRESS:
if _type == TYPE_ADDRESS: if not is_address(o.address):
if not is_address(data): raise Exception("Invalid bitcoin address: {}".format(o.address))
raise Exception("Invalid bitcoin address: {}".format(data)) if o.value == '!':
if value == '!':
if i_max is not None: if i_max is not None:
raise Exception("More than one output set to spend max") raise Exception("More than one output set to spend max")
i_max = i i_max = i
@ -593,14 +592,13 @@ class Abstract_Wallet(AddressSynchronizer):
else: else:
# FIXME?? this might spend inputs with negative effective value... # FIXME?? this might spend inputs with negative effective value...
sendable = sum(map(lambda x:x['value'], inputs)) sendable = sum(map(lambda x:x['value'], inputs))
_type, data, value = outputs[i_max] outputs[i_max] = outputs[i_max]._replace(value=0)
outputs[i_max] = (_type, data, 0)
tx = Transaction.from_io(inputs, outputs[:]) tx = Transaction.from_io(inputs, outputs[:])
fee = fee_estimator(tx.estimated_size()) fee = fee_estimator(tx.estimated_size())
amount = sendable - tx.output_value() - fee amount = sendable - tx.output_value() - fee
if amount < 0: if amount < 0:
raise NotEnoughFunds() raise NotEnoughFunds()
outputs[i_max] = (_type, data, amount) outputs[i_max] = outputs[i_max]._replace(value=amount)
tx = Transaction.from_io(inputs, outputs[:]) tx = Transaction.from_io(inputs, outputs[:])
# Sort the inputs and outputs deterministically # Sort the inputs and outputs deterministically
@ -694,14 +692,13 @@ class Abstract_Wallet(AddressSynchronizer):
s = sorted(s, key=lambda x: x[2]) s = sorted(s, key=lambda x: x[2])
for o in s: for o in s:
i = outputs.index(o) i = outputs.index(o)
otype, address, value = o if o.value - delta >= self.dust_threshold():
if value - delta >= self.dust_threshold(): outputs[i] = o._replace(value=o.value-delta)
outputs[i] = otype, address, value - delta
delta = 0 delta = 0
break break
else: else:
del outputs[i] del outputs[i]
delta -= value delta -= o.value
if delta > 0: if delta > 0:
continue continue
if delta > 0: if delta > 0:
@ -714,8 +711,8 @@ class Abstract_Wallet(AddressSynchronizer):
def cpfp(self, tx, fee): def cpfp(self, tx, fee):
txid = tx.txid() txid = tx.txid()
for i, o in enumerate(tx.outputs()): for i, o in enumerate(tx.outputs()):
otype, address, value = o address, value = o.address, o.value
if otype == TYPE_ADDRESS and self.is_mine(address): if o.type == TYPE_ADDRESS and self.is_mine(address):
break break
else: else:
return return
@ -725,7 +722,7 @@ class Abstract_Wallet(AddressSynchronizer):
return return
self.add_input_info(item) self.add_input_info(item)
inputs = [item] inputs = [item]
outputs = [(TYPE_ADDRESS, address, value - fee)] outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)]
locktime = self.get_local_height() locktime = self.get_local_height()
# note: no need to call tx.BIP_LI01_sort() here - single input/output # note: no need to call tx.BIP_LI01_sort() here - single input/output
return Transaction.from_io(inputs, outputs, locktime=locktime) return Transaction.from_io(inputs, outputs, locktime=locktime)

Loading…
Cancel
Save