diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index fb4f36be4..63ab6d4fa 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -38,7 +38,7 @@ jobs: run: brew install llvm - name: Install wasm-bindgen - run: cargo install wasm-bindgen-cli + run: cargo install --locked wasm-bindgen-cli - name: "Install dependencies" run: npm ci diff --git a/payjoin-ffi/python/test/test_payjoin_integration_test.py b/payjoin-ffi/python/test/test_payjoin_integration_test.py index ccb30e6d5..ba2693b2d 100644 --- a/payjoin-ffi/python/test/test_payjoin_integration_test.py +++ b/payjoin-ffi/python/test/test_payjoin_integration_test.py @@ -7,12 +7,6 @@ from typing import Optional import unittest -try: - import payjoin.bitcoin as bitcoinffi -except ImportError: - bitcoinffi = None - raise unittest.SkipTest("bitcoin_ffi helpers are not available in this binding") - # The below sys path setting is required to use the 'payjoin' module in the 'src' directory # This script is in the 'tests' directory and the 'payjoin' module is in the 'src' directory sys.path.insert( @@ -101,15 +95,13 @@ async def process_receiver_proposal( def create_receiver_context( self, - receiver_address: bitcoinffi.Address, + address: str, directory: str, ohttp_keys: OhttpKeys, recv_persister: InMemoryReceiverSessionEventLog, ) -> Initialized: receiver = ( - ReceiverBuilder( - address=receiver_address, directory=directory, ohttp_keys=ohttp_keys - ) + ReceiverBuilder(address=address, directory=directory, ohttp_keys=ohttp_keys) .build() .save(recv_persister) ) @@ -207,10 +199,7 @@ async def process_provisional_proposal( async def test_integration_v2_to_v2(self): try: - receiver_address = bitcoinffi.Address( - json.loads(self.receiver.call("getnewaddress", [])), - bitcoinffi.Network.REGTEST, - ) + receiver_address = json.loads(self.receiver.call("getnewaddress", [])) init_tracing() services = TestServices.initialize() @@ -282,32 +271,37 @@ async def test_integration_v2_to_v2(self): headers={"Content-Type": request.request.content_type}, content=request.request.body, ) - checked_payjoin_proposal_psbt = ( - send_ctx.process_response(response.content, request.ohttp_ctx) - .save(sender_persister) - .inner - ) - print(f"checked_payjoin_proposal_psbt: {checked_payjoin_proposal_psbt}") - self.assertIsNotNone(checked_payjoin_proposal_psbt) + poll_outcome = send_ctx.process_response( + response.content, request.ohttp_ctx + ).save(sender_persister) + print(f"poll_outcome: {poll_outcome}") + self.assertIsNotNone(poll_outcome) + self.assertTrue(poll_outcome.is_PROGRESS()) payjoin_psbt = json.loads( self.sender.call( "walletprocesspsbt", - [checked_payjoin_proposal_psbt.serialize_base64()], + [poll_outcome.psbt_base64], ) )["psbt"] final_psbt = json.loads( self.sender.call("finalizepsbt", [payjoin_psbt, json.dumps(False)]) )["psbt"] - payjoin_tx = bitcoinffi.Psbt.deserialize_base64(final_psbt).extract_tx() - self.sender.call( - "sendrawtransaction", [json.dumps(payjoin_tx.serialize().hex())] - ) + final_tx_hex = json.loads( + self.sender.call("finalizepsbt", [final_psbt, json.dumps(True)]) + )["hex"] + self.sender.call("sendrawtransaction", [json.dumps(final_tx_hex)]) # Check resulting transaction and balances - network_fees = bitcoinffi.Psbt.deserialize_base64(final_psbt).fee().to_btc() + decoded_psbt = json.loads( + self.sender.call("decodepsbt", [json.dumps(final_psbt)]) + ) + network_fees = float(decoded_psbt["fee"]) + decoded_tx = json.loads( + self.sender.call("decoderawtransaction", [json.dumps(final_tx_hex)]) + ) # Sender sent the entire value of their utxo to receiver (minus fees) - self.assertEqual(len(payjoin_tx.input()), 2) - self.assertEqual(len(payjoin_tx.output()), 1) + self.assertEqual(len(decoded_tx["vin"]), 2) + self.assertEqual(len(decoded_tx["vout"]), 1) self.assertEqual( float( json.loads(self.receiver.call("getbalances", []))["mine"][ @@ -323,7 +317,7 @@ async def test_integration_v2_to_v2(self): raise -def build_sweep_psbt(sender: RpcClient, pj_uri: PjUri) -> bitcoinffi.Psbt: +def build_sweep_psbt(sender: RpcClient, pj_uri: PjUri) -> str: outputs = {} outputs[pj_uri.address()] = 50 psbt = json.loads( @@ -354,25 +348,21 @@ def build_sweep_psbt(sender: RpcClient, pj_uri: PjUri) -> bitcoinffi.Psbt: def get_inputs(rpc_connection: RpcClient) -> list[InputPair]: utxos = json.loads(rpc_connection.call("listunspent", [])) inputs = [] - for utxo in utxos[:1]: - txin = bitcoinffi.TxIn( - previous_output=bitcoinffi.OutPoint(txid=utxo["txid"], vout=utxo["vout"]), - script_sig=bitcoinffi.Script(bytes()), + for utxo in utxos: + txid = utxo["txid"] + vout = utxo["vout"] + script_pubkey = bytes.fromhex(utxo["scriptPubKey"]) + amount_sat = round(utxo["amount"] * 100_000_000) + + txin = PlainTxIn( + previous_output=PlainOutPoint(txid=txid, vout=vout), + script_sig=bytes(), sequence=0, witness=[], ) - raw_tx = json.loads( - rpc_connection.call( - "gettransaction", - [json.dumps(utxo["txid"]), json.dumps(True), json.dumps(True)], - ) - ) - prev_out = raw_tx["decoded"]["vout"][utxo["vout"]] - prev_spk = bitcoinffi.Script(bytes.fromhex(prev_out["scriptPubKey"]["hex"])) - prev_amount = bitcoinffi.Amount.from_btc(prev_out["value"]) - tx_out = bitcoinffi.TxOut(value=prev_amount, script_pubkey=prev_spk) - psbt_in = PsbtInput( - witness_utxo=tx_out, redeem_script=None, witness_script=None + witness_utxo = PlainTxOut(value_sat=amount_sat, script_pubkey=script_pubkey) + psbt_in = PlainPsbtInput( + witness_utxo=witness_utxo, redeem_script=None, witness_script=None ) inputs.append(InputPair(txin=txin, psbtin=psbt_in, expected_weight=None)) @@ -402,15 +392,41 @@ def __init__(self, connection: RpcClient): def callback(self, script): try: - address = bitcoinffi.Address.from_script( - bitcoinffi.Script(script), bitcoinffi.Network.REGTEST + script_hex = bytes(script).hex() + decoded_script = json.loads( + self.connection.call("decodescript", [json.dumps(script_hex)]) ) - return json.loads(self.connection.call("getaddressinfo", [str(address)]))[ - "ismine" - ] + + candidates = [] + if isinstance(decoded_script.get("address"), str): + candidates.append(decoded_script["address"]) + if isinstance(decoded_script.get("addresses"), list): + candidates.extend( + addr + for addr in decoded_script["addresses"] + if isinstance(addr, str) + ) + if isinstance(decoded_script.get("p2sh"), str): + candidates.append(decoded_script["p2sh"]) + segwit = decoded_script.get("segwit") + if isinstance(segwit, dict): + if isinstance(segwit.get("address"), str): + candidates.append(segwit["address"]) + if isinstance(segwit.get("addresses"), list): + candidates.extend( + addr for addr in segwit["addresses"] if isinstance(addr, str) + ) + + for addr in candidates: + info = json.loads( + self.connection.call("getaddressinfo", [json.dumps(addr)]) + ) + if info.get("ismine") is True: + return True + return False except Exception as e: print(f"An error occurred: {e}") - return None + return False class CheckInputsNotSeenCallback(IsOutputKnown):