Skip to content

Commit 077e7c7

Browse files
committed
add some tests
1 parent c6f7246 commit 077e7c7

File tree

3 files changed

+141
-86
lines changed

3 files changed

+141
-86
lines changed

electrum/gui/qt/main_window.py

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,8 +1683,10 @@ def on_failure(exc_info):
16831683
on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
16841684
if external_keypairs:
16851685
# can sign directly
1686+
print('\nexternal keys: ',external_keypairs)#
16861687
task = partial(tx.sign, external_keypairs)
16871688
else:
1689+
print('\nexternal keys2: ', external_keypairs) #
16881690
task = partial(self.wallet.sign_transaction, tx, password)
16891691
msg = _('Signing transaction...')
16901692
WaitingDialog(self, msg, task, on_success, on_failure)
@@ -1731,57 +1733,6 @@ def broadcast_done(result):
17311733
WaitingDialog(self, _('Broadcasting transaction...'),
17321734
broadcast_thread, broadcast_done, self.on_error)
17331735

1734-
def exchange_psbt_http(self, payjoin):
1735-
""" """
1736-
import requests
1737-
assert payjoin.is_complete()
1738-
1739-
print(payjoin.to_json())
1740-
print(payjoin.serialize_as_base64())
1741-
1742-
for txin in payjoin.inputs():
1743-
print(txin)
1744-
print(txin.utxo)
1745-
print('\n',txin.witness_utxo)
1746-
print('\n', txin.to_json())
1747-
1748-
if txin.utxo:
1749-
print(txin.utxo.outputs())
1750-
print(txin.prevout.out_idx)
1751-
print(txin.utxo.outputs()[txin.prevout.out_idx])
1752-
1753-
print()
1754-
1755-
1756-
print('\n',txin.utxo)
1757-
txin.convert_utxo_to_witness_utxo()
1758-
print('\n', txin.witness_utxo)
1759-
print('\n', txin.to_json())
1760-
print()
1761-
payjoin.convert_all_utxos_to_witness_utxos()
1762-
print(payjoin.serialize_as_base64())
1763-
print(payjoin.to_json())
1764-
1765-
1766-
url = 'https://testnet.demo.btcpayserver.org/BTC/pj'
1767-
payload = payjoin.serialize_as_base64()
1768-
headers = {'content-type': 'text/plain',
1769-
'content-length': str(len(payload))
1770-
}
1771-
print(headers)
1772-
1773-
try:
1774-
r = requests.post(url, data=payload, headers=headers)
1775-
except:
1776-
pass
1777-
1778-
print(payload)
1779-
print(r.status_code)
1780-
print(r.headers)
1781-
print(r.text)
1782-
1783-
1784-
17851736
def mktx_for_open_channel(self, funding_sat):
17861737
coins = self.get_coins(nonlocal_only=True)
17871738
make_tx = lambda fee_est: self.wallet.lnworker.mktx_for_open_channel(coins=coins,

electrum/gui/qt/transaction_dialog.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from electrum.i18n import _
4646
from electrum.plugin import run_hook
4747
from electrum import simple_config
48-
from electrum.transaction import SerializationError, Transaction, PartialTransaction, PartialTxInput
48+
from electrum.transaction import SerializationError, Transaction, PartialTransaction, PartialTxInput, PayjoinTransaction
4949
from electrum.logging import get_logger
5050

5151
from .util import (MessageBoxMixin, read_QIcon, Buttons, icon_path,
@@ -105,12 +105,10 @@ def __init__(self, *, parent: 'ElectrumWindow', desc, prompt_if_unsaved, finaliz
105105
self.config = parent.config
106106
self.wallet = parent.wallet
107107
self.prompt_if_unsaved = prompt_if_unsaved
108-
if payjoin:
109-
self.pj = payjoin['pj']
110-
self.pjos = payjoin['pjos']
111-
self.pj_available = True
112-
else:
113-
self.pj_available = False
108+
109+
self.payjoin = PayjoinTransaction(payjoin)
110+
print(self.payjoin)#
111+
114112
self.saved = False
115113
self.desc = desc
116114
self.setMinimumWidth(950)
@@ -224,15 +222,31 @@ def set_tx(self, tx: 'Transaction'):
224222
# note: this might fetch prev txs over the network.
225223
tx.add_info_from_wallet(self.wallet)
226224

225+
226+
227227
def do_broadcast(self):
228228
self.main_window.push_top_level_window(self)
229+
229230
if self.payjoin_cb.isChecked():
230-
self.main_window.exchange_psbt_http(self._gettx_for_coinjoin())
231-
else:
232-
try:
233-
self.main_window.broadcast_transaction(self.tx)
234-
finally:
235-
self.main_window.pop_top_level_window(self)
231+
self.payjoin.set_tx(self.tx)
232+
tx = self.payjoin.do_payjoin()
233+
if tx is not None:
234+
#self.set_tx(tx)
235+
self.tx = copy.deepcopy(tx)
236+
print('broadcast1: ', self.tx.to_json())#
237+
print('broadcast1: ', self.tx.serialize_as_base64())#
238+
self.sign()
239+
print('external keyspairs', self.external_keypairs)
240+
print('broadcast2: ', self.tx.to_json())#
241+
print('broadcast2: ', self.tx.serialize_as_base64())#
242+
print('broadcast2: ', self.tx._serialize_as_base64()) #
243+
print('broadcast2: ', self.tx.is_complete()) #
244+
"""
245+
try:
246+
self.main_window.broadcast_transaction(self.tx)
247+
finally:
248+
self.main_window.pop_top_level_window(self)
249+
"""
236250
self.saved = True
237251
self.update()
238252

@@ -671,8 +685,8 @@ def add_tx_stats(self, vbox):
671685
# set visibility after parenting can be determined by Qt
672686
self.rbf_label.setVisible(self.finalized)
673687
self.rbf_cb.setVisible(not self.finalized)
674-
self.payjoin_cb.setVisible(self.pj_available)
675-
print('pj_available in dialog:', self.pj_available)#
688+
self.payjoin_cb.setVisible(self.payjoin.is_available())
689+
print('pj_available in dialog:', self.payjoin.is_available())#
676690
self.locktime_final_label.setVisible(self.finalized)
677691
self.locktime_setter_widget.setVisible(not self.finalized)
678692

electrum/transaction.py

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
from enum import IntEnum
3939
import itertools
4040
import binascii
41+
import requests
42+
import copy
4143

4244
from . import ecc, bitcoin, constants, segwit_addr, bip32
4345
from .bip32 import BIP32Node
@@ -1345,12 +1347,14 @@ def scriptpubkey(self) -> Optional[bytes]:
13451347
return None
13461348

13471349
def is_complete(self) -> bool:
1348-
if self.script_sig is not None and self.witness is not None:
1350+
if self.script_sig is not None or self.witness is not None:
13491351
return True
13501352
if self.is_coinbase_input():
13511353
return True
13521354
if self.script_sig is not None and not Transaction.is_segwit_input(self):
13531355
return True
1356+
if self.witness is not None and Transaction.is_segwit_input(self):
1357+
return True
13541358
signatures = list(self.part_sigs.values())
13551359
s = len(signatures)
13561360
# note: The 'script_type' field is currently only set by the wallet,
@@ -1374,7 +1378,7 @@ def clear_fields_when_finalized():
13741378
self.redeem_script = None
13751379
self.witness_script = None
13761380

1377-
if self.script_sig is not None and self.witness is not None:
1381+
if self.script_sig is not None or self.witness is not None:
13781382
clear_fields_when_finalized()
13791383
return # already finalized
13801384
if self.is_complete():
@@ -1992,29 +1996,115 @@ def remove_signatures(self):
19921996
self.invalidate_ser_cache()
19931997

19941998

1995-
class PayjoinTransaction(PartialTransaction):
1999+
class PayjoinTransaction():
19962000

1997-
@classmethod
1998-
def from_tx(cls, tx: Transaction) -> 'PayjoinTransaction':
1999-
res = cls(None)
2000-
res._inputs = [PartialTxInput.from_txin(txin, strip_witness = False) for txin in tx.inputs()]
2001-
res._outputs = [PartialTxOutput.from_txout(txout) for txout in tx.outputs()]
2002-
res.version = tx.version
2003-
res.locktime = tx.locktime
2004-
return res
2001+
def __init__(self, payjoin_link=None):
2002+
self._version = 1
2003+
self.pj = payjoin_link.get('pj') if payjoin_link else None
2004+
self.pjos = payjoin_link.get('pjos') if payjoin_link else None
20052005

2006-
def serialize_as_base64(self, force_psbt = True) -> str:
2007-
raw_bytes = self.serialize_as_bytes(force_psbt = force_psbt)
2008-
return base64.b64encode(raw_bytes).decode('ascii')
2006+
self.payjoin_original = None
2007+
self.pj_proposal_received = False
20092008

2010-
def check_for_encrypted_connection(self):
2011-
pass
20122009

2013-
def create_original_psbt(self):
2014-
pass
2010+
def is_available(self):
2011+
return self.pj is not None
20152012

2016-
def check_payjoin_proposal(self):
2017-
pass
2013+
def set_tx(self, tx: PartialTransaction) -> None:
2014+
if self.is_available():
2015+
self.tx = copy.deepcopy(tx)
2016+
2017+
def prepare_original_psbt(self):
2018+
assert not self.pj_proposal_received
2019+
assert self.tx.is_complete()
2020+
self.payjoin_original = copy.deepcopy(self.tx)
2021+
self.payjoin_original.prepare_for_export_for_coinjoin()
2022+
self.payjoin_original.convert_all_utxos_to_witness_utxos()
2023+
2024+
def do_payjoin(self):
2025+
self.prepare_original_psbt()
2026+
print('\noriginal psbt',self.payjoin_original.to_json())#
2027+
for i,txin in enumerate(self.payjoin_original.inputs()):#
2028+
print('\n txinputs:',i,txin.to_json()) #
2029+
2030+
2031+
if not self.exchange_payjoin_original():
2032+
return None
2033+
self.pj_proposal = PartialTransaction.from_raw_psbt(self.payjoin_proposal_b64)
2034+
self.pj_proposal_received = True
2035+
if not self.validate_payjoin_proposal():
2036+
return None
2037+
return self.pj_proposal
2038+
2039+
2040+
2041+
def exchange_payjoin_original(self, url=None):
2042+
""" """
2043+
url = self.pj
2044+
payload = self.payjoin_original.serialize_as_base64()
2045+
headers = {'content-type': 'text/plain',
2046+
'content-length': str(len(payload))
2047+
}
2048+
print('header ',headers)#
2049+
try:
2050+
r = requests.post(url, data=payload, headers=headers)
2051+
print(r.status_code)#
2052+
print(r.text)#
2053+
if r.status_code==200:
2054+
self.payjoin_proposal_b64 = r.text
2055+
print(self.payjoin_proposal_b64)#
2056+
else:
2057+
_logger.debug(f"payjoin is flawed {r.text}")
2058+
return False
2059+
except Exception as e:
2060+
print(repr(e))#
2061+
return False
2062+
return True
2063+
2064+
@staticmethod
2065+
def validate_inputs_and_outputs(pj_original: PartialTransaction, pj_proposal: PartialTransaction) -> Optional[bool]:
2066+
for txin in pj_original.inputs():
2067+
# check if order is important
2068+
if not txin.prevout.to_str() in [x.prevout.to_str() for x in pj_proposal.inputs()]:
2069+
# list(payjoin_proposal.inputs()).prevout.to_str():
2070+
raise Exception(f"Inputs from the original payjoin missing in payjoin proposal.")
2071+
for txin in pj_proposal.inputs():
2072+
if not txin.is_complete() and not txin.prevout.to_str() in [x.prevout.to_str() for x in pj_original.inputs()]:
2073+
raise Exception(f"Newly added inputs are not signed.")
2074+
2075+
for txout in pj_original.outputs():
2076+
# check if order is important
2077+
if txout.is_mine and not txout.scriptpubkey.hex() in [x.scriptpubkey.hex() for x in pj_proposal.outputs()]:
2078+
raise Exception(f"Sender outputs from the original payjoin missing in payjoin proposal.")
2079+
for txout in pj_proposal.outputs():
2080+
if not txout.scriptpubkey.hex() in [x.scriptpubkey.hex() for x in pj_original.outputs()]:
2081+
raise Exception(f"Newly added outputs.")
2082+
return True
2083+
2084+
2085+
def validate_payjoin_proposal(self):
2086+
2087+
if not self.validate_inputs_and_outputs(self.payjoin_original, self.pj_proposal):
2088+
return False
2089+
"""
2090+
if self.payjoin_original.get_fee() > self.payjoin_proposal.get_fee():
2091+
return False
2092+
"""
2093+
for i, txin in enumerate(self.tx.inputs()):
2094+
if self.pj_proposal._inputs[i].prevout.to_str() != txin.prevout.to_str():
2095+
raise Exception(f"Inputs from the original payjoin missing in payjoin proposal2.")
2096+
else:
2097+
print('\n sig \n',txin.to_json())
2098+
#procedure for parttxin
2099+
txin.part_sigs = {}
2100+
txin.script_sig = None
2101+
txin.witness = None
2102+
self.pj_proposal._inputs[i] = txin
2103+
print('\n sig2 \n', txin.to_json())
2104+
2105+
2106+
self.pj_proposal.invalidate_ser_cache()
2107+
return True
20182108

20192109

20202110
def pack_bip32_root_fingerprint_and_int_path(xfp: bytes, path: Sequence[int]) -> bytes:

0 commit comments

Comments
 (0)