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. 2
      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

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

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