33import os
44import sys
55import httpx
6+ import logging
67
78from payjoin import *
89from typing import Optional
1516)
1617
1718import hashlib
19+ import subprocess
20+ import shutil
1821import unittest
1922from pprint import *
2023from bitcoin import SelectParams
@@ -34,7 +37,86 @@ def get_rpc_credentials_from_cookie(cookie_path):
3437 credentials = cookie_file .read ().strip ()
3538 return credentials .split (":" )
3639
37- # Function to create and load a wallet if it doesn't already exist
40+ from typing import Optional , Tuple
41+ import time
42+
43+ from pathlib import Path
44+
45+ BITCOIND_DATADIR = Path ("/tmp/bitcoind-test" )
46+ COOKIE_PATH = BITCOIND_DATADIR / "regtest" / ".cookie"
47+ RPC_URL = "http://127.0.0.1:18443"
48+
49+ def init_bitcoind ():
50+ bitcoind_exe = os .getenv ("BITCOIND_EXE" ) or shutil .which ("bitcoind" )
51+ if not bitcoind_exe :
52+ raise RuntimeError ("bitcoind not found" )
53+
54+ args = [
55+ bitcoind_exe ,
56+ "-regtest" ,
57+ f"-datadir={ BITCOIND_DATADIR } " ,
58+ "-server" ,
59+ "-fallbackfee=0.0002" ,
60+ "-rpcbind=127.0.0.1" ,
61+ "-rpcallowip=127.0.0.1" ,
62+ "-printtoconsole" ,
63+ "-txindex"
64+ ]
65+
66+ BITCOIND_DATADIR .mkdir (parents = True , exist_ok = True )
67+
68+ process = subprocess .Popen (args , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
69+
70+ for _ in range (30 ):
71+ if COOKIE_PATH .exists ():
72+ break
73+ time .sleep (1 )
74+ else :
75+ process .terminate ()
76+ raise RuntimeError ("bitcoind did not create cookie file in time" )
77+
78+ return process , BITCOIND_DATADIR
79+
80+
81+ def get_cookie_auth ():
82+ for _ in range (30 ):
83+ if COOKIE_PATH .exists ():
84+ with open (COOKIE_PATH ) as f :
85+ cookie = f .read ().strip ()
86+ return f"http://{ cookie } @127.0.0.1:18443"
87+ time .sleep (1 )
88+ raise RuntimeError ("bitcoind cookie not found after timeout" )
89+
90+ def wait_for_bitcoind (rpc_url , timeout = 30 ):
91+ client = Proxy (rpc_url )
92+ for _ in range (timeout ):
93+ try :
94+ client ._call ("getblockchaininfo" )
95+ return client
96+ except Exception as e :
97+ print (f"Wanting for bitcoind: { e } " )
98+ time .sleep (1 )
99+ raise RuntimeError ("bitcoind RPC not ready after timeout" )
100+
101+ def init_bitcoind_sender_receiver ():
102+ process , datadir = init_bitcoind ()
103+ rpc_url = get_cookie_auth ()
104+
105+ wait_for_bitcoind (rpc_url )
106+
107+ wallet_names = ["receiver" , "sender" ]
108+ clients = []
109+
110+ for wallet_name in wallet_names :
111+ default_client = Proxy (rpc_url )
112+ create_and_load_wallet (default_client , wallet_name )
113+ wallet_client = Proxy (f"{ rpc_url } /wallet/{ wallet_name } " )
114+ clients .append (wallet_client )
115+ wallet_client ._call ("generatetoaddress" , 1 , wallet_client ._call ("getnewaddress" ))
116+
117+ receiver , sender = clients
118+ return process , receiver , sender , datadir
119+
38120def create_and_load_wallet (rpc_connection , wallet_name ):
39121 try :
40122 # Try to load the wallet using the _call method
@@ -44,22 +126,11 @@ def create_and_load_wallet(rpc_connection, wallet_name):
44126 # Check if the error code indicates the wallet does not exist
45127 if e .error ["code" ] == - 18 : # Wallet not found error code
46128 # Create the wallet since it does not exist using the _call method
47- rpc_connection ._call ("createwallet" , wallet_name )
48- print (f"Wallet '{ wallet_name } ' created and loaded successfully." )
129+ rpc_connection ._call ("createwallet" , wallet_name )
130+ print (f"Wallet '{ wallet_name } ' created and loaded successfully." )
49131 elif e .error ["code" ] == - 35 : # Wallet already loaded
50132 print (f"Wallet '{ wallet_name } ' created and loaded successfully." )
51133
52-
53- # Set up RPC connections
54- rpc_user = os .environ .get ("RPC_USER" , "admin1" )
55- rpc_password = os .environ .get ("RPC_PASSWORD" , "123" )
56- rpc_host = os .environ .get ("RPC_HOST" , "localhost" )
57- rpc_port = os .environ .get ("RPC_PORT" , "18443" )
58- #ensure this is where your access cookie is located
59- rpc_data_dir = os .environ .get ("RPC_DATA_DIR" , "~/.bitcoin/regtest" )
60- cookie_path = os .path .expanduser (os .path .join (rpc_data_dir , ".cookie" ))
61- rpc_user , rpc_password = get_rpc_credentials_from_cookie (cookie_path )
62-
63134class InMemoryReceiverPersister (ReceiverPersister ):
64135 def __init__ (self ):
65136 super ().__init__ ()
@@ -94,26 +165,22 @@ def load(self, token: SenderToken) -> Sender:
94165class TestPayjoin (unittest .IsolatedAsyncioTestCase ):
95166 @classmethod
96167 def setUpClass (cls ):
97- # Initialize wallets once before all tests
98- sender_wallet_name = "sender"
99- sender_rpc_url = f"http://{ rpc_user } :{ rpc_password } @{ rpc_host } :{ rpc_port } /wallet/{ sender_wallet_name } "
100- cls .sender = Proxy (service_url = sender_rpc_url )
101- create_and_load_wallet (cls .sender , sender_wallet_name )
102- cls .sender .generatetoaddress (1 , cls .sender .getnewaddress ())
103-
104- receiver_wallet_name = "receiver"
105- receiver_rpc_url = f"http://{ rpc_user } :{ rpc_password } @{ rpc_host } :{ rpc_port } /wallet/{ receiver_wallet_name } "
106- cls .receiver = Proxy (service_url = receiver_rpc_url )
107- create_and_load_wallet (cls .receiver , receiver_wallet_name )
108- cls .receiver .generatetoaddress (1 , cls .receiver .getnewaddress ())
168+ cls .process , cls .receiver , cls .sender , cls .data_dir = init_bitcoind_sender_receiver ()
109169
170+ @classmethod
171+ def tearDownClass (cls ):
172+ if hasattr (cls , "process" ) and cls .process .poll () is None :
173+ cls .process .terminate ()
174+ cls .process .wait ()
175+
176+ if BITCOIND_DATADIR .exists ():
177+ shutil .rmtree (BITCOIND_DATADIR )
178+
110179 async def test_integration_v2_to_v2 (self ):
111180 try :
112181 receiver_address = bitcoinffi .Address (str (self .receiver .getnewaddress ()), bitcoinffi .Network .REGTEST )
113182 init_tracing ()
114183 self .receiver .generatetoaddress (101 , bitcoinffi .Address ("mz3QWUnL94q2RdRxFtPq747SngoBu5uQMi" , bitcoinffi .Network .REGTEST ))
115- pre_payjoin_receiver_balance = self .receiver .getbalance ()
116- pre_payjoin_sender_balance = self .sender .getbalance ()
117184 services = TestServices .initialize ()
118185
119186 services .wait_for_services_ready ()
@@ -145,8 +212,6 @@ async def test_integration_v2_to_v2(self):
145212 # Inside the Sender:
146213 # Create a funded PSBT (not broadcasted) to address with amount given in the pj_uri
147214 pj_uri = session .pj_uri ()
148- outputs = {}
149- outputs [pj_uri .address ()] = 0.0001
150215 psbt = build_sweep_psbt (self .sender , pj_uri )
151216 new_sender = SenderBuilder (psbt , pj_uri ).build_recommended (1000 )
152217 persister = InMemorySenderPersister ()
@@ -201,16 +266,14 @@ async def test_integration_v2_to_v2(self):
201266 final_psbt = self .sender ._call ("finalizepsbt" , payjoin_psbt , False )["psbt" ]
202267 payjoin_tx = bitcoinffi .Psbt .deserialize_base64 (final_psbt ).extract_tx ()
203268 self .sender .sendrawtransaction (payjoin_tx )
204- # print(f"Tx sent: {payjoin_tx.compute_txid()}")
205- self .receiver .generatetoaddress (1 , bitcoinffi .Address ("mz3QWUnL94q2RdRxFtPq747SngoBu5uQMi" , bitcoinffi .Network .REGTEST ))
206269
207270 # Check resulting transaction and balances
208- network_fees = bitcoinffi .Psbt .deserialize_base64 (final_psbt ).fee ().to_sat ()
271+ network_fees = bitcoinffi .Psbt .deserialize_base64 (final_psbt ).fee ().to_btc ()
209272 # Sender sent the entire value of their utxo to receiver (minus fees)
210273 self .assertEqual (len (payjoin_tx .input ()), 2 );
211- self .assertEqual (len (payjoin_tx .output ()), 2 );
212- self .assertEqual (self .receiver .getbalance (), pre_payjoin_receiver_balance + 10000 )
213- self .assertEqual (self .sender .getbalance (), pre_payjoin_sender_balance - ( 10000 + network_fees ) )
274+ self .assertEqual (len (payjoin_tx .output ()), 1 );
275+ self .assertEqual (float ( self .receiver ._call ( "getbalances" )[ "mine" ][ "untrusted_pending" ]), 100 - network_fees )
276+ self .assertEqual (self .sender .getbalance (), 0 )
214277 return
215278 except Exception as e :
216279 print ("Caught:" , e )
@@ -227,13 +290,13 @@ def handle_directory_payjoin_proposal(receiver: Proxy, proposal: UncheckedPropos
227290
228291def build_sweep_psbt (sender : Proxy , pj_uri : PjUri ) -> bitcoinffi .Psbt :
229292 outputs = {}
230- outputs [pj_uri .address ()] = 0.0001
293+ outputs [pj_uri .address ()] = 49.99998318
231294 psbt = sender ._call (
232295 "walletcreatefundedpsbt" ,
233296 [],
234297 outputs ,
235298 0 ,
236- {"lockUnspents" : True , "fee_rate" : 10 , "subtract_fee_from_outputs" : [0 ]},
299+ {"lockUnspents" : True , "fee_rate" : 10 , "subtract_fee_from_outputs" : [0 ]}
237300 )["psbt" ]
238301 return sender ._call ("walletprocesspsbt" , psbt , True , None , False )["psbt" ]
239302
0 commit comments