mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
transaction: introduce TxOutput namedtuple
This commit is contained in:
parent
f64062b6f1
commit
2eb72d496f
21 changed files with 122 additions and 109 deletions
|
@ -28,7 +28,7 @@ from collections import defaultdict
|
|||
from . import bitcoin
|
||||
from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
|
||||
from .util import PrintError, profiler, bfh, VerifiedTxInfo, TxMinedStatus
|
||||
from .transaction import Transaction
|
||||
from .transaction import Transaction, TxOutput
|
||||
from .synchronizer import Synchronizer
|
||||
from .verifier import SPV
|
||||
from .blockchain import hash_header
|
||||
|
@ -112,12 +112,11 @@ class AddressSynchronizer(PrintError):
|
|||
return addr
|
||||
return None
|
||||
|
||||
def get_txout_address(self, txo):
|
||||
_type, x, v = txo
|
||||
if _type == TYPE_ADDRESS:
|
||||
addr = x
|
||||
elif _type == TYPE_PUBKEY:
|
||||
addr = bitcoin.public_key_to_p2pkh(bfh(x))
|
||||
def get_txout_address(self, txo: TxOutput):
|
||||
if txo.type == TYPE_ADDRESS:
|
||||
addr = txo.address
|
||||
elif txo.type == TYPE_PUBKEY:
|
||||
addr = bitcoin.public_key_to_p2pkh(bfh(txo.address))
|
||||
else:
|
||||
addr = None
|
||||
return addr
|
||||
|
|
|
@ -26,7 +26,7 @@ from collections import defaultdict, namedtuple
|
|||
from math import floor, log10
|
||||
|
||||
from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
|
||||
from .transaction import Transaction
|
||||
from .transaction import Transaction, TxOutput
|
||||
from .util import NotEnoughFunds, PrintError
|
||||
|
||||
|
||||
|
@ -178,7 +178,7 @@ class CoinChooserBase(PrintError):
|
|||
# size of the change output, add it to the transaction.
|
||||
dust = sum(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)]
|
||||
self.print_error('change:', change)
|
||||
if dust:
|
||||
|
|
|
@ -38,7 +38,7 @@ from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_enc
|
|||
from . import bitcoin
|
||||
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
|
||||
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 .plugin import run_hook
|
||||
|
||||
|
@ -226,7 +226,7 @@ class Commands:
|
|||
txin['signatures'] = [None]
|
||||
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.sign(keypairs)
|
||||
return tx.as_dict()
|
||||
|
@ -415,7 +415,7 @@ class Commands:
|
|||
for address, amount in outputs:
|
||||
address = self._resolver(address)
|
||||
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)
|
||||
tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
|
||||
|
|
|
@ -713,13 +713,14 @@ class ElectrumWindow(App):
|
|||
self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy
|
||||
|
||||
def get_max_amount(self):
|
||||
from electrum.transaction import TxOutput
|
||||
if run_hook('abort_send', self):
|
||||
return ''
|
||||
inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
|
||||
if not inputs:
|
||||
return ''
|
||||
addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
|
||||
outputs = [(TYPE_ADDRESS, addr, '!')]
|
||||
outputs = [TxOutput(TYPE_ADDRESS, addr, '!')]
|
||||
try:
|
||||
tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
|
||||
except NoDynamicFeeEstimates as e:
|
||||
|
|
|
@ -206,9 +206,9 @@ class OutputList(RecycleView):
|
|||
|
||||
def update(self, outputs):
|
||||
res = []
|
||||
for (type, address, amount) in outputs:
|
||||
value = self.app.format_amount_and_units(amount)
|
||||
res.append({'address': address, 'value': value})
|
||||
for o in outputs:
|
||||
value = self.app.format_amount_and_units(o.value)
|
||||
res.append({'address': o.address, 'value': value})
|
||||
self.data = res
|
||||
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ from kivy.utils import platform
|
|||
|
||||
from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat
|
||||
from electrum import bitcoin
|
||||
from electrum.transaction import TxOutput
|
||||
from electrum.util import timestamp_to_datetime
|
||||
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
|
||||
from electrum.plugin import run_hook
|
||||
|
@ -256,7 +257,7 @@ class SendScreen(CScreen):
|
|||
except:
|
||||
self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
|
||||
return
|
||||
outputs = [(bitcoin.TYPE_ADDRESS, address, amount)]
|
||||
outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
|
||||
message = self.screen.message
|
||||
amount = sum(map(lambda x:x[2], outputs))
|
||||
if self.app.electrum_config.get('use_rbf'):
|
||||
|
|
|
@ -50,7 +50,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
|
|||
export_meta, import_meta, bh2u, bfh, InvalidPassword,
|
||||
base_units, base_units_list, base_unit_name_to_decimal_point,
|
||||
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.wallet import Multisig_Wallet, CannotBumpFee
|
||||
|
||||
|
@ -1306,7 +1306,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
outputs = self.payto_e.get_outputs(self.is_max)
|
||||
if not outputs:
|
||||
_type, addr = self.get_payto_or_dummy()
|
||||
outputs = [(_type, addr, amount)]
|
||||
outputs = [TxOutput(_type, addr, amount)]
|
||||
is_sweep = bool(self.tx_external_keypairs)
|
||||
make_tx = lambda fee_est: \
|
||||
self.wallet.make_unsigned_transaction(
|
||||
|
@ -1485,14 +1485,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
self.show_error(_('No outputs'))
|
||||
return
|
||||
|
||||
for _type, addr, amount in outputs:
|
||||
if addr is None:
|
||||
for o in outputs:
|
||||
if o.address is None:
|
||||
self.show_error(_('Bitcoin Address is None'))
|
||||
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'))
|
||||
return
|
||||
if amount is None:
|
||||
if o.value is None:
|
||||
self.show_error(_('Invalid Amount'))
|
||||
return
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ from decimal import Decimal
|
|||
|
||||
from electrum import bitcoin
|
||||
from electrum.util import bfh
|
||||
from electrum.transaction import TxOutput
|
||||
|
||||
from .qrtextedit import ScanQRTextEdit
|
||||
from .completion_text_edit import CompletionTextEdit
|
||||
|
@ -77,7 +78,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
|
|||
x, y = line.split(',')
|
||||
out_type, out = self.parse_output(x)
|
||||
amount = self.parse_amount(y)
|
||||
return out_type, out, amount
|
||||
return TxOutput(out_type, out, amount)
|
||||
|
||||
def parse_output(self, x):
|
||||
try:
|
||||
|
@ -139,16 +140,16 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
|
|||
is_max = False
|
||||
for i, line in enumerate(lines):
|
||||
try:
|
||||
_type, to_address, amount = self.parse_address_and_amount(line)
|
||||
output = self.parse_address_and_amount(line)
|
||||
except:
|
||||
self.errors.append((i, line.strip()))
|
||||
continue
|
||||
|
||||
outputs.append((_type, to_address, amount))
|
||||
if amount == '!':
|
||||
outputs.append(output)
|
||||
if output.value == '!':
|
||||
is_max = True
|
||||
else:
|
||||
total += amount
|
||||
total += output.value
|
||||
|
||||
self.win.is_max = is_max
|
||||
self.outputs = outputs
|
||||
|
@ -174,7 +175,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit):
|
|||
amount = self.amount_edit.get_amount()
|
||||
|
||||
_type, addr = self.payto_address
|
||||
self.outputs = [(_type, addr, amount)]
|
||||
self.outputs = [TxOutput(_type, addr, amount)]
|
||||
|
||||
return self.outputs[:]
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ _ = lambda x:x
|
|||
from electrum import WalletStorage, Wallet
|
||||
from electrum.util import format_satoshis, set_verbosity
|
||||
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
|
||||
from electrum.transaction import TxOutput
|
||||
import getpass, datetime
|
||||
|
||||
# minimal fdisk like gui for console usage
|
||||
|
@ -189,7 +190,8 @@ class ElectrumGui:
|
|||
if c == "n": return
|
||||
|
||||
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:
|
||||
print(str(e))
|
||||
return
|
||||
|
|
|
@ -6,6 +6,7 @@ import getpass
|
|||
import electrum
|
||||
from electrum.util import format_satoshis, set_verbosity
|
||||
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
|
||||
from electrum.transaction import TxOutput
|
||||
from .. import Wallet, WalletStorage
|
||||
|
||||
_ = lambda x:x
|
||||
|
@ -340,7 +341,8 @@ class ElectrumGui:
|
|||
else:
|
||||
password = None
|
||||
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:
|
||||
self.show_message(str(e))
|
||||
return
|
||||
|
|
|
@ -42,6 +42,7 @@ from .util import print_error, bh2u, bfh
|
|||
from .util import export_meta, import_meta
|
||||
|
||||
from .bitcoin import TYPE_ADDRESS
|
||||
from .transaction import TxOutput
|
||||
|
||||
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', '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 = []
|
||||
for o in self.details.outputs:
|
||||
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.payment_url = self.details.payment_url
|
||||
|
||||
|
@ -225,8 +226,8 @@ class PaymentRequest:
|
|||
|
||||
def get_address(self):
|
||||
o = self.outputs[0]
|
||||
assert o[0] == TYPE_ADDRESS
|
||||
return o[1]
|
||||
assert o.type == TYPE_ADDRESS
|
||||
return o.address
|
||||
|
||||
def get_requestor(self):
|
||||
return self.requestor if self.requestor else self.get_address()
|
||||
|
|
|
@ -534,9 +534,9 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
|
|||
self.give_error("No matching x_key for sign_transaction") # should never happen
|
||||
|
||||
# Build pubkeyarray from outputs
|
||||
for _type, address, amount in tx.outputs():
|
||||
assert _type == TYPE_ADDRESS
|
||||
info = tx.output_info.get(address)
|
||||
for o in tx.outputs():
|
||||
assert o.type == TYPE_ADDRESS
|
||||
info = tx.output_info.get(o.address)
|
||||
if info is not None:
|
||||
index, xpubs, m = info
|
||||
changePath = self.get_derivation() + "/%d/%d" % index
|
||||
|
|
|
@ -28,7 +28,7 @@ from electrum.plugin import BasePlugin, hook
|
|||
from electrum.i18n import _
|
||||
from electrum.bitcoin import is_address, TYPE_SCRIPT
|
||||
from electrum.util import bfh
|
||||
from electrum.transaction import opcodes
|
||||
from electrum.transaction import opcodes, TxOutput
|
||||
|
||||
|
||||
class HW_PluginBase(BasePlugin):
|
||||
|
@ -91,13 +91,13 @@ def is_any_tx_output_on_change_branch(tx):
|
|||
return False
|
||||
|
||||
|
||||
def trezor_validate_op_return_output_and_get_data(_type, address, amount):
|
||||
if _type != TYPE_SCRIPT:
|
||||
raise Exception("Unexpected output type: {}".format(_type))
|
||||
script = bfh(address)
|
||||
def trezor_validate_op_return_output_and_get_data(output: TxOutput) -> bytes:
|
||||
if output.type != TYPE_SCRIPT:
|
||||
raise Exception("Unexpected output type: {}".format(output.type))
|
||||
script = bfh(output.address)
|
||||
if not (script[0] == opcodes.OP_RETURN and
|
||||
script[1] == len(script) - 2 and script[1] <= 75):
|
||||
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."))
|
||||
return script[2:]
|
||||
|
|
|
@ -382,7 +382,7 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||
txoutputtype.amount = amount
|
||||
if _type == TYPE_SCRIPT:
|
||||
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:
|
||||
if is_segwit_address(address):
|
||||
txoutputtype.script_type = self.types.PAYTOWITNESS
|
||||
|
@ -401,7 +401,8 @@ class KeepKeyPlugin(HW_PluginBase):
|
|||
has_change = False
|
||||
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
|
||||
|
||||
info = tx.output_info.get(address)
|
||||
|
|
|
@ -394,9 +394,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
|||
self.give_error("Transaction with more than 2 outputs not supported")
|
||||
has_change = False
|
||||
any_output_on_change_branch = is_any_tx_output_on_change_branch(tx)
|
||||
for _type, address, amount in tx.outputs():
|
||||
assert _type == TYPE_ADDRESS
|
||||
info = tx.output_info.get(address)
|
||||
for o in tx.outputs():
|
||||
assert o.type == TYPE_ADDRESS
|
||||
info = tx.output_info.get(o.address)
|
||||
if (info is not None) and len(tx.outputs()) > 1 \
|
||||
and not has_change:
|
||||
index, xpubs, m = info
|
||||
|
@ -407,9 +407,9 @@ class Ledger_KeyStore(Hardware_KeyStore):
|
|||
changePath = self.get_derivation()[2:] + "/%d/%d"%index
|
||||
has_change = True
|
||||
else:
|
||||
output = address
|
||||
output = o.address
|
||||
else:
|
||||
output = address
|
||||
output = o.address
|
||||
|
||||
self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
|
||||
try:
|
||||
|
|
|
@ -453,7 +453,7 @@ class SafeTPlugin(HW_PluginBase):
|
|||
txoutputtype.amount = amount
|
||||
if _type == TYPE_SCRIPT:
|
||||
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:
|
||||
txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
|
||||
txoutputtype.address = address
|
||||
|
@ -463,7 +463,8 @@ class SafeTPlugin(HW_PluginBase):
|
|||
has_change = False
|
||||
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
|
||||
|
||||
info = tx.output_info.get(address)
|
||||
|
|
|
@ -464,7 +464,7 @@ class TrezorPlugin(HW_PluginBase):
|
|||
txoutputtype.amount = amount
|
||||
if _type == TYPE_SCRIPT:
|
||||
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:
|
||||
txoutputtype.script_type = self.types.OutputScriptType.PAYTOADDRESS
|
||||
txoutputtype.address = address
|
||||
|
@ -474,7 +474,8 @@ class TrezorPlugin(HW_PluginBase):
|
|||
has_change = False
|
||||
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
|
||||
|
||||
info = tx.output_info.get(address)
|
||||
|
|
|
@ -33,6 +33,7 @@ from urllib.parse import quote
|
|||
|
||||
from electrum import bitcoin, ecc, constants, keystore, version
|
||||
from electrum.bitcoin import *
|
||||
from electrum.transaction import TxOutput
|
||||
from electrum.mnemonic import Mnemonic
|
||||
from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
|
||||
from electrum.i18n import _
|
||||
|
@ -273,7 +274,7 @@ class Wallet_2fa(Multisig_Wallet):
|
|||
fee = self.extra_fee(config) if not is_sweep else 0
|
||||
if fee:
|
||||
address = self.billing_info['billing_address']
|
||||
fee_output = (TYPE_ADDRESS, address, fee)
|
||||
fee_output = TxOutput(TYPE_ADDRESS, address, fee)
|
||||
try:
|
||||
tx = mk_tx(outputs + [fee_output])
|
||||
except NotEnoughFunds:
|
||||
|
@ -395,9 +396,9 @@ class TrustedCoinPlugin(BasePlugin):
|
|||
def get_tx_extra_fee(self, wallet, tx):
|
||||
if type(wallet) != Wallet_2fa:
|
||||
return
|
||||
for _type, addr, amount in tx.outputs():
|
||||
if _type == TYPE_ADDRESS and wallet.is_billing_address(addr):
|
||||
return addr, amount
|
||||
for o in tx.outputs():
|
||||
if o.type == TYPE_ADDRESS and wallet.is_billing_address(o.address):
|
||||
return o.address, o.value
|
||||
|
||||
def finish_requesting(func):
|
||||
def f(self, *args, **kwargs):
|
||||
|
|
|
@ -10,6 +10,7 @@ from electrum import SimpleConfig
|
|||
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.util import bfh, bh2u
|
||||
from electrum.transaction import TxOutput
|
||||
|
||||
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 -> 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)
|
||||
|
||||
self.assertTrue(tx.is_complete())
|
||||
|
@ -552,7 +553,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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)
|
||||
|
||||
self.assertTrue(tx.is_complete())
|
||||
|
@ -605,7 +606,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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 = Transaction(tx.serialize()) # simulates moving partial txn between cosigners
|
||||
self.assertFalse(tx.is_complete())
|
||||
|
@ -628,7 +629,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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)
|
||||
|
||||
self.assertTrue(tx.is_complete())
|
||||
|
@ -696,7 +697,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet1a.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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)
|
||||
txid = tx.txid()
|
||||
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)
|
||||
|
||||
# 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)
|
||||
txid = tx.txid()
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
self.assertTrue(tx.is_complete())
|
||||
|
@ -796,7 +797,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet2.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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)
|
||||
|
||||
self.assertTrue(tx.is_complete())
|
||||
|
@ -832,7 +833,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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)
|
||||
tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
|
||||
tx.set_rbf(True)
|
||||
|
@ -918,7 +919,7 @@ class TestWalletSending(TestCaseForTestnet):
|
|||
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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)
|
||||
tx = wallet.make_unsigned_transaction(coins, outputs, config=self.config, fixed_fee=5000)
|
||||
tx.set_rbf(True)
|
||||
|
@ -1048,7 +1049,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1088,7 +1089,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325341
|
||||
|
@ -1129,7 +1130,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325341
|
||||
|
@ -1165,7 +1166,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1199,7 +1200,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1233,7 +1234,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1270,7 +1271,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1307,7 +1308,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1344,7 +1345,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325340
|
||||
|
@ -1393,7 +1394,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325503
|
||||
|
@ -1450,7 +1451,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325504
|
||||
|
@ -1509,7 +1510,7 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
|
|||
wallet_online.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# 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.set_rbf(True)
|
||||
tx.locktime = 1325505
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
# 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
|
||||
|
||||
|
@ -59,6 +59,10 @@ class NotRecognizedRedeemScript(Exception):
|
|||
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):
|
||||
def __init__(self):
|
||||
self.input = None
|
||||
|
@ -721,7 +725,7 @@ class Transaction:
|
|||
return
|
||||
d = deserialize(self.raw, force_full_parse)
|
||||
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.version = d['version']
|
||||
self.is_partial_originally = d['partial']
|
||||
|
@ -1180,17 +1184,17 @@ class Transaction:
|
|||
|
||||
def get_outputs(self):
|
||||
"""convert pubkeys to addresses"""
|
||||
o = []
|
||||
for type, x, v in self.outputs():
|
||||
if type == TYPE_ADDRESS:
|
||||
addr = x
|
||||
elif type == TYPE_PUBKEY:
|
||||
outputs = []
|
||||
for o in self.outputs():
|
||||
if o.type == TYPE_ADDRESS:
|
||||
addr = o.address
|
||||
elif o.type == TYPE_PUBKEY:
|
||||
# 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:
|
||||
addr = 'SCRIPT ' + x
|
||||
o.append((addr,v)) # consider using yield (addr, v)
|
||||
return o
|
||||
addr = 'SCRIPT ' + o.address
|
||||
outputs.append((addr, o.value)) # consider using yield (addr, v)
|
||||
return outputs
|
||||
|
||||
def get_output_addresses(self):
|
||||
return [addr for addr, val in self.get_outputs()]
|
||||
|
|
|
@ -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 . import transaction, bitcoin, coinchooser, paymentrequest, contacts
|
||||
from .transaction import Transaction
|
||||
from .transaction import Transaction, TxOutput
|
||||
from .plugin import run_hook
|
||||
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
|
||||
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)
|
||||
total = sum(i.get('value') for i in inputs)
|
||||
if fee is None:
|
||||
outputs = [(TYPE_ADDRESS, recipient, total)]
|
||||
outputs = [TxOutput(TYPE_ADDRESS, recipient, total)]
|
||||
tx = Transaction.from_io(inputs, outputs)
|
||||
fee = config.estimate_fee(tx.estimated_size())
|
||||
if total - fee < 0:
|
||||
|
@ -141,7 +141,7 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100):
|
|||
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)))
|
||||
|
||||
outputs = [(TYPE_ADDRESS, recipient, total - fee)]
|
||||
outputs = [TxOutput(TYPE_ADDRESS, recipient, total - fee)]
|
||||
locktime = network.get_local_height()
|
||||
|
||||
tx = Transaction.from_io(inputs, outputs, locktime=locktime)
|
||||
|
@ -538,11 +538,10 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
# check outputs
|
||||
i_max = None
|
||||
for i, o in enumerate(outputs):
|
||||
_type, data, value = o
|
||||
if _type == TYPE_ADDRESS:
|
||||
if not is_address(data):
|
||||
raise Exception("Invalid bitcoin address: {}".format(data))
|
||||
if value == '!':
|
||||
if o.type == TYPE_ADDRESS:
|
||||
if not is_address(o.address):
|
||||
raise Exception("Invalid bitcoin address: {}".format(o.address))
|
||||
if o.value == '!':
|
||||
if i_max is not None:
|
||||
raise Exception("More than one output set to spend max")
|
||||
i_max = i
|
||||
|
@ -593,14 +592,13 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
else:
|
||||
# FIXME?? this might spend inputs with negative effective value...
|
||||
sendable = sum(map(lambda x:x['value'], inputs))
|
||||
_type, data, value = outputs[i_max]
|
||||
outputs[i_max] = (_type, data, 0)
|
||||
outputs[i_max] = outputs[i_max]._replace(value=0)
|
||||
tx = Transaction.from_io(inputs, outputs[:])
|
||||
fee = fee_estimator(tx.estimated_size())
|
||||
amount = sendable - tx.output_value() - fee
|
||||
if amount < 0:
|
||||
raise NotEnoughFunds()
|
||||
outputs[i_max] = (_type, data, amount)
|
||||
outputs[i_max] = outputs[i_max]._replace(value=amount)
|
||||
tx = Transaction.from_io(inputs, outputs[:])
|
||||
|
||||
# Sort the inputs and outputs deterministically
|
||||
|
@ -694,14 +692,13 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
s = sorted(s, key=lambda x: x[2])
|
||||
for o in s:
|
||||
i = outputs.index(o)
|
||||
otype, address, value = o
|
||||
if value - delta >= self.dust_threshold():
|
||||
outputs[i] = otype, address, value - delta
|
||||
if o.value - delta >= self.dust_threshold():
|
||||
outputs[i] = o._replace(value=o.value-delta)
|
||||
delta = 0
|
||||
break
|
||||
else:
|
||||
del outputs[i]
|
||||
delta -= value
|
||||
delta -= o.value
|
||||
if delta > 0:
|
||||
continue
|
||||
if delta > 0:
|
||||
|
@ -714,8 +711,8 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
def cpfp(self, tx, fee):
|
||||
txid = tx.txid()
|
||||
for i, o in enumerate(tx.outputs()):
|
||||
otype, address, value = o
|
||||
if otype == TYPE_ADDRESS and self.is_mine(address):
|
||||
address, value = o.address, o.value
|
||||
if o.type == TYPE_ADDRESS and self.is_mine(address):
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
@ -725,7 +722,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
return
|
||||
self.add_input_info(item)
|
||||
inputs = [item]
|
||||
outputs = [(TYPE_ADDRESS, address, value - fee)]
|
||||
outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)]
|
||||
locktime = self.get_local_height()
|
||||
# note: no need to call tx.BIP_LI01_sort() here - single input/output
|
||||
return Transaction.from_io(inputs, outputs, locktime=locktime)
|
||||
|
|
Loading…
Add table
Reference in a new issue