Decred: Add to test_xmr_persistent

master^2
tecnovert 7 months ago
parent 2a8c04b285
commit 446d6fe357
  1. 2
      basicswap/__init__.py
  2. 42
      basicswap/basicswap.py
  3. 9
      basicswap/interface/base.py
  4. 41
      basicswap/interface/dcr/dcr.py
  5. 2
      basicswap/interface/dcr/messages.py
  6. 2
      basicswap/interface/dcr/script.py
  7. 50
      basicswap/interface/dcr/util.py
  8. 3
      basicswap/interface/firo.py
  9. 3
      basicswap/interface/nav.py
  10. 3
      basicswap/interface/pivx.py
  11. 3
      basicswap/interface/xmr.py
  12. 100
      basicswap/templates/wallet.html
  13. 6
      basicswap/util/extkey.py
  14. 116
      bin/basicswap_prepare.py
  15. 28
      bin/basicswap_run.py
  16. 6
      tests/basicswap/common.py
  17. 54
      tests/basicswap/common_xmr.py
  18. 48
      tests/basicswap/extended/test_dcr.py
  19. 62
      tests/basicswap/extended/test_xmr_persistent.py

@ -1,3 +1,3 @@
name = "basicswap" name = "basicswap"
__version__ = "0.13.0" __version__ = "0.13.1"

@ -774,9 +774,7 @@ class BasicSwap(BaseApp):
if self.coin_clients[c]['connection_type'] == 'rpc': if self.coin_clients[c]['connection_type'] == 'rpc':
ci = self.ci(c) ci = self.ci(c)
self.waitForDaemonRPC(c, with_wallet=False) self.waitForDaemonRPC(c)
if c not in (Coins.XMR,) and ci.checkWallets() >= 1:
self.waitForDaemonRPC(c)
core_version = ci.getDaemonVersion() core_version = ci.getDaemonVersion()
self.log.info('%s Core version %d', ci.coin_name(), core_version) 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) self.log.info('Scanned %d unread messages.', nm)
def stopDaemon(self, coin) -> None: def stopDaemon(self, coin) -> None:
if coin == Coins.XMR: if coin in (Coins.XMR, Coins.DCR):
return return
num_tries = 10 num_tries = 10
authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie')
@ -893,6 +891,17 @@ class BasicSwap(BaseApp):
self.stopDaemon(c) self.stopDaemon(c)
def waitForDaemonRPC(self, coin_type, with_wallet: bool = True) -> None: 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 startup_tries = self.startup_tries
chain_client_settings = self.getChainClientSettings(coin_type) chain_client_settings = self.getChainClientSettings(coin_type)
if 'startup_tries' in chain_client_settings: 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) self.log.debug('Generated new receive address %s for %s', new_addr, Coins(coin_type).name)
return new_addr return new_addr
def getRelayFeeRateForCoin(self, coin_type):
return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee']
def getFeeRateForCoin(self, coin_type, conf_target: int = 2): def getFeeRateForCoin(self, coin_type, conf_target: int = 2):
return self.ci(coin_type).get_fee_rate(conf_target) return self.ci(coin_type).get_fee_rate(conf_target)
@ -3376,11 +3382,11 @@ class BasicSwap(BaseApp):
txn_script] txn_script]
redeem_txn = ci.setTxSignature(bytes.fromhex(redeem_txn), witness_stack).hex() redeem_txn = ci.setTxSignature(bytes.fromhex(redeem_txn), witness_stack).hex()
else: else:
script = (len(redeem_sig) // 2).to_bytes(1) + bytes.fromhex(redeem_sig) script = (len(redeem_sig) // 2).to_bytes(1, 'big') + bytes.fromhex(redeem_sig)
script += (33).to_bytes(1) + pubkey script += (33).to_bytes(1, 'big') + pubkey
script += (32).to_bytes(1) + secret script += (32).to_bytes(1, 'big') + secret
script += (OpCodes.OP_1).to_bytes(1) script += (OpCodes.OP_1).to_bytes(1, 'big')
script += (OpCodes.OP_PUSHDATA1).to_bytes(1) + (len(txn_script)).to_bytes(1) + txn_script 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() redeem_txn = ci.setTxScriptSig(bytes.fromhex(redeem_txn), 0, script).hex()
if coin_type in (Coins.NAV, Coins.DCR): if coin_type in (Coins.NAV, Coins.DCR):
@ -3488,10 +3494,10 @@ class BasicSwap(BaseApp):
txn_script] txn_script]
refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex() refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex()
else: else:
script = (len(refund_sig) // 2).to_bytes(1) + bytes.fromhex(refund_sig) script = (len(refund_sig) // 2).to_bytes(1, 'big') + bytes.fromhex(refund_sig)
script += (33).to_bytes(1) + pubkey script += (33).to_bytes(1, 'big') + pubkey
script += (OpCodes.OP_0).to_bytes(1) script += (OpCodes.OP_0).to_bytes(1, 'big')
script += (OpCodes.OP_PUSHDATA1).to_bytes(1) + (len(txn_script)).to_bytes(1) + txn_script 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) refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, script)
if coin_type in (Coins.NAV, Coins.DCR): if coin_type in (Coins.NAV, Coins.DCR):
@ -4517,8 +4523,8 @@ class BasicSwap(BaseApp):
except Exception as e: except Exception as e:
if 'Block not available (pruned data)' in str(e): if 'Block not available (pruned data)' in str(e):
# TODO: Better solution? # TODO: Better solution?
bci = self.callcoinrpc(coin_type, 'getblockchaininfo') bci = ci.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']) 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'] last_height_checked = bci['pruneheight']
continue continue
else: else:

@ -153,16 +153,19 @@ class CoinInterface:
def use_tx_vsize(self) -> bool: def use_tx_vsize(self) -> bool:
return self._use_segwit return self._use_segwit
def getLockTxSwapOutputValue(self, bid, xmr_swap): def getLockTxSwapOutputValue(self, bid, xmr_swap) -> int:
return bid.amount return bid.amount
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap): def getLockRefundTxSwapOutputValue(self, bid, xmr_swap) -> int:
return xmr_swap.a_swap_refund_value return xmr_swap.a_swap_refund_value
def getLockRefundTxSwapOutput(self, xmr_swap): def getLockRefundTxSwapOutput(self, xmr_swap) -> int:
# Only one prevout exists # Only one prevout exists
return 0 return 0
def checkWallets(self) -> int:
return 1
class AdaptorSigInterface(): class AdaptorSigInterface():
def getScriptLockTxDummyWitness(self, script: bytes): def getScriptLockTxDummyWitness(self, script: bytes):

