From 446d6fe35710ab98858a6b9e8e868c0590a67043 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Mon, 20 May 2024 16:29:14 +0200 Subject: [PATCH] Decred: Add to test_xmr_persistent --- basicswap/__init__.py | 2 +- basicswap/basicswap.py | 42 ++++--- basicswap/interface/base.py | 9 +- basicswap/interface/dcr/dcr.py | 41 +++--- basicswap/interface/dcr/messages.py | 2 +- basicswap/interface/dcr/script.py | 2 +- basicswap/interface/dcr/util.py | 50 ++++++++ basicswap/interface/firo.py | 3 - basicswap/interface/nav.py | 3 - basicswap/interface/pivx.py | 3 - basicswap/interface/xmr.py | 3 - basicswap/templates/wallet.html | 100 +++++++-------- basicswap/util/extkey.py | 6 +- bin/basicswap_prepare.py | 118 +++++++++++++----- bin/basicswap_run.py | 28 ++++- tests/basicswap/common.py | 6 +- tests/basicswap/common_xmr.py | 54 ++++++-- tests/basicswap/extended/test_dcr.py | 48 ++----- .../basicswap/extended/test_xmr_persistent.py | 62 ++++++++- 19 files changed, 394 insertions(+), 188 deletions(-) create mode 100644 basicswap/interface/dcr/util.py diff --git a/basicswap/__init__.py b/basicswap/__init__.py index f48cb4b..f54d37e 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.13.0" +__version__ = "0.13.1" diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 04f4920..c566c45 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -774,9 +774,7 @@ class BasicSwap(BaseApp): if self.coin_clients[c]['connection_type'] == 'rpc': ci = self.ci(c) - self.waitForDaemonRPC(c, with_wallet=False) - if c not in (Coins.XMR,) and ci.checkWallets() >= 1: - self.waitForDaemonRPC(c) + self.waitForDaemonRPC(c) core_version = ci.getDaemonVersion() self.log.info('%s Core version %d', ci.coin_name(), core_version) @@ -857,7 +855,7 @@ class BasicSwap(BaseApp): self.log.info('Scanned %d unread messages.', nm) def stopDaemon(self, coin) -> None: - if coin == Coins.XMR: + if coin in (Coins.XMR, Coins.DCR): return num_tries = 10 authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') @@ -893,6 +891,17 @@ class BasicSwap(BaseApp): self.stopDaemon(c) def waitForDaemonRPC(self, coin_type, with_wallet: bool = True) -> None: + + if with_wallet: + self.waitForDaemonRPC(coin_type, with_wallet=False) + if coin_type in (Coins.XMR,): + return + ci = self.ci(coin_type) + # checkWallets can adjust the wallet name. + if ci.checkWallets() < 1: + self.log.error('No wallets found for coin {}.'.format(ci.coin_name())) + self.stopRunning(1) # systemd will try to restart the process if fail_code != 0 + startup_tries = self.startup_tries chain_client_settings = self.getChainClientSettings(coin_type) if 'startup_tries' in chain_client_settings: @@ -1938,9 +1947,6 @@ class BasicSwap(BaseApp): self.log.debug('Generated new receive address %s for %s', new_addr, Coins(coin_type).name) return new_addr - def getRelayFeeRateForCoin(self, coin_type): - return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee'] - def getFeeRateForCoin(self, coin_type, conf_target: int = 2): return self.ci(coin_type).get_fee_rate(conf_target) @@ -3376,11 +3382,11 @@ class BasicSwap(BaseApp): txn_script] redeem_txn = ci.setTxSignature(bytes.fromhex(redeem_txn), witness_stack).hex() else: - script = (len(redeem_sig) // 2).to_bytes(1) + bytes.fromhex(redeem_sig) - script += (33).to_bytes(1) + pubkey - script += (32).to_bytes(1) + secret - script += (OpCodes.OP_1).to_bytes(1) - script += (OpCodes.OP_PUSHDATA1).to_bytes(1) + (len(txn_script)).to_bytes(1) + txn_script + script = (len(redeem_sig) // 2).to_bytes(1, 'big') + bytes.fromhex(redeem_sig) + script += (33).to_bytes(1, 'big') + pubkey + script += (32).to_bytes(1, 'big') + secret + script += (OpCodes.OP_1).to_bytes(1, 'big') + script += (OpCodes.OP_PUSHDATA1).to_bytes(1, 'big') + (len(txn_script)).to_bytes(1, 'big') + txn_script redeem_txn = ci.setTxScriptSig(bytes.fromhex(redeem_txn), 0, script).hex() if coin_type in (Coins.NAV, Coins.DCR): @@ -3488,10 +3494,10 @@ class BasicSwap(BaseApp): txn_script] refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex() else: - script = (len(refund_sig) // 2).to_bytes(1) + bytes.fromhex(refund_sig) - script += (33).to_bytes(1) + pubkey - script += (OpCodes.OP_0).to_bytes(1) - script += (OpCodes.OP_PUSHDATA1).to_bytes(1) + (len(txn_script)).to_bytes(1) + txn_script + script = (len(refund_sig) // 2).to_bytes(1, 'big') + bytes.fromhex(refund_sig) + script += (33).to_bytes(1, 'big') + pubkey + script += (OpCodes.OP_0).to_bytes(1, 'big') + script += (OpCodes.OP_PUSHDATA1).to_bytes(1, 'big') + (len(txn_script)).to_bytes(1, 'big') + txn_script refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, script) if coin_type in (Coins.NAV, Coins.DCR): @@ -4517,8 +4523,8 @@ class BasicSwap(BaseApp): except Exception as e: if 'Block not available (pruned data)' in str(e): # TODO: Better solution? - bci = self.callcoinrpc(coin_type, 'getblockchaininfo') - self.log.error('Coin %s last_height_checked %d set to pruneheight %d', self.ci(coin_type).coin_name(), last_height_checked, bci['pruneheight']) + bci = ci.getBlockchainInfo() + self.log.error('Coin %s last_height_checked %d set to pruneheight %d', ci.coin_name(), last_height_checked, bci['pruneheight']) last_height_checked = bci['pruneheight'] continue else: diff --git a/basicswap/interface/base.py b/basicswap/interface/base.py index 7a1ce0c..6b250d1 100644 --- a/basicswap/interface/base.py +++ b/basicswap/interface/base.py @@ -153,16 +153,19 @@ class CoinInterface: def use_tx_vsize(self) -> bool: return self._use_segwit - def getLockTxSwapOutputValue(self, bid, xmr_swap): + def getLockTxSwapOutputValue(self, bid, xmr_swap) -> int: return bid.amount - def getLockRefundTxSwapOutputValue(self, bid, xmr_swap): + def getLockRefundTxSwapOutputValue(self, bid, xmr_swap) -> int: return xmr_swap.a_swap_refund_value - def getLockRefundTxSwapOutput(self, xmr_swap): + def getLockRefundTxSwapOutput(self, xmr_swap) -> int: # Only one prevout exists return 0 + def checkWallets(self) -> int: + return 1 + class AdaptorSigInterface(): def getScriptLockTxDummyWitness(self, script: bytes): diff --git a/basicswap/interface/dcr/dcr.py b/basicswap/interface/dcr/dcr.py index a27efaf..c43f066 100644 --- a/basicswap/interface/dcr/dcr.py +++ b/basicswap/interface/dcr/dcr.py @@ -109,7 +109,7 @@ def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransactio for txi_n, txi in enumerate(sign_vins): hash_buffer += txi.prevout.hash.to_bytes(32, 'little') hash_buffer += txi.prevout.n.to_bytes(4, 'little') - hash_buffer += txi.prevout.tree.to_bytes(1) + hash_buffer += txi.prevout.tree.to_bytes(1, 'little') # In the case of SigHashNone and SigHashSingle, commit to 0 for everything that is not the input being signed instead. if (masked_hash_type == SigHashType.SigHashNone @@ -308,14 +308,19 @@ class DCRInterface(Secp256k1Interface): def getChainHeight(self) -> int: return self.rpc('getblockcount') - def checkWallets(self) -> int: - # Only one wallet possible? - return 1 - def initialiseWallet(self, key: bytes) -> None: # Load with --create pass + def getWalletSeedID(self): + masterpubkey = self.rpc_wallet('getmasterpubkey') + masterpubkey_data = self.decode_address(masterpubkey)[4:] + return hash160(masterpubkey_data).hex() + + def checkExpectedSeed(self, expect_seedid) -> bool: + self._expect_seedid_hex = expect_seedid + return expect_seedid == self.getWalletSeedID() + def getDaemonVersion(self): return self.rpc('getnetworkinfo')['version'] @@ -368,7 +373,7 @@ class DCRInterface(Secp256k1Interface): def encodeKey(self, key_bytes: bytes) -> str: wif_prefix = self.chainparams_network()['key_prefix'] key_type = 0 # STEcdsaSecp256k1 - b = wif_prefix.to_bytes(2, 'big') + key_type.to_bytes(1) + key_bytes + b = wif_prefix.to_bytes(2, 'big') + key_type.to_bytes(1, 'big') + key_bytes b += blake256(b)[:4] return b58encode(b) @@ -433,7 +438,7 @@ class DCRInterface(Secp256k1Interface): script_hash = self.pkh(script) assert len(script_hash) == 20 - return OP_HASH160.to_bytes(1) + len(script_hash).to_bytes(1) + script_hash + OP_EQUAL.to_bytes(1) + return bytes((OP_HASH160,)) + bytes((len(script_hash),)) + script_hash + bytes((OP_EQUAL,)) def encodeScriptDest(self, script_dest: bytes) -> str: script_hash = script_dest[2:-1] # Extract hash from script @@ -442,7 +447,7 @@ class DCRInterface(Secp256k1Interface): def getPubkeyHashDest(self, pkh: bytes) -> bytes: # P2PKH assert len(pkh) == 20 - return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1) + return bytes((OP_DUP,)) + bytes((OP_HASH160,)) + bytes((len(pkh),)) + pkh + bytes((OP_EQUALVERIFY,)) + bytes((OP_CHECKSIG,)) def getPkDest(self, K: bytes) -> bytearray: return self.getPubkeyHashDest(self.pkh(K)) @@ -532,7 +537,7 @@ class DCRInterface(Secp256k1Interface): prove_utxos.append(outpoint) hasher.update(outpoint[0]) hasher.update(outpoint[1].to_bytes(2, 'big')) - hasher.update(outpoint[2].to_bytes(1)) + hasher.update(outpoint[2].to_bytes(1, 'big')) if sum_value >= amount_for: break utxos_hash = hasher.digest() @@ -554,7 +559,7 @@ class DCRInterface(Secp256k1Interface): def encodeProofUtxos(self, proof_utxos): packed_utxos = bytes() for utxo in proof_utxos: - packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big') + utxo[2].to_bytes(1) + packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big') + utxo[2].to_bytes(1, 'big') return packed_utxos def decodeProofUtxos(self, msg_utxos): @@ -573,7 +578,7 @@ class DCRInterface(Secp256k1Interface): for outpoint in utxos: hasher.update(outpoint[0]) hasher.update(outpoint[1].to_bytes(2, 'big')) - hasher.update(outpoint[2].to_bytes(1)) + hasher.update(outpoint[2].to_bytes(1, 'big')) utxos_hash = hasher.digest() passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) @@ -841,19 +846,19 @@ class DCRInterface(Secp256k1Interface): Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf) script = bytearray() - script += OP_IF.to_bytes(1) + script += bytes((OP_IF,)) push_script_data(script, bytes((2,))) push_script_data(script, Kal_enc) push_script_data(script, Kaf_enc) push_script_data(script, bytes((2,))) - script += OP_CHECKMULTISIG.to_bytes(1) - script += OP_ELSE.to_bytes(1) + script += bytes((OP_CHECKMULTISIG,)) + script += bytes((OP_ELSE,)) script += CScriptNum.encode(CScriptNum(csv_val)) - script += OP_CHECKSEQUENCEVERIFY.to_bytes(1) - script += OP_DROP.to_bytes(1) + script += bytes((OP_CHECKSEQUENCEVERIFY,)) + script += bytes((OP_DROP,)) push_script_data(script, Kaf_enc) - script += OP_CHECKSIG.to_bytes(1) - script += OP_ENDIF.to_bytes(1) + script += bytes((OP_CHECKSIG,)) + script += bytes((OP_ENDIF,)) return script diff --git a/basicswap/interface/dcr/messages.py b/basicswap/interface/dcr/messages.py index 2c37aa0..00e7f67 100644 --- a/basicswap/interface/dcr/messages.py +++ b/basicswap/interface/dcr/messages.py @@ -162,7 +162,7 @@ class CTransaction: for txi in self.vin: data += txi.prevout.hash.to_bytes(32, 'little') data += txi.prevout.n.to_bytes(4, 'little') - data += txi.prevout.tree.to_bytes(1) + data += txi.prevout.tree.to_bytes(1, 'little') data += txi.sequence.to_bytes(4, 'little') data += encode_compactsize(len(self.vout)) diff --git a/basicswap/interface/dcr/script.py b/basicswap/interface/dcr/script.py index a40edb2..3c0762f 100644 --- a/basicswap/interface/dcr/script.py +++ b/basicswap/interface/dcr/script.py @@ -39,7 +39,7 @@ def push_script_data(data_array: bytearray, data: bytes) -> None: return if len_data < OP_PUSHDATA1: - data_array += len_data.to_bytes(1) + data_array += len_data.to_bytes(1, 'little') elif len_data <= 0xff: data_array += bytes((OP_PUSHDATA1, len_data)) elif len_data <= 0xffff: diff --git a/basicswap/interface/dcr/util.py b/basicswap/interface/dcr/util.py new file mode 100644 index 0000000..6d5dfbf --- /dev/null +++ b/basicswap/interface/dcr/util.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import os +import select +import subprocess + + +def createDCRWallet(args, hex_seed, logging, delay_event): + logging.info('Creating DCR wallet') + (pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read + p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) + + try: + while p.poll() is None: + while len(select.select([pipe_r], [], [], 0)[0]) == 1: + buf = os.read(pipe_r, 1024).decode('utf-8') + logging.debug(f'dcrwallet {buf}') + response = None + if 'Use the existing configured private passphrase' in buf: + response = b'y\n' + elif 'Do you want to add an additional layer of encryption' in buf: + response = b'n\n' + elif 'Do you have an existing wallet seed' in buf: + response = b'y\n' + elif 'Enter existing wallet seed' in buf: + response = (hex_seed + '\n').encode('utf-8') + elif 'Seed input successful' in buf: + pass + elif 'Upgrading database from version' in buf: + pass + elif 'Ticket commitments db upgrade done' in buf: + pass + else: + raise ValueError(f'Unexpected output: {buf}') + if response is not None: + p.stdin.write(response) + p.stdin.flush() + delay_event.wait(0.1) + except Exception as e: + logging.error(f'dcrwallet --create failed: {e}') + finally: + if p.poll() is None: + p.terminate() + os.close(pipe_r) + os.close(pipe_w) + p.stdin.close() diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index d7f7701..8943cef 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -42,9 +42,6 @@ class FIROInterface(BTCInterface): # No multiwallet support self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) - def checkWallets(self) -> int: - return 1 - def getExchangeName(self, exchange_name): return 'zcoin' diff --git a/basicswap/interface/nav.py b/basicswap/interface/nav.py index ade690e..82c7019 100644 --- a/basicswap/interface/nav.py +++ b/basicswap/interface/nav.py @@ -73,9 +73,6 @@ class NAVInterface(BTCInterface): # No multiwallet support self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) - def checkWallets(self) -> int: - return 1 - def use_p2shp2wsh(self) -> bool: # p2sh-p2wsh return True diff --git a/basicswap/interface/pivx.py b/basicswap/interface/pivx.py index 038744c..a88a6a1 100644 --- a/basicswap/interface/pivx.py +++ b/basicswap/interface/pivx.py @@ -35,9 +35,6 @@ class PIVXInterface(BTCInterface): # No multiwallet support self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) - def checkWallets(self) -> int: - return 1 - def signTxWithWallet(self, tx): rv = self.rpc('signrawtransaction', [tx.hex()]) return bytes.fromhex(rv['hex']) diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 30b587f..387ba0b 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -129,9 +129,6 @@ class XMRInterface(CoinInterface): self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ') - def checkWallets(self) -> int: - return 1 - def setFeePriority(self, new_priority): ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value') self._fee_priority = new_priority diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html index bee4d87..caa23dd 100644 --- a/basicswap/templates/wallet.html +++ b/basicswap/templates/wallet.html @@ -1,5 +1,5 @@ {% include 'header.html' %} -{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg, circular_error_svg, circular_info_svg, cross_close_svg, breadcrumb_line_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, red_cross_close_svg, blue_cross_close_svg, circular_update_messages_svg, circular_error_messages_svg %} +{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg, circular_error_svg, circular_info_svg, cross_close_svg, breadcrumb_line_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, red_cross_close_svg, blue_cross_close_svg, circular_update_messages_svg, circular_error_messages_svg %}
@@ -27,7 +27,7 @@
- {% include 'inc_messages.html' %} + {% include 'inc_messages.html' %} {% if w.updating %} {% endif %} {% if w.havedata %} - {% if w.error %} + {% if w.error %}