def test_decode_p2wpkh(self): swap_file = os.path.join(TEST_DATADIRS, 'test') + '.json' swap_info = { 'side': 'a', 'a_coin': 'PART', 'b_coin': 'XMR', 'a_amount': 1, 'b_amount': 2, 'a_feerate': 0.00032595, 'b_feerate': 0.0012595, 'a_addr_f': 'bart1qy68z7jn0mz64cpcgq77uvlp8h0yxmsdpsq6a6j', 'lock1': 10, 'lock2': 11, } swap_info['a_connect'] = { 'port': 1234, 'username': '******'.format(123), 'password': '******'.format(123) } swap_info['b_connect'] = { 'port': 1235, 'wallet_port': 123, 'wallet_auth': '123', } try: callSwapTool(swap_file, 'init', swap_info) assert (False), 'Should fail' except Exception as e: assert ('Unknown bech32 hrp' in str(e))
def startSwap(self, ID_ALICE_SWAP, ID_BOB_SWAP, amount_a, amount_b): logging.info('Set initial parameters.') part_addr_bob = callnoderpc( ID_BOB_PART, 'getnewaddress', ['bob\'s addr', False, False, False, 'bech32']) # After a successful swap the coinA amount will be in an output to part_addr_bob swap_info = { 'side': 'a', 'a_coin': 'PART', 'b_coin': 'XMR', 'a_amount': amount_a, 'b_amount': amount_b, 'a_feerate': 0.00032595, 'b_feerate': 0.0012595, 'a_addr_f': part_addr_bob, 'lock1': 10, 'lock2': 11, } swap_info['a_connect'] = { 'port': BASE_RPC_PORT + ID_ALICE_PART, 'username': '******'.format(ID_ALICE_PART), 'password': '******'.format(ID_ALICE_PART) } swap_info['b_connect'] = { 'port': XMR_BASE_RPC_PORT + ID_ALICE_XMR, 'wallet_port': XMR_BASE_WALLET_RPC_PORT + ID_ALICE_XMR, 'wallet_auth': self.xmr_wallet_auth[ID_ALICE_XMR], } callSwapTool(ID_ALICE_SWAP, 'init', swap_info) swap_info['a_connect'] = { 'port': BASE_RPC_PORT + ID_BOB_PART, 'username': '******'.format(ID_BOB_PART), 'password': '******'.format(ID_BOB_PART) } swap_info['b_connect'] = { 'port': XMR_BASE_RPC_PORT + ID_BOB_XMR, 'wallet_port': XMR_BASE_WALLET_RPC_PORT + ID_BOB_XMR, 'wallet_auth': self.xmr_wallet_auth[ID_BOB_XMR], } swap_info['side'] = 'b' callSwapTool(ID_BOB_SWAP, 'init', swap_info) logging.info('Alice and Bob exchange keys.') msg1f = callSwapTool(ID_ALICE_SWAP, 'msg1f') msg1l = callSwapTool(ID_BOB_SWAP, 'msg1l') callSwapTool(ID_ALICE_SWAP, 'processmsg', str_param=msg1l) callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg1f)
def startSwap(self, ID_ALICE_SWAP, ID_BOB_SWAP, amount_a, amount_b): logging.info('Set initial parameters.') btc_addr_bob1 = callnoderpc(ID_BOB_BTC, 'getnewaddress', ['bob\'s addr', 'bech32']) ignr, a_pkhash_f = segwit_addr.decode('bcrt', btc_addr_bob1) # After a successful swap the coinA amount will be in an output to a_pkhash_f swap_info = { 'side': 'a', 'a_coin': 'BTC', 'b_coin': 'XMR', 'a_amount': amount_a, 'b_amount': amount_b, 'a_feerate': 0.00032595, 'b_feerate': 0.0012595, 'a_pkhash_f': bytes(a_pkhash_f).hex(), } swap_info['a_connect'] = { 'port': BASE_RPC_PORT + ID_ALICE_BTC, 'username': '******'.format(ID_ALICE_BTC), 'password': '******'.format(ID_ALICE_BTC) } swap_info['b_connect'] = { 'port': XMR_BASE_RPC_PORT + ID_ALICE_XMR, 'wallet_port': XMR_BASE_WALLET_RPC_PORT + ID_ALICE_XMR, 'wallet_auth': self.xmr_wallet_auth[ID_ALICE_XMR], } callSwapTool(ID_ALICE_SWAP, 'init', swap_info) swap_info['a_connect'] = { 'port': BASE_RPC_PORT + ID_BOB_BTC, 'username': '******'.format(ID_BOB_BTC), 'password': '******'.format(ID_BOB_BTC) } swap_info['b_connect'] = { 'port': XMR_BASE_RPC_PORT + ID_BOB_XMR, 'wallet_port': XMR_BASE_WALLET_RPC_PORT + ID_BOB_XMR, 'wallet_auth': self.xmr_wallet_auth[ID_BOB_XMR], } swap_info['side'] = 'b' callSwapTool(ID_BOB_SWAP, 'init', swap_info) logging.info('Alice and Bob exchange keys.') msg1f = callSwapTool(ID_ALICE_SWAP, 'msg1f') msg1l = callSwapTool(ID_BOB_SWAP, 'msg1l') callSwapTool(ID_ALICE_SWAP, 'processmsg', str_param=msg1l) callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg1f)
def publishALockRefundTx(self, user_id_part, user_id_swap): alockrefundtxid = None for i in range(20): ''' self.part_stakelimit += 10 callnoderpc(0, 'walletsettings', ['stakelimit', {'height': self.part_stakelimit}]) print('self.part_stakelimit', self.part_stakelimit) ''' time.sleep(1) logging.info( 'PART blocks: %d', callnoderpc(user_id_part, 'getblockchaininfo')['blocks']) try: alockrefundtxid = callSwapTool(user_id_swap, 'publishalockrefundtx').strip() break except Exception as e: print(str(e)) if 'Transaction already in block chain' in str(e): break assert ('non-BIP68-final' in str(e)) assert (alockrefundtxid is not None) logging.info('alockrefundtxid %s', alockrefundtxid) return alockrefundtxid
def publishALockRefundFollowerSpendTx(self, user_id_part, user_id_swap, a_pkhash_f): alockrefundspendtxid = None for i in range(20): time.sleep(1) logging.info( 'PART blocks: %d', callnoderpc(user_id_part, 'getblockchaininfo')['blocks']) try: alockrefundspendtxid = callSwapTool( user_id_swap, 'publishalockrefundspendftx', str_param=bytes(a_pkhash_f).hex()).strip() break except Exception as e: print(str(e)) if 'Transaction already in block chain' in str(e): break assert ('non-BIP68-final' in str(e)) assert (alockrefundspendtxid is not None) logging.info('alockrefundspendtxid %s', alockrefundspendtxid) return alockrefundspendtxid
def test_04_follower_recover_b_lock_tx(self): ID_ALICE_SWAP = os.path.join(TEST_DIR, 'test_04_alice_swap_state') + '.json' ID_BOB_SWAP = os.path.join(TEST_DIR, 'test_04_bob_swap_state') + '.json' alice_btc_start = make_int( callnoderpc(ID_ALICE_PART, 'getbalances')['mine']['trusted']) bob_btc_start = make_int( callnoderpc(ID_BOB_PART, 'getbalances')['mine']['trusted']) alice_xmr_start = self.callxmrnodewallet(ID_ALICE_XMR, 'get_balance')['balance'] bob_xmr_start = self.callxmrnodewallet(ID_BOB_XMR, 'get_balance')['balance'] logging.info( 'Test start wallet states:\nalice_btc_start: %ld\nbob_btc_start: %ld\nalice_xmr_start: %ld\nbob_xmr_start: %ld', alice_btc_start, bob_btc_start, alice_xmr_start, bob_xmr_start) # Same steps as in test_01_swap_successful self.startSwap(ID_ALICE_SWAP, ID_BOB_SWAP, 3, 4) msg2f = callSwapTool(ID_ALICE_SWAP, 'msg2f') callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg2f) msg3l = callSwapTool(ID_BOB_SWAP, 'msg3l') callSwapTool(ID_ALICE_SWAP, 'processmsg', str_param=msg3l) msg4f = callSwapTool(ID_ALICE_SWAP, 'msg4f') a_lock_txid = callSwapTool(ID_ALICE_SWAP, 'publishalocktx').strip() logging.info( 'Bob verifies the lock spend tx and encrypted signature from Alice.' ) callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg4f) logging.info('Bob waits for the script-chain lock tx to confirm.') num_tries = 30 for i in range(1 + num_tries): rv = callSwapTool(ID_BOB_SWAP, 'confirmalocktx') print('confirmalocktx', rv) if rv.strip() == 'True': break if i >= num_tries: raise ValueError( 'Timed out waiting for script-chain lock tx to confirm.') logging.info('Then publishes the second-chain lock tx.') b_lock_txid = callSwapTool(ID_BOB_SWAP, 'publishblocktx') logging.info( 'Alice waits for the scriptless-chain lock tx to confirm.') num_tries = 120 for i in range(1 + num_tries): rv = callSwapTool(ID_ALICE_SWAP, 'confirmblocktx') print('confirmblocktx', rv) if rv.strip() == 'True': break if i >= num_tries: raise ValueError( 'Timed out waiting for scriptless-chain lock tx to confirm.' ) time.sleep(2) logging.info( 'Alice detects a problem with the scriptless-chain lock tx and decides to cancel the swap' ) a_lock_refund_txid = self.publishALockRefundTx(ID_ALICE_PART, ID_ALICE_SWAP) # Import key to receive refund in wallet. Simple method for testing. kal = callSwapTool(ID_ALICE_SWAP, 'getkal') kal_wif = bytes_to_wif(h2b(kal), prefix=0x2e) callnoderpc(ID_ALICE_PART, 'importprivkey', [kal_wif, 'swap refund']) alockrefundspendtxid = callSwapTool(ID_ALICE_SWAP, 'publishalockrefundspendtx') rv = callnoderpc(ID_ALICE_PART, 'getbalances') print('getbalances', dumpj(rv)) alice_btc = make_int(rv['mine']['trusted']) + make_int( rv['mine']['untrusted_pending']) logging.info('alice_btc %ld', alice_btc) logging.info('Bob waits for Alice to spend the lock refund tx.') num_tries = 20 for i in range(1 + num_tries): rv = callSwapTool(ID_BOB_SWAP, 'findalockrefundspendtx') print('findalockrefundspendtx', rv) if rv.strip() == 'True': break if i >= num_tries: raise ValueError( 'Timed out waiting for script-chain lock refund spend tx to confirm.' ) time.sleep(1) logging.info('Then he can recover his scriptless-chain lock tx coin.') self.callxmrnodewallet(ID_BOB_XMR, 'open_wallet', {'filename': 'testwallet'}) xmr_addr_bob = self.callxmrnodewallet(ID_BOB_XMR, 'get_address')['address'] rv = callSwapTool(ID_BOB_SWAP, 'redeemblocktx', str_param=xmr_addr_bob) print('redeemblocktx', rv) self.callxmrnodewallet(ID_BOB_XMR, 'close_wallet') self.callxmrnodewallet(ID_BOB_XMR, 'open_wallet', {'filename': 'testwallet'})
def test_03_follower_recover_a_lock_tx(self): ID_ALICE_SWAP = os.path.join(TEST_DIR, 'test_03_alice_swap_state') + '.json' ID_BOB_SWAP = os.path.join(TEST_DIR, 'test_03_bob_swap_state') + '.json' alice_btc_start = make_int( callnoderpc(ID_ALICE_PART, 'getbalances')['mine']['trusted']) bob_btc_start = make_int( callnoderpc(ID_BOB_PART, 'getbalances')['mine']['trusted']) alice_xmr_start = self.callxmrnodewallet(ID_ALICE_XMR, 'get_balance')['balance'] bob_xmr_start = self.callxmrnodewallet(ID_BOB_XMR, 'get_balance')['balance'] logging.info( 'Test start wallet states:\nalice_btc_start: %ld\nbob_btc_start: %ld\nalice_xmr_start: %ld\nbob_xmr_start: %ld', alice_btc_start, bob_btc_start, alice_xmr_start, bob_xmr_start) # Same steps as in test_01_swap_successful self.startSwap(ID_ALICE_SWAP, ID_BOB_SWAP, 3, 4) msg2f = callSwapTool(ID_ALICE_SWAP, 'msg2f') callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg2f) msg3l = callSwapTool(ID_BOB_SWAP, 'msg3l') callSwapTool(ID_ALICE_SWAP, 'processmsg', str_param=msg3l) msg4f = callSwapTool(ID_ALICE_SWAP, 'msg4f') a_lock_txid = callSwapTool(ID_ALICE_SWAP, 'publishalocktx').strip() logging.info('Alice stops responding here.') # Wait for the mining node to receive the tx for i in range(10): try: callnoderpc(0, 'getrawtransaction', [a_lock_txid]) break except Exception as e: print('Waiting for node 0 to see tx', str(e)) time.sleep(1) a_lock_refund_txid = self.publishALockRefundTx(ID_BOB_PART, ID_BOB_SWAP) # Wait for the mining node to receive the tx for i in range(10): try: callnoderpc(0, 'getrawtransaction', [a_lock_refund_txid]) break except Exception as e: print('Waiting for node 0 to see tx', str(e)) time.sleep(1) btc_addr_bob = callnoderpc( ID_BOB_PART, 'getnewaddress', ['bob\'s addr', False, False, False, 'bech32']) ignr, a_pkhash_f = segwit_addr.decode('rtpw', btc_addr_bob) assert (a_pkhash_f is not None) a_lock_refund_spend_txid = self.publishALockRefundFollowerSpendTx( ID_BOB_PART, ID_BOB_SWAP, a_pkhash_f) rv = callnoderpc(ID_BOB_PART, 'getbalances') print('getbalances', dumpj(rv)) bob_btc_end = make_int(rv['mine']['trusted']) + make_int( rv['mine']['untrusted_pending']) logging.info('bob_btc_end %ld', bob_btc_end) assert (bob_btc_end > bob_btc_start)
def test_02_leader_recover_a_lock_tx(self): ID_ALICE_SWAP = os.path.join(TEST_DIR, 'test_02_alice_swap_state') + '.json' ID_BOB_SWAP = os.path.join(TEST_DIR, 'test_02_bob_swap_state') + '.json' alice_btc_start = make_int( callnoderpc(ID_ALICE_PART, 'getbalances')['mine']['trusted']) bob_btc_start = make_int( callnoderpc(ID_BOB_PART, 'getbalances')['mine']['trusted']) alice_xmr_start = self.callxmrnodewallet(ID_ALICE_XMR, 'get_balance')['balance'] bob_xmr_start = self.callxmrnodewallet(ID_BOB_XMR, 'get_balance')['balance'] logging.info( 'Test start wallet states:\nalice_btc_start: %ld\nbob_btc_start: %ld\nalice_xmr_start: %ld\nbob_xmr_start: %ld', alice_btc_start, bob_btc_start, alice_xmr_start, bob_xmr_start) self.startSwap(ID_ALICE_SWAP, ID_BOB_SWAP, 2, 3) logging.info( 'Alice creates the script-chain lock and refund txns and signs the refund tx, sends to Bob.' ) msg2f = callSwapTool(ID_ALICE_SWAP, 'msg2f') logging.info( 'Bob verifies the txns and signs the refund tx and creates an encrypted signature for the refund spend tx encumbered by Alice\'s coin B key share.' ) callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg2f) msg3l = callSwapTool(ID_BOB_SWAP, 'msg3l') logging.info( 'Alice verifies the signature and encrypted signature from Bob.') callSwapTool(ID_ALICE_SWAP, 'processmsg', str_param=msg3l) logging.info( 'Creates the lock spend tx and signs an encrypted signature encumbered by Bob\'s coin B key share' ) msg4f = callSwapTool(ID_ALICE_SWAP, 'msg4f') logging.info('Publishes the script-chain lock tx.') a_lock_txid = callSwapTool(ID_ALICE_SWAP, 'publishalocktx').strip() # Wait for the mining node to receive the tx for i in range(10): try: callnoderpc(0, 'getrawtransaction', [a_lock_txid]) break except Exception as e: print('Waiting for node 0 to see tx', str(e)) time.sleep(1) logging.info('Bob stops responding here.') alice_btc = make_int( callnoderpc(ID_ALICE_PART, 'getbalances')['mine']['trusted']) logging.info('alice_btc %ld', alice_btc) a_lock_refund_txid = self.publishALockRefundTx(ID_ALICE_PART, ID_ALICE_SWAP) # Import key to receive refund in wallet. Simple method for testing. kal = callSwapTool(ID_ALICE_SWAP, 'getkal') kal_wif = bytes_to_wif(h2b(kal), prefix=0x2e) callnoderpc(ID_ALICE_PART, 'importprivkey', [kal_wif, 'swap refund']) alockrefundspendtxid = callSwapTool(ID_ALICE_SWAP, 'publishalockrefundspendtx') rv = callnoderpc(ID_ALICE_PART, 'getbalances') alice_btc_end = make_int(rv['mine']['trusted']) + make_int( rv['mine']['untrusted_pending']) logging.info('alice_btc_end %ld', alice_btc_end) assert (alice_btc_end > alice_btc)
def test_01_swap_successful(self): ID_ALICE_SWAP = os.path.join(TEST_DIR, 'test_01_alice_swap_state') + '.json' ID_BOB_SWAP = os.path.join(TEST_DIR, 'test_01_bob_swap_state') + '.json' self.startSwap(ID_ALICE_SWAP, ID_BOB_SWAP, 1, 2) logging.info( 'Alice creates the script-chain lock and refund txns and signs the refund tx, sends to Bob.' ) msg2f = callSwapTool(ID_ALICE_SWAP, 'msg2f') logging.info( 'Bob verifies the txns and signs the refund tx and creates an encrypted signature for the refund spend tx encumbered by Alice\'s coin B key share.' ) callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg2f) msg3l = callSwapTool(ID_BOB_SWAP, 'msg3l') logging.info( 'Alice verifies the signature and encrypted signature from Bob.') callSwapTool(ID_ALICE_SWAP, 'processmsg', str_param=msg3l) logging.info( 'Creates the lock spend tx and signs an encrypted signature encumbered by Bob\'s coin B key share' ) msg4f = callSwapTool(ID_ALICE_SWAP, 'msg4f') logging.info('Publishes the script-chain lock tx.') a_lock_txid = callSwapTool(ID_ALICE_SWAP, 'publishalocktx') # Check that the script-chain lock refund tx isn't mineable yet try: rv = callSwapTool(ID_ALICE_SWAP, 'publishalockrefundtx') assert (False) except Exception as e: assert ('non-BIP68-final' in str(e)) logging.info( 'Bob verifies the lock spend tx and encrypted signature from Alice.' ) callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg4f) logging.info('Bob waits for the script-chain lock tx to confirm.') num_tries = 30 for i in range(1 + num_tries): rv = callSwapTool(ID_BOB_SWAP, 'confirmalocktx') print('confirmalocktx', rv) if rv.strip() == 'True': break if i >= num_tries: raise ValueError( 'Timed out waiting for script-chain lock tx to confirm.') logging.info('Then publishes the second-chain lock tx.') b_lock_txid = callSwapTool(ID_BOB_SWAP, 'publishblocktx') logging.info( 'Alice waits for the scriptless-chain lock tx to confirm.') num_tries = 120 for i in range(1 + num_tries): rv = callSwapTool(ID_ALICE_SWAP, 'confirmblocktx') print('confirmblocktx', rv) if rv.strip() == 'True': break if i >= num_tries: raise ValueError( 'Timed out waiting for scriptless-chain lock tx to confirm.' ) time.sleep(2) logging.info( 'Alice shares the secret value with Bob, allowing the script-chain lock tx to be spent' ) msg5f = callSwapTool(ID_ALICE_SWAP, 'msg5f') callSwapTool(ID_BOB_SWAP, 'processmsg', str_param=msg5f) logging.info('Bob spends from the script-chain lock tx') alockspendtxid = callSwapTool(ID_BOB_SWAP, 'publishalockspendtx') logging.info('alockspendtxid %s', alockspendtxid) logging.info( 'Alice looks for Bob\'s script-chain lock spend tx and extracts the sig' ) num_tries = 20 for i in range(1 + num_tries): rv = callSwapTool(ID_ALICE_SWAP, 'findalockspendtx') print('findalockspendtx', rv) if rv.strip() == 'True': break if i >= num_tries: raise ValueError( 'Timed out waiting for script-chain lock spend tx to confirm.' ) time.sleep(1) self.callxmrnodewallet(ID_ALICE_XMR, 'open_wallet', {'filename': 'testwallet'}) xmr_addr_alice1 = self.callxmrnodewallet(ID_ALICE_XMR, 'get_address')['address'] logging.info( 'Alice redeems the scriptless-chain lock tx to her address: %s', xmr_addr_alice1) rv = callSwapTool(ID_ALICE_SWAP, 'redeemblocktx', str_param=xmr_addr_alice1) print('redeemblocktx', rv) self.callxmrnodewallet(ID_ALICE_XMR, 'close_wallet') self.callxmrnodewallet(ID_ALICE_XMR, 'open_wallet', {'filename': 'testwallet'}) logging.info('Waiting for Alice\'s XMR to confirm...') num_tries = 120 for i in range(num_tries + 1): rv = self.callxmrnodewallet(ID_ALICE_XMR, 'get_balance') if rv['balance'] > 0 and rv['blocks_to_unlock'] == 0: break r = callrpc_xmr_na(XMR_BASE_RPC_PORT + ID_ALICE_XMR, 'get_block_count') print('XMR blocks', r['count']) if i >= num_tries: raise ValueError( 'Balance not confirming on node {}'.format(ID_ALICE_XMR)) time.sleep(2) logging.info('Waiting for Bob\'s BTC to confirm...') for i in range(num_tries + 1): rv = callnoderpc(ID_BOB_PART, 'getbalances') if rv['mine']['trusted'] > 0: break print('btc height', i, callnoderpc(ID_BOB_PART, 'getblockchaininfo')['blocks']) if i >= num_tries: raise ValueError( 'Balance not confirming on node {}'.format(ID_ALICE_XMR)) time.sleep(1)