@ -109,7 +109,7 @@ def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransactio
for txi_n, txi in enumerate(sign_vins): for txi_n, txi in enumerate(sign_vins):
hash_buffer += txi.prevout.hash.to_bytes(32, 'little') hash_buffer += txi.prevout.hash.to_bytes(32, 'little')
hash_buffer += txi.prevout.n.to_bytes(4, '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. # 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 if (masked_hash_type == SigHashType.SigHashNone
@ -308,14 +308,19 @@ class DCRInterface(Secp256k1Interface):
def getChainHeight(self) -> int: def getChainHeight(self) -> int:
return self.rpc('getblockcount') return self.rpc('getblockcount')
def checkWallets(self) -> int:
# Only one wallet possible?
return 1
def initialiseWallet(self, key: bytes) -> None: def initialiseWallet(self, key: bytes) -> None:
# Load with --create # Load with --create
pass 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): def getDaemonVersion(self):
return self.rpc('getnetworkinfo')['version'] return self.rpc('getnetworkinfo')['version']
@ -368,7 +373,7 @@ class DCRInterface(Secp256k1Interface):
def encodeKey(self, key_bytes: bytes) -> str: def encodeKey(self, key_bytes: bytes) -> str:
wif_prefix = self.chainparams_network()['key_prefix'] wif_prefix = self.chainparams_network()['key_prefix']
key_type = 0 # STEcdsaSecp256k1 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] b += blake256(b)[:4]
return b58encode(b) return b58encode(b)
@ -433,7 +438,7 @@ class DCRInterface(Secp256k1Interface):
script_hash = self.pkh(script) script_hash = self.pkh(script)
assert len(script_hash) == 20 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: def encodeScriptDest(self, script_dest: bytes) -> str:
script_hash = script_dest[2:-1] # Extract hash from script script_hash = script_dest[2:-1] # Extract hash from script
@ -442,7 +447,7 @@ class DCRInterface(Secp256k1Interface):
def getPubkeyHashDest(self, pkh: bytes) -> bytes: def getPubkeyHashDest(self, pkh: bytes) -> bytes:
# P2PKH # P2PKH
assert len(pkh) == 20 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: def getPkDest(self, K: bytes) -> bytearray:
return self.getPubkeyHashDest(self.pkh(K)) return self.getPubkeyHashDest(self.pkh(K))
@ -532,7 +537,7 @@ class DCRInterface(Secp256k1Interface):
prove_utxos.append(outpoint) prove_utxos.append(outpoint)
hasher.update(outpoint[0]) hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big')) 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: if sum_value >= amount_for:
break break
utxos_hash = hasher.digest() utxos_hash = hasher.digest()
@ -554,7 +559,7 @@ class DCRInterface(Secp256k1Interface):
def encodeProofUtxos(self, proof_utxos): def encodeProofUtxos(self, proof_utxos):
packed_utxos = bytes() packed_utxos = bytes()
for utxo in proof_utxos: 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 return packed_utxos
def decodeProofUtxos(self, msg_utxos): def decodeProofUtxos(self, msg_utxos):
@ -573,7 +578,7 @@ class DCRInterface(Secp256k1Interface):
for outpoint in utxos: for outpoint in utxos:
hasher.update(outpoint[0]) hasher.update(outpoint[0])
hasher.update(outpoint[1].to_bytes(2, 'big')) 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() utxos_hash = hasher.digest()
passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) 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) Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf)
script = bytearray() script = bytearray()
script += OP_IF.to_bytes(1) script += bytes((OP_IF,))
push_script_data(script, bytes((2,))) push_script_data(script, bytes((2,)))
push_script_data(script, Kal_enc) push_script_data(script, Kal_enc)
push_script_data(script, Kaf_enc) push_script_data(script, Kaf_enc)
push_script_data(script, bytes((2,))) push_script_data(script, bytes((2,)))
script += OP_CHECKMULTISIG.to_bytes(1) script += bytes((OP_CHECKMULTISIG,))
script += OP_ELSE.to_bytes(1) script += bytes((OP_ELSE,))
script += CScriptNum.encode(CScriptNum(csv_val)) script += CScriptNum.encode(CScriptNum(csv_val))
script += OP_CHECKSEQUENCEVERIFY.to_bytes(1) script += bytes((OP_CHECKSEQUENCEVERIFY,))
script += OP_DROP.to_bytes(1) script += bytes((OP_DROP,))
push_script_data(script, Kaf_enc) push_script_data(script, Kaf_enc)
script += OP_CHECKSIG.to_bytes(1) script += bytes((OP_CHECKSIG,))
script += OP_ENDIF.to_bytes(1) script += bytes((OP_ENDIF,))
return script return script

@ -162,7 +162,7 @@ class CTransaction:
for txi in self.vin: for txi in self.vin:
data += txi.prevout.hash.to_bytes(32, 'little') data += txi.prevout.hash.to_bytes(32, 'little')
data += txi.prevout.n.to_bytes(4, '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 += txi.sequence.to_bytes(4, 'little')
data += encode_compactsize(len(self.vout)) data += encode_compactsize(len(self.vout))

@ -39,7 +39,7 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
return return
if len_data < OP_PUSHDATA1: if len_data < OP_PUSHDATA1:
data_array += len_data.to_bytes(1) data_array += len_data.to_bytes(1, 'little')
elif len_data <= 0xff: elif len_data <= 0xff:
data_array += bytes((OP_PUSHDATA1, len_data)) data_array += bytes((OP_PUSHDATA1, len_data))
elif len_data <= 0xffff: elif len_data <= 0xffff:

@ -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()

@ -42,9 +42,6 @@ class FIROInterface(BTCInterface):
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) 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): def getExchangeName(self, exchange_name):
return 'zcoin' return 'zcoin'

@ -73,9 +73,6 @@ class NAVInterface(BTCInterface):
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) 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: def use_p2shp2wsh(self) -> bool:
# p2sh-p2wsh # p2sh-p2wsh
return True return True

@ -35,9 +35,6 @@ class PIVXInterface(BTCInterface):
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
def checkWallets(self) -> int:
return 1
def signTxWithWallet(self, tx): def signTxWithWallet(self, tx):
rv = self.rpc('signrawtransaction', [tx.hex()]) rv = self.rpc('signrawtransaction', [tx.hex()])
return bytes.fromhex(rv['hex']) return bytes.fromhex(rv['hex'])

@ -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.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 ') 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): def setFeePriority(self, new_priority):
ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value') ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value')
self._fee_priority = new_priority self._fee_priority = new_priority

@ -1,5 +1,5 @@
{% include 'header.html' %} {% 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 %}
<script src="/static/js/libs//qrcode.js"></script> <script src="/static/js/libs//qrcode.js"></script>
<div class="container mx-auto"> <div class="container mx-auto">
<section class="p-5 mt-5"> <section class="p-5 mt-5">
@ -27,7 +27,7 @@
</div> </div>
</div> </div>
</section> </section>
{% include 'inc_messages.html' %} {% include 'inc_messages.html' %}
{% if w.updating %} {% if w.updating %}
<section class="py-4" id="messages_updating" role="alert"> <section class="py-4" id="messages_updating" role="alert">
<div class="container px-4 mx-auto"> <div class="container px-4 mx-auto">
@ -42,7 +42,7 @@
<li class="font-semibold text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li> <li class="font-semibold text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li>
<li class="font-medium text-sm text-blue-500">Please wait...</li> <li class="font-medium text-sm text-blue-500">Please wait...</li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="w-auto p-2"> <div class="w-auto p-2">
<button type="button" class="ms-auto bg-blue-50 text-blue-500 rounded-lg focus:ring-0 focus:ring-blue-400 p-1.5 hover:bg-blue-200 inline-flex items-center justify-center h-8 w-8 focus:outline-none dark:bg-gray-800 dark:text-blue-400 dark:hover:bg-gray-700" data-dismiss-target="#messages_updating" aria-label="Close"><span class="sr-only">Close</span> <button type="button" class="ms-auto bg-blue-50 text-blue-500 rounded-lg focus:ring-0 focus:ring-blue-400 p-1.5 hover:bg-blue-200 inline-flex items-center justify-center h-8 w-8 focus:outline-none dark:bg-gray-800 dark:text-blue-400 dark:hover:bg-gray-700" data-dismiss-target="#messages_updating" aria-label="Close"><span class="sr-only">Close</span>
@ -55,7 +55,7 @@
</section> </section>
{% endif %} {% endif %}
{% if w.havedata %} {% if w.havedata %}
{% if w.error %} {% if w.error %}
<section class="py-4" id="messages_error" role="alert"> <section class="py-4" id="messages_error" role="alert">
<div class="container px-4 mx-auto"> <div class="container px-4 mx-auto">
<div class="p-6 text-green-800 rounded-lg bg-red-50 border border-red-400 dark:bg-gray-500 dark:text-red-400 rounded-md"> <div class="p-6 text-green-800 rounded-lg bg-red-50 border border-red-400 dark:bg-gray-500 dark:text-red-400 rounded-md">
@ -113,7 +113,7 @@
</tr> {% if w.cid == '1' %} {# PART #} </tr> {% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td> <td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} (<span class="usd-value"></span>) <td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
{% if w.blind_unconfirmed %} {% if w.blind_unconfirmed %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span>
{% endif %} {% endif %}
@ -136,11 +136,11 @@
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} (<span class="usd-value"></span>) <td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
{% if w.mweb_pending %} {% if w.mweb_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{# / LTC #} {# / LTC #}
{% if w.locked_utxos %} {% if w.locked_utxos %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Locked Outputs:</td> <td class="py-3 px-6 bold">Locked Outputs:</td>
@ -202,9 +202,11 @@
<div class="container mt-5 mx-auto"> <div class="container mt-5 mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl"> <div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6"> <div class="px-6">
{% if w.cid != '4' %} {# DCR #}
<div class="flex flex-wrap justify-end"> <div class="flex flex-wrap justify-end">
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div> <div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -369,7 +371,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{# / LTC #} {# / LTC #}
</div> </div>
</div> </div>
@ -383,7 +385,7 @@
<script> <script>
// Particl Stealth // Particl Stealth
var stealthAddress = "{{ w.stealth_address }}"; var stealthAddress = "{{ w.stealth_address }}";
var qrCodeStealth = new QRCode(document.getElementById("qrcode-stealth"), { var qrCodeStealth = new QRCode(document.getElementById("qrcode-stealth"), {
text: stealthAddress, text: stealthAddress,
width: 170, width: 170,
@ -399,7 +401,7 @@
<script> <script>
// Litecoin MWEB // Litecoin MWEB
var mwebAddress = "{{ w.mweb_address }}"; var mwebAddress = "{{ w.mweb_address }}";
var qrCodeMWEB = new QRCode(document.getElementById("qrcode-mweb"), { var qrCodeMWEB = new QRCode(document.getElementById("qrcode-mweb"), {
text: mwebAddress, text: mwebAddress,
width: 170, width: 170,
@ -408,14 +410,14 @@
colorLight: "#ffffff", colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.L correctLevel: QRCode.CorrectLevel.L
}); });
</script> </script>
{% endif %} {% endif %}
{% if w.cid == '6' %} {% if w.cid == '6' %}
{# XMR #} {# XMR #}
<script> <script>
// Monero Sub // Monero Sub
var moneroSubAddress = "{{ w.deposit_address }}"; var moneroSubAddress = "{{ w.deposit_address }}";
var qrCodeMoneroSub = new QRCode(document.getElementById("qrcode-monero-sub"), { var qrCodeMoneroSub = new QRCode(document.getElementById("qrcode-monero-sub"), {
text: moneroSubAddress, text: moneroSubAddress,
width: 170, width: 170,
@ -428,7 +430,7 @@
<script> <script>
// Monero Main // Monero Main
var moneroMainAddress = "{{ w.main_address }}"; var moneroMainAddress = "{{ w.main_address }}";
var qrCodeMoneroMain = new QRCode(document.getElementById("qrcode-monero-main"), { var qrCodeMoneroMain = new QRCode(document.getElementById("qrcode-monero-main"), {
text: moneroMainAddress, text: moneroMainAddress,
width: 170, width: 170,
@ -442,7 +444,7 @@
<script> <script>
// Default // Default
var defaultAddress = "{{ w.deposit_address }}"; var defaultAddress = "{{ w.deposit_address }}";
var qrCodeDepost = new QRCode(document.getElementById("qrcode-deposit"), { var qrCodeDepost = new QRCode(document.getElementById("qrcode-deposit"), {
text: defaultAddress, text: defaultAddress,
width: 170, width: 170,
@ -462,48 +464,48 @@
document.execCommand('copy'); document.execCommand('copy');
document.body.removeChild(el); document.body.removeChild(el);
} }
function copyAndShowMessage(elementId) { function copyAndShowMessage(elementId) {
const addressElement = document.getElementById(elementId); const addressElement = document.getElementById(elementId);
if (!addressElement) return; if (!addressElement) return;
const addressText = addressElement.innerText.trim(); const addressText = addressElement.innerText.trim();
copyToClipboard(addressText); copyToClipboard(addressText);
addressElement.innerText = 'Copied to clipboard'; addressElement.innerText = 'Copied to clipboard';
const originalWidth = addressElement.offsetWidth; const originalWidth = addressElement.offsetWidth;
addressElement.classList.add('copying'); addressElement.classList.add('copying');
addressElement.parentElement.style.width = `${originalWidth}px`; addressElement.parentElement.style.width = `${originalWidth}px`;
setTimeout(function () { setTimeout(function () {
addressElement.innerText = addressText; addressElement.innerText = addressText;
addressElement.classList.remove('copying'); addressElement.classList.remove('copying');
addressElement.parentElement.style.width = ''; addressElement.parentElement.style.width = '';
}, 2000); }, 2000);
} }
const stealthAddressElement = document.getElementById('stealth_address'); const stealthAddressElement = document.getElementById('stealth_address');
if (stealthAddressElement) { if (stealthAddressElement) {
stealthAddressElement.addEventListener('click', function () { stealthAddressElement.addEventListener('click', function () {
copyAndShowMessage('stealth_address'); copyAndShowMessage('stealth_address');
}); });
} }
const mainDepositAddressElement = document.getElementById('main_deposit_address'); const mainDepositAddressElement = document.getElementById('main_deposit_address');
if (mainDepositAddressElement) { if (mainDepositAddressElement) {
mainDepositAddressElement.addEventListener('click', function () { mainDepositAddressElement.addEventListener('click', function () {
copyAndShowMessage('main_deposit_address'); copyAndShowMessage('main_deposit_address');
}); });
} }
const moneroMainAddressElement = document.getElementById('monero_main_address'); const moneroMainAddressElement = document.getElementById('monero_main_address');
if (moneroMainAddressElement) { if (moneroMainAddressElement) {
moneroMainAddressElement.addEventListener('click', function () { moneroMainAddressElement.addEventListener('click', function () {
copyAndShowMessage('monero_main_address'); copyAndShowMessage('monero_main_address');
}); });
} }
const moneroSubAddressElement = document.getElementById('monero_sub_address'); const moneroSubAddressElement = document.getElementById('monero_sub_address');
if (moneroSubAddressElement) { if (moneroSubAddressElement) {
moneroSubAddressElement.addEventListener('click', function () { moneroSubAddressElement.addEventListener('click', function () {
@ -572,7 +574,7 @@
var selectedType = typeSelect.value; var selectedType = typeSelect.value;
var floatBalance; var floatBalance;
var calculatedAmount; var calculatedAmount;
switch(selectedType) { switch(selectedType) {
case 'plain': case 'plain':
floatBalance = parseFloat(balance); floatBalance = parseFloat(balance);
@ -591,7 +593,7 @@
calculatedAmount = floatBalance * percent; calculatedAmount = floatBalance * percent;
break; break;
} }
amountInput.value = calculatedAmount.toFixed(8); amountInput.value = calculatedAmount.toFixed(8);
} }
</script> </script>
@ -606,7 +608,7 @@
var selectedType = typeSelect.value; var selectedType = typeSelect.value;
var floatBalance; var floatBalance;
var calculatedAmount; var calculatedAmount;
switch(selectedType) { switch(selectedType) {
case 'plain': case 'plain':
floatBalance = parseFloat(balance); floatBalance = parseFloat(balance);
@ -621,7 +623,7 @@
calculatedAmount = floatBalance * percent; calculatedAmount = floatBalance * percent;
break; break;
} }
amountInput.value = calculatedAmount.toFixed(8); amountInput.value = calculatedAmount.toFixed(8);
} }
</script> </script>
@ -633,10 +635,10 @@
var amountInput = document.getElementById('amount'); var amountInput = document.getElementById('amount');
var floatBalance; var floatBalance;
var calculatedAmount; var calculatedAmount;
floatBalance = parseFloat(balance); floatBalance = parseFloat(balance);
calculatedAmount = floatBalance * percent; calculatedAmount = floatBalance * percent;
if (cid === '6' && percent === 1) { if (cid === '6' && percent === 1) {
amountInput.setAttribute('data-hidden', 'true'); amountInput.setAttribute('data-hidden', 'true');
amountInput.placeholder = 'Sweep All'; amountInput.placeholder = 'Sweep All';
@ -652,7 +654,7 @@
amountInput.placeholder = ''; amountInput.placeholder = '';
amountInput.disabled = false; amountInput.disabled = false;
} }
if (cid === '6' && percent === 1) { if (cid === '6' && percent === 1) {
var sweepAllCheckbox = document.getElementById('sweepall'); var sweepAllCheckbox = document.getElementById('sweepall');
if (sweepAllCheckbox) { if (sweepAllCheckbox) {
@ -665,7 +667,7 @@
} }
} }
} }
</script> </script>
{% endif %} {% endif %}
</div> </div>
@ -677,7 +679,7 @@
<td class="py-3 px-6"> <input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked=checked{% endif %}> </td> {% else %} <td class="py-3 px-6 bold">Subtract Fee:</td> <td class="py-3 px-6"> <input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked=checked{% endif %}> </td> {% else %} <td class="py-3 px-6 bold">Subtract Fee:</td>
<td class="py-3 px-6"> <input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked=checked{% endif %}> </td> <td class="py-3 px-6"> <input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked=checked{% endif %}> </td>
{% endif %} {% endif %}
<td> <td>
</td> </td>
</tr> </tr>
{% if w.cid == '1' %} {% if w.cid == '1' %}
@ -855,7 +857,7 @@
'DECRED': 'DCR', 'DECRED': 'DCR',
'WOWNERO': 'WOW' 'WOWNERO': 'WOW'
}; };
const getUsdValue = (cryptoValue, coinSymbol) => fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`) const getUsdValue = (cryptoValue, coinSymbol) => fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -866,16 +868,16 @@
throw new Error(`Invalid exchange rate for ${coinSymbol}`); throw new Error(`Invalid exchange rate for ${coinSymbol}`);
} }
}); });
const updateUsdValue = async (cryptoCell, coinFullName, usdValueSpan) => { const updateUsdValue = async (cryptoCell, coinFullName, usdValueSpan) => {
const coinSymbol = coinNameToSymbol[coinFullName] || ''; const coinSymbol = coinNameToSymbol[coinFullName] || '';
if (!coinSymbol) { if (!coinSymbol) {
console.error(`Coin symbol not found for full name: ${coinFullName}`); console.error(`Coin symbol not found for full name: ${coinFullName}`);
return; return;
} }
const cryptoValue = parseFloat(cryptoCell.textContent); const cryptoValue = parseFloat(cryptoCell.textContent);
if (!isNaN(cryptoValue) && cryptoValue !== 0) { if (!isNaN(cryptoValue) && cryptoValue !== 0) {
try { try {
const usdValue = await getUsdValue(cryptoValue, coinSymbol); const usdValue = await getUsdValue(cryptoValue, coinSymbol);
@ -894,19 +896,19 @@
} }
} }
}; };
const calculateTotalUsdValue = async () => { const calculateTotalUsdValue = async () => {
const coinNameValues = document.querySelectorAll('.coinname-value'); const coinNameValues = document.querySelectorAll('.coinname-value');
let totalUsdValue = 0; let totalUsdValue = 0;
for (const coinNameValue of coinNameValues) { for (const coinNameValue of coinNameValues) {
const coinFullName = coinNameValue.getAttribute('data-coinname'); const coinFullName = coinNameValue.getAttribute('data-coinname');
const cryptoValue = parseFloat(coinNameValue.textContent); const cryptoValue = parseFloat(coinNameValue.textContent);
const coinSymbol = coinNameToSymbol[coinFullName]; const coinSymbol = coinNameToSymbol[coinFullName];
if (coinSymbol) { if (coinSymbol) {
const usdValueSpan = coinNameValue.querySelector('.usd-value'); const usdValueSpan = coinNameValue.querySelector('.usd-value');
if (!isNaN(cryptoValue) && cryptoValue !== 0) { if (!isNaN(cryptoValue) && cryptoValue !== 0) {
try { try {
const usdValue = await getUsdValue(cryptoValue, coinSymbol); const usdValue = await getUsdValue(cryptoValue, coinSymbol);
@ -926,24 +928,24 @@
console.error(`Coin symbol not found for full name: ${coinFullName}`); console.error(`Coin symbol not found for full name: ${coinFullName}`);
} }
} }
const totalUsdValueElement = document.getElementById('total-usd-value'); const totalUsdValueElement = document.getElementById('total-usd-value');
if (totalUsdValueElement) { if (totalUsdValueElement) {
totalUsdValueElement.textContent = `$${totalUsdValue.toFixed(2)}`; totalUsdValueElement.textContent = `$${totalUsdValue.toFixed(2)}`;
} }
}; };
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const coinNameValues = document.querySelectorAll('.coinname-value'); const coinNameValues = document.querySelectorAll('.coinname-value');
for (const coinNameValue of coinNameValues) { for (const coinNameValue of coinNameValues) {
const coinFullName = coinNameValue.getAttribute('data-coinname'); const coinFullName = coinNameValue.getAttribute('data-coinname');
const usdValueSpan = coinNameValue.querySelector('.usd-value'); const usdValueSpan = coinNameValue.querySelector('.usd-value');
updateUsdValue(coinNameValue, coinFullName, usdValueSpan); updateUsdValue(coinNameValue, coinFullName, usdValueSpan);
} }
calculateTotalUsdValue(); calculateTotalUsdValue();
function set_sweep_all(element) { function set_sweep_all(element) {
let input = document.getElementById('amount'); let input = document.getElementById('amount');
if (element.checked) { if (element.checked) {
@ -952,7 +954,7 @@
input.disabled = false; input.disabled = false;
} }
} }
let cb_sweepall = document.getElementById('sweepall'); let cb_sweepall = document.getElementById('sweepall');
if (cb_sweepall) { if (cb_sweepall) {
set_sweep_all(cb_sweepall); set_sweep_all(cb_sweepall);
@ -960,7 +962,7 @@
set_sweep_all(event.currentTarget); set_sweep_all(event.currentTarget);
}); });
} }
}); });
</script> </script>
{% include 'footer.html' %} {% include 'footer.html' %}
@ -968,11 +970,11 @@
function confirmReseed() { function confirmReseed() {
return confirm("Are you sure?\nBackup your wallet before and after.\nWon't detect used keys.\nShould only be used for new wallets."); return confirm("Are you sure?\nBackup your wallet before and after.\nWon't detect used keys.\nShould only be used for new wallets.");
} }
function confirmWithdrawal() { function confirmWithdrawal() {
return confirm("Are you sure?"); return confirm("Are you sure?");
} }
function confirmUTXOResize() { function confirmUTXOResize() {
return confirm("Are you sure?"); return confirm("Are you sure?");
} }

@ -13,7 +13,7 @@ from coincurve.keys import (
def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes): def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes):
return hmac_sha512(chaincode, key_data_type.to_bytes(1) + keydata + child_no.to_bytes(4, 'big')) return hmac_sha512(chaincode, key_data_type.to_bytes(1, 'big') + keydata + child_no.to_bytes(4, 'big'))
def hash160_dcr(data: bytes) -> bytes: def hash160_dcr(data: bytes) -> bytes:
@ -85,7 +85,7 @@ class ExtKeyPair():
return out return out
def encode_v(self) -> bytes: def encode_v(self) -> bytes:
return self._depth.to_bytes(1) + \ return self._depth.to_bytes(1, 'big') + \
self._fingerprint + \ self._fingerprint + \
self._child_no.to_bytes(4, 'big') + \ self._child_no.to_bytes(4, 'big') + \
self._chaincode + \ self._chaincode + \
@ -94,7 +94,7 @@ class ExtKeyPair():
def encode_p(self) -> bytes: def encode_p(self) -> bytes:
pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey
return self._depth.to_bytes(1) + \ return self._depth.to_bytes(1, 'big') + \
self._fingerprint + \ self._fingerprint + \
self._child_no.to_bytes(4, 'big') + \ self._child_no.to_bytes(4, 'big') + \
self._chaincode + \ self._chaincode + \

@ -5,37 +5,40 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os import contextlib
import sys import gnupg
import hashlib
import json import json
import time import logging
import mmap import mmap
import stat import os
import gnupg import platform
import socks import random
import shutil import shutil
import signal import signal
import socket import socket
import hashlib import socks
import stat
import sys
import tarfile import tarfile
import zipfile import threading
import logging import time
import platform
import contextlib
import urllib.parse import urllib.parse
import zipfile
from urllib.error import ContentTooShortError from urllib.error import ContentTooShortError
from urllib.request import Request, urlopen
from urllib.parse import _splittype from urllib.parse import _splittype
from urllib.request import Request, urlopen
import basicswap.config as cfg import basicswap.config as cfg
from basicswap import __version__
from basicswap.base import getaddrinfo_tor from basicswap.base import getaddrinfo_tor
from basicswap.basicswap import BasicSwap from basicswap.basicswap import BasicSwap
from basicswap.chainparams import Coins from basicswap.chainparams import Coins
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from basicswap import __version__
from basicswap.ui.util import getCoinName from basicswap.ui.util import getCoinName
from basicswap.util import toBool from basicswap.util import toBool
from basicswap.util.rfc2440 import rfc2440_hash_password from basicswap.util.rfc2440 import rfc2440_hash_password
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from bin.basicswap_run import startDaemon, startXmrWalletDaemon from bin.basicswap_run import startDaemon, startXmrWalletDaemon
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0') PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
@ -92,7 +95,6 @@ known_coins = {
disabled_coins = [ disabled_coins = [
'navcoin', 'navcoin',
'namecoin', # Needs update 'namecoin', # Needs update
'decred', # In-progress
] ]
expected_key_ids = { expected_key_ids = {
@ -169,8 +171,11 @@ BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
DCR_RPC_HOST = os.getenv('DCR_RPC_HOST', '127.0.0.1') DCR_RPC_HOST = os.getenv('DCR_RPC_HOST', '127.0.0.1')
DCR_RPC_PORT = int(os.getenv('DCR_RPC_PORT', 9109)) DCR_RPC_PORT = int(os.getenv('DCR_RPC_PORT', 9109))
DCR_RPC_USER = os.getenv('DCR_RPC_USER', '') DCR_WALLET_RPC_HOST = os.getenv('DCR_WALLET_RPC_HOST', '127.0.0.1')
DCR_RPC_PWD = os.getenv('DCR_RPC_PWD', '') DCR_WALLET_RPC_PORT = int(os.getenv('DCR_WALLET_RPC_PORT', 9209))
DCR_WALLET_PWD = os.getenv('DCR_WALLET_PWD', random.randbytes(random.randint(14, 18)).hex())
DCR_RPC_USER = os.getenv('DCR_RPC_USER', 'user')
DCR_RPC_PWD = os.getenv('DCR_RPC_PWD', random.randbytes(random.randint(14, 18)).hex())
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1') NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698)) NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
@ -367,16 +372,16 @@ def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True):
socket.setdefaulttimeout(timeout) socket.setdefaulttimeout(timeout)
def popConnectionParameters(): def popConnectionParameters() -> None:
if use_tor_proxy: if use_tor_proxy:
socket.socket = default_socket socket.socket = default_socket
socket.getaddrinfo = default_socket_getaddrinfo socket.getaddrinfo = default_socket_getaddrinfo
socket.setdefaulttimeout(default_socket_timeout) socket.setdefaulttimeout(default_socket_timeout)
def downloadFile(url, path, timeout=5, resume_from=0): def downloadFile(url: str, path: str, timeout=5, resume_from=0) -> None:
logger.info('Downloading file %s', url) logger.info(f'Downloading file {url}')
logger.info('To %s', path) logger.info(f'To {path}')
try: try:
setConnectionParameters(timeout=timeout) setConnectionParameters(timeout=timeout)
urlretrieve(url, path, make_reporthook(resume_from), resume_from=resume_from) urlretrieve(url, path, make_reporthook(resume_from), resume_from=resume_from)
@ -397,13 +402,12 @@ def importPubkeyFromUrls(gpg, pubkeyurls):
try: try:
logger.info('Importing public key from url: ' + url) logger.info('Importing public key from url: ' + url)
rv = gpg.import_keys(downloadBytes(url)) rv = gpg.import_keys(downloadBytes(url))
for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY')
break break
except Exception as e: except Exception as e:
logging.warning('Import from url failed: %s', str(e)) logging.warning('Import from url failed: %s', str(e))
for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY')
def testTorConnection(): def testTorConnection():
test_url = 'https://check.torproject.org/' test_url = 'https://check.torproject.org/'
@ -773,6 +777,8 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if coin in ('navcoin', ): if coin in ('navcoin', ):
pubkey_filename = '{}_builder.pgp'.format(coin) pubkey_filename = '{}_builder.pgp'.format(coin)
elif coin in ('decred', ):
pubkey_filename = '{}_release.pgp'.format(coin)
else: else:
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name) pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
pubkeyurls = [ pubkeyurls = [
@ -914,6 +920,40 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write(opt_line + '\n') fp.write(opt_line + '\n')
return return
if coin == 'decred':
chainname = 'simnet' if chain == 'regtest' else chain
core_conf_path = os.path.join(data_dir, 'dcrd.conf')
if os.path.exists(core_conf_path):
exitWithError('{} exists'.format(core_conf_path))
with open(core_conf_path, 'w') as fp:
if chain != 'mainnet':
fp.write(chainname + '=1\n')
fp.write('debuglevel=debug\n')
fp.write('notls=1\n')
fp.write('rpclisten={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
fp.write('rpcuser={}\n'.format(core_settings['rpcuser']))
fp.write('rpcpass={}\n'.format(core_settings['rpcpassword']))
wallet_conf_path = os.path.join(data_dir, 'dcrwallet.conf')
if os.path.exists(wallet_conf_path):
exitWithError('{} exists'.format(wallet_conf_path))
with open(wallet_conf_path, 'w') as fp:
if chain != 'mainnet':
fp.write(chainname + '=1\n')
fp.write('debuglevel=debug\n')
fp.write('noservertls=1\n')
fp.write('noclienttls=1\n')
fp.write('rpcconnect={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
fp.write('rpclisten={}:{}\n'.format(core_settings['walletrpchost'], core_settings['walletrpcport']))
fp.write('username={}\n'.format(core_settings['rpcuser']))
fp.write('password={}\n'.format(core_settings['rpcpassword']))
return
core_conf_path = os.path.join(data_dir, coin + '.conf') core_conf_path = os.path.join(data_dir, coin + '.conf')
if os.path.exists(core_conf_path): if os.path.exists(core_conf_path):
exitWithError('{} exists'.format(core_conf_path)) exitWithError('{} exists'.format(core_conf_path))
@ -1253,6 +1293,8 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
if coin_settings['manage_wallet_daemon']: if coin_settings['manage_wallet_daemon']:
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '') filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename)) daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
elif c == Coins.DCR:
pass
else: else:
if coin_settings['manage_daemon']: if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
@ -1266,6 +1308,17 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
swap_client.setCoinRunParams(c) swap_client.setCoinRunParams(c)
swap_client.createCoinInterface(c) swap_client.createCoinInterface(c)
if c == Coins.DCR:
if coin_settings['manage_wallet_daemon']:
from basicswap.interface.dcr.util import createDCRWallet
extra_opts = ['--appdata="{}"'.format(coin_settings['datadir']),
'--pass={}'.format(coin_settings['wallet_pwd']),
]
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
args = [os.path.join(coin_settings['bindir'], filename), '--create'] + extra_opts
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
createDCRWallet(args, hex_seed, logger, threading.Event())
if c in coins_to_create_wallets_for: if c in coins_to_create_wallets_for:
swap_client.waitForDaemonRPC(c, with_wallet=False) swap_client.waitForDaemonRPC(c, with_wallet=False)
# Create wallet if it doesn't exist yet # Create wallet if it doesn't exist yet
@ -1300,7 +1353,9 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
c = swap_client.getCoinIdFromName(coin_name) c = swap_client.getCoinIdFromName(coin_name)
if c in (Coins.PART, ): if c in (Coins.PART, ):
continue continue
swap_client.waitForDaemonRPC(c) if c not in (Coins.DCR, ):
# initialiseWallet only sets main_wallet_seedid_
swap_client.waitForDaemonRPC(c)
try: try:
swap_client.initialiseWallet(c, raise_errors=True) swap_client.initialiseWallet(c, raise_errors=True)
except Exception as e: except Exception as e:
@ -1320,11 +1375,11 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
print('') print('')
for pair in coins_failed_to_initialise: for pair in coins_failed_to_initialise:
c, _ = pair c, e = pair
if c in (Coins.PIVX, ): if c in (Coins.PIVX, ):
print(f'NOTE - Unable to initialise wallet for {getCoinName(c)}. To complete setup click \'Reseed Wallet\' from the ui page once chain is synced.') print(f'NOTE - Unable to initialise wallet for {getCoinName(c)}. To complete setup click \'Reseed Wallet\' from the ui page once chain is synced.')
else: else:
print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}') print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}: {e}')
if particl_wallet_mnemonic is not None: if particl_wallet_mnemonic is not None:
if particl_wallet_mnemonic: if particl_wallet_mnemonic:
@ -1637,10 +1692,17 @@ def main():
'decred': { 'decred': {
'connection_type': 'rpc' if 'decred' in with_coins else 'none', 'connection_type': 'rpc' if 'decred' in with_coins else 'none',
'manage_daemon': True if ('decred' in with_coins and DCR_RPC_HOST == '127.0.0.1') else False, 'manage_daemon': True if ('decred' in with_coins and DCR_RPC_HOST == '127.0.0.1') else False,
'manage_wallet_daemon': True if ('decred' in with_coins and DCR_WALLET_RPC_HOST == '127.0.0.1') else False,
'wallet_pwd': DCR_WALLET_PWD,
'rpchost': DCR_RPC_HOST, 'rpchost': DCR_RPC_HOST,
'rpcport': DCR_RPC_PORT + port_offset, 'rpcport': DCR_RPC_PORT + port_offset,
'walletrpchost': DCR_WALLET_RPC_HOST,
'walletrpcport': DCR_WALLET_RPC_PORT + port_offset,
'rpcuser': DCR_RPC_USER,
'rpcpassword': DCR_RPC_PWD,
'datadir': os.getenv('DCR_DATA_DIR', os.path.join(data_dir, 'decred')), 'datadir': os.getenv('DCR_DATA_DIR', os.path.join(data_dir, 'decred')),
'bindir': os.path.join(bin_dir, 'decred'), 'bindir': os.path.join(bin_dir, 'decred'),
'use_csv': True,
'use_segwit': True, 'use_segwit': True,
'blocks_confirmed': 2, 'blocks_confirmed': 2,
'conf_target': 2, 'conf_target': 2,

@ -236,7 +236,33 @@ def runClient(fp, data_dir, chain, start_only_coins):
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid)) swap_client.log.info('Started {} {}'.format(filename, pid))
continue continue # /monero
if c == 'decred':
appdata = v['datadir']
extra_opts = [f'--appdata="{appdata}"', ]
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
filename = 'dcrd' + ('.exe' if os.name == 'nt' else '')
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrd_stdout.log'}
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
if v['manage_wallet_daemon'] is True:
swap_client.log.info(f'Starting {display_name} wallet daemon')
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
wallet_pwd = v['wallet_pwd']
extra_opts.append(f'--pass="{wallet_pwd}"')
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrwallet_stdout.log'}
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
continue # /decred
if v['manage_daemon'] is True: if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon') swap_client.log.info(f'Starting {display_name} daemon')

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert # Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. # file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
@ -35,6 +35,10 @@ LTC_BASE_PORT = 34792
LTC_BASE_RPC_PORT = 35792 LTC_BASE_RPC_PORT = 35792
LTC_BASE_ZMQ_PORT = 36792 LTC_BASE_ZMQ_PORT = 36792
DCR_BASE_PORT = 18555
DCR_BASE_RPC_PORT = 9110
PIVX_BASE_PORT = 34892 PIVX_BASE_PORT = 34892
PIVX_BASE_RPC_PORT = 35892 PIVX_BASE_RPC_PORT = 35892
PIVX_BASE_ZMQ_PORT = 36892 PIVX_BASE_ZMQ_PORT = 36892

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert # Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -29,6 +29,7 @@ from tests.basicswap.common import (
BASE_PORT, BASE_RPC_PORT, BASE_PORT, BASE_RPC_PORT,
BTC_BASE_PORT, BTC_BASE_RPC_PORT, BTC_BASE_TOR_PORT, BTC_BASE_PORT, BTC_BASE_RPC_PORT, BTC_BASE_TOR_PORT,
LTC_BASE_PORT, LTC_BASE_RPC_PORT, LTC_BASE_PORT, LTC_BASE_RPC_PORT,
DCR_BASE_PORT, DCR_BASE_RPC_PORT,
PIVX_BASE_PORT, PIVX_BASE_PORT,
) )
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
@ -46,6 +47,7 @@ BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT
BITCOIN_TOR_PORT_BASE = int(os.getenv('BITCOIN_TOR_PORT_BASE', BTC_BASE_TOR_PORT)) BITCOIN_TOR_PORT_BASE = int(os.getenv('BITCOIN_TOR_PORT_BASE', BTC_BASE_TOR_PORT))
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT)) LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
DECRED_RPC_PORT_BASE = int(os.getenv('DECRED_RPC_PORT_BASE', DCR_BASE_RPC_PORT))
FIRO_BASE_PORT = 34832 FIRO_BASE_PORT = 34832
FIRO_BASE_RPC_PORT = 35832 FIRO_BASE_RPC_PORT = 35832
@ -93,11 +95,14 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE) os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE)
os.environ['BTC_RPC_PORT'] = str(BITCOIN_RPC_PORT_BASE) os.environ['BTC_RPC_PORT'] = str(BITCOIN_RPC_PORT_BASE)
os.environ['LTC_RPC_PORT'] = str(LITECOIN_RPC_PORT_BASE) os.environ['LTC_RPC_PORT'] = str(LITECOIN_RPC_PORT_BASE)
os.environ['DCR_RPC_PORT'] = str(DECRED_RPC_PORT_BASE)
os.environ['FIRO_RPC_PORT'] = str(FIRO_RPC_PORT_BASE) os.environ['FIRO_RPC_PORT'] = str(FIRO_RPC_PORT_BASE)
os.environ['XMR_RPC_USER'] = 'xmr_user' os.environ['XMR_RPC_USER'] = 'xmr_user'
os.environ['XMR_RPC_PWD'] = 'xmr_pwd' os.environ['XMR_RPC_PWD'] = 'xmr_pwd'
os.environ['DCR_RPC_PWD'] = 'dcr_pwd'
import bin.basicswap_prepare as prepareSystem import bin.basicswap_prepare as prepareSystem
# Hack: Reload module to set env vars as the basicswap_prepare module is initialised if imported from elsewhere earlier # Hack: Reload module to set env vars as the basicswap_prepare module is initialised if imported from elsewhere earlier
from importlib import reload from importlib import reload
@ -126,9 +131,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
with open(config_path) as fs: with open(config_path) as fs:
settings = json.load(fs) settings = json.load(fs)
with open(os.path.join(datadir_path, 'particl', 'particl.conf'), 'r') as fp: config_filename = os.path.join(datadir_path, 'particl', 'particl.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines() lines = fp.readlines()
with open(os.path.join(datadir_path, 'particl', 'particl.conf'), 'w') as fp: with open(config_filename, 'w') as fp:
for line in lines: for line in lines:
if not line.startswith('staking'): if not line.startswith('staking'):
fp.write(line) fp.write(line)
@ -158,9 +164,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
if 'bitcoin' in coins_array: if 'bitcoin' in coins_array:
# Pruned nodes don't provide blocks # Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf'), 'r') as fp: config_filename = os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines() lines = fp.readlines()
with open(os.path.join(datadir_path, 'bitcoin', 'bitcoin.conf'), 'w') as fp: with open(config_filename, 'w') as fp:
for line in lines: for line in lines:
if not line.startswith('prune'): if not line.startswith('prune'):
fp.write(line) fp.write(line)
@ -188,9 +195,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
if 'litecoin' in coins_array: if 'litecoin' in coins_array:
# Pruned nodes don't provide blocks # Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'litecoin', 'litecoin.conf'), 'r') as fp: config_filename = os.path.join(datadir_path, 'litecoin', 'litecoin.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines() lines = fp.readlines()
with open(os.path.join(datadir_path, 'litecoin', 'litecoin.conf'), 'w') as fp: with open(config_filename, 'w') as fp:
for line in lines: for line in lines:
if not line.startswith('prune'): if not line.startswith('prune'):
fp.write(line) fp.write(line)
@ -213,11 +221,34 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []): for opt in EXTRA_CONFIG_JSON.get('ltc{}'.format(node_id), []):
fp.write(opt + '\n') fp.write(opt + '\n')
if 'decred' in coins_array:
# Pruned nodes don't provide blocks
config_filename = os.path.join(datadir_path, 'decred', 'dcrd.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines()
with open(config_filename, 'w') as fp:
for line in lines:
if not line.startswith('prune'):
fp.write(line)
fp.write('listen=127.0.0.1:{}\n'.format(DCR_BASE_PORT + node_id + port_ofs))
fp.write('noseeders=1\n')
fp.write('nodnsseed=1\n')
fp.write('nodiscoverip=1\n')
if node_id == 0:
fp.write('miningaddr=SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH\n')
for ip in range(num_nodes):
if ip != node_id:
fp.write('addpeer=127.0.0.1:{}\n'.format(DCR_BASE_PORT + ip + port_ofs))
config_filename = os.path.join(datadir_path, 'decred', 'dcrwallet.conf')
with open(config_filename, 'a') as fp:
fp.write('enablevoting=1\n')
if 'pivx' in coins_array: if 'pivx' in coins_array:
# Pruned nodes don't provide blocks # Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'r') as fp: config_filename = os.path.join(datadir_path, 'pivx', 'pivx.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines() lines = fp.readlines()
with open(os.path.join(datadir_path, 'pivx', 'pivx.conf'), 'w') as fp: with open(config_filename, 'w') as fp:
for line in lines: for line in lines:
if not line.startswith('prune'): if not line.startswith('prune'):
fp.write(line) fp.write(line)
@ -242,9 +273,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
if 'firo' in coins_array: if 'firo' in coins_array:
# Pruned nodes don't provide blocks # Pruned nodes don't provide blocks
with open(os.path.join(datadir_path, 'firo', 'firo.conf'), 'r') as fp: config_filename = os.path.join(datadir_path, 'firo', 'firo.conf')
with open(config_filename, 'r') as fp:
lines = fp.readlines() lines = fp.readlines()
with open(os.path.join(datadir_path, 'firo', 'firo.conf'), 'w') as fp: with open(config_filename, 'w') as fp:
for line in lines: for line in lines:
if not line.startswith('prune'): if not line.startswith('prune'):
fp.write(line) fp.write(line)

@ -9,8 +9,7 @@ import copy
import logging import logging
import os import os
import random import random
import select
import subprocess
import unittest import unittest
import basicswap.config as cfg import basicswap.config as cfg
@ -36,6 +35,9 @@ from basicswap.interface.dcr.messages import (
SigHashType, SigHashType,
TxSerializeType, TxSerializeType,
) )
from basicswap.interface.dcr.util import (
createDCRWallet,
)
from tests.basicswap.common import ( from tests.basicswap.common import (
compare_bid_states, compare_bid_states,
compare_bid_states_unordered, compare_bid_states_unordered,
@ -466,6 +468,9 @@ def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
f'rpcuser=test{node_id}\n', f'rpcuser=test{node_id}\n',
f'rpcpass=test_pass{node_id}\n', f'rpcpass=test_pass{node_id}\n',
'notls=1\n', 'notls=1\n',
'noseeders=1\n',
'nodnsseed=1\n',
'nodiscoverip=1\n',
'miningaddr=SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH\n',] 'miningaddr=SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH\n',]
for i in range(0, num_nodes): for i in range(0, num_nodes):
@ -511,8 +516,8 @@ class Test(BaseTest):
@classmethod @classmethod
def prepareExtraCoins(cls): def prepareExtraCoins(cls):
ci0 = cls.swap_clients[0].ci(cls.test_coin)
if not cls.restore_instance: if not cls.restore_instance:
ci0 = cls.swap_clients[0].ci(cls.test_coin)
assert (ci0.rpc_wallet('getnewaddress') == cls.dcr_mining_addr) assert (ci0.rpc_wallet('getnewaddress') == cls.dcr_mining_addr)
cls.dcr_ticket_account = ci0.rpc_wallet('getaccount', [cls.dcr_mining_addr, ]) cls.dcr_ticket_account = ci0.rpc_wallet('getaccount', [cls.dcr_mining_addr, ])
ci0.rpc('generate', [110,]) ci0.rpc('generate', [110,])
@ -567,42 +572,9 @@ class Test(BaseTest):
waitForRPC(make_rpc_func(i, base_rpc_port=DCR_BASE_RPC_PORT), test_delay_event, rpc_command='getnetworkinfo', max_tries=12) waitForRPC(make_rpc_func(i, base_rpc_port=DCR_BASE_RPC_PORT), test_delay_event, rpc_command='getnetworkinfo', max_tries=12)
logging.info('Creating wallet')
extra_opts.append('--pass=test_pass') extra_opts.append('--pass=test_pass')
args = [os.path.join(DCR_BINDIR, DCR_WALLET), '--create'] + extra_opts args = [os.path.join(DCR_BINDIR, DCR_WALLET), '--create'] + extra_opts
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read createDCRWallet(args, cls.hex_seeds[i], logging, test_delay_event)
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 = (cls.hex_seeds[i] + '\n').encode('utf-8')
elif 'Seed input successful' in buf:
pass
else:
raise ValueError(f'Unexpected output: {buf}')
if response is not None:
p.stdin.write(response)
p.stdin.flush()
test_delay_event.wait(0.1)
except Exception as e:
logging.error(f'{DCR_WALLET} --create failed: {e}')
finally:
if p.poll() is None:
p.terminate()
os.close(pipe_r)
os.close(pipe_w)
p.stdin.close()
test_delay_event.wait(1.0) test_delay_event.wait(1.0)
@ -769,7 +741,7 @@ class Test(BaseTest):
script = bytearray() script = bytearray()
push_script_data(script, bytes((3,))) push_script_data(script, bytes((3,)))
script += OP_CHECKSEQUENCEVERIFY.to_bytes(1) script += bytes((OP_CHECKSEQUENCEVERIFY,))
script_dest = ci0.getScriptDest(script) script_dest = ci0.getScriptDest(script)
script_info = ci0.rpc_wallet('decodescript', [script_dest.hex(),]) script_info = ci0.rpc_wallet('decodescript', [script_dest.hex(),])

@ -49,6 +49,7 @@ from tests.basicswap.common_xmr import (
prepare_nodes, prepare_nodes,
XMR_BASE_RPC_PORT, XMR_BASE_RPC_PORT,
) )
from basicswap.interface.dcr.rpc import callrpc as callrpc_dcr
import bin.basicswap_run as runSystem import bin.basicswap_run as runSystem
@ -61,6 +62,7 @@ UI_PORT = 12700 + PORT_OFS
PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT)) PARTICL_RPC_PORT_BASE = int(os.getenv('PARTICL_RPC_PORT_BASE', BASE_RPC_PORT))
BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT)) BITCOIN_RPC_PORT_BASE = int(os.getenv('BITCOIN_RPC_PORT_BASE', BTC_BASE_RPC_PORT))
LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT)) LITECOIN_RPC_PORT_BASE = int(os.getenv('LITECOIN_RPC_PORT_BASE', LTC_BASE_RPC_PORT))
DECRED_WALLET_RPC_PORT_BASE = int(os.getenv('DECRED_WALLET_RPC_PORT_BASE', 9210))
XMR_BASE_RPC_PORT = int(os.getenv('XMR_BASE_RPC_PORT', XMR_BASE_RPC_PORT)) XMR_BASE_RPC_PORT = int(os.getenv('XMR_BASE_RPC_PORT', XMR_BASE_RPC_PORT))
TEST_COINS_LIST = os.getenv('TEST_COINS_LIST', 'bitcoin,monero') TEST_COINS_LIST = os.getenv('TEST_COINS_LIST', 'bitcoin,monero')
@ -88,6 +90,11 @@ def callltcrpc(node_id, method, params=[], wallet=None, base_rpc_port=LITECOIN_R
return callrpc(base_rpc_port + node_id, auth, method, params, wallet) return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
def calldcrrpc(node_id, method, params=[], wallet=None, base_rpc_port=DECRED_WALLET_RPC_PORT_BASE):
auth = 'user:dcr_pwd'
return callrpc_dcr(base_rpc_port + node_id, auth, method, params)
def updateThread(cls): def updateThread(cls):
while not cls.delay_event.is_set(): while not cls.delay_event.is_set():
try: try:
@ -98,7 +105,7 @@ def updateThread(cls):
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max)) cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
def updateThreadXmr(cls): def updateThreadXMR(cls):
xmr_auth = None xmr_auth = None
if os.getenv('XMR_RPC_USER', '') != '': if os.getenv('XMR_RPC_USER', '') != '':
xmr_auth = (os.getenv('XMR_RPC_USER', ''), os.getenv('XMR_RPC_PWD', '')) xmr_auth = (os.getenv('XMR_RPC_USER', ''), os.getenv('XMR_RPC_PWD', ''))
@ -108,10 +115,38 @@ def updateThreadXmr(cls):
if cls.xmr_addr is not None: if cls.xmr_addr is not None:
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}, auth=xmr_auth) callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}, auth=xmr_auth)
except Exception as e: except Exception as e:
print('updateThreadXmr error', str(e)) print('updateThreadXMR error', str(e))
cls.delay_event.wait(random.randrange(cls.xmr_update_min, cls.xmr_update_max)) cls.delay_event.wait(random.randrange(cls.xmr_update_min, cls.xmr_update_max))
def updateThreadDCR(cls):
while not cls.delay_event.is_set():
try:
pass
num_passed: int = 0
for i in range(30):
try:
calldcrrpc(0, 'purchaseticket', [cls.dcr_acc, 0.1, 0])
num_passed += 1
if num_passed >= 5:
break
cls.delay_event.wait(0.1)
except Exception as e:
if 'double spend' in str(e):
pass
else:
logging.warning('updateThreadDCR purchaseticket {}'.format(e))
cls.delay_event.wait(0.5)
try:
if num_passed >= 5:
calldcrrpc(0, 'generate', [1,])
except Exception as e:
logging.warning('updateThreadDCR generate {}'.format(e))
except Exception as e:
print('updateThreadDCR error', str(e))
cls.delay_event.wait(random.randrange(cls.dcr_update_min, cls.dcr_update_max))
class Test(unittest.TestCase): class Test(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -123,12 +158,18 @@ class Test(unittest.TestCase):
cls.xmr_update_min = int(os.getenv('XMR_UPDATE_THREAD_MIN_WAIT', '1')) cls.xmr_update_min = int(os.getenv('XMR_UPDATE_THREAD_MIN_WAIT', '1'))
cls.xmr_update_max = cls.xmr_update_min * 4 cls.xmr_update_max = cls.xmr_update_min * 4
cls.dcr_update_min = int(os.getenv('DCR_UPDATE_THREAD_MIN_WAIT', '1'))
cls.dcr_update_max = cls.dcr_update_min * 4
cls.delay_event = threading.Event() cls.delay_event = threading.Event()
cls.update_thread = None cls.update_thread = None
cls.update_thread_xmr = None cls.update_thread_xmr = None
cls.update_thread_dcr = None
cls.processes = [] cls.processes = []
cls.btc_addr = None cls.btc_addr = None
cls.xmr_addr = None cls.xmr_addr = None
cls.dcr_addr = 'SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH'
cls.dcr_acc = None
random.seed(time.time()) random.seed(time.time())
@ -197,13 +238,25 @@ class Test(unittest.TestCase):
have_blocks: int = callltcrpc(0, 'getblockcount') have_blocks: int = callltcrpc(0, 'getblockcount')
callltcrpc(0, 'generatetoaddress', [500 - have_blocks, self.ltc_addr], wallet='wallet.dat') callltcrpc(0, 'generatetoaddress', [500 - have_blocks, self.ltc_addr], wallet='wallet.dat')
if 'decred' in TEST_COINS_LIST:
if RESET_TEST:
addr = calldcrrpc(0, 'getnewaddress')
# assert (addr == self.dcr_addr)
self.dcr_acc = calldcrrpc(0, 'getaccount', [self.dcr_addr, ])
addr = calldcrrpc(0, 'generate', [110,])
else:
self.dcr_acc = calldcrrpc(0, 'getaccount', [self.dcr_addr, ])
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
self.update_thread_dcr.start()
# Lower output split threshold for more stakeable outputs # Lower output split threshold for more stakeable outputs
for i in range(NUM_NODES): for i in range(NUM_NODES):
callpartrpc(i, 'walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) callpartrpc(i, 'walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}])
self.update_thread = threading.Thread(target=updateThread, args=(self,)) self.update_thread = threading.Thread(target=updateThread, args=(self,))
self.update_thread.start() self.update_thread.start()
self.update_thread_xmr = threading.Thread(target=updateThreadXmr, args=(self,)) self.update_thread_xmr = threading.Thread(target=updateThreadXMR, args=(self,))
self.update_thread_xmr.start() self.update_thread_xmr.start()
# Wait for height, or sequencelock is thrown off by genesis blocktime # Wait for height, or sequencelock is thrown off by genesis blocktime
@ -228,12 +281,15 @@ class Test(unittest.TestCase):
cls.update_thread.join() cls.update_thread.join()
if cls.update_thread_xmr: if cls.update_thread_xmr:
cls.update_thread_xmr.join() cls.update_thread_xmr.join()
if cls.update_thread_dcr:
cls.update_thread_dcr.join()
for p in cls.processes: for p in cls.processes:
p.terminate() p.terminate()
for p in cls.processes: for p in cls.processes:
p.join() p.join()
cls.update_thread = None cls.update_thread = None
cls.update_thread_xmr = None cls.update_thread_xmr = None
cls.update_thread_dcr = None
cls.processes = [] cls.processes = []
def test_persistent(self): def test_persistent(self):

Loading…
Cancel
Save