2021-03-17 23:36:11 +01:00
|
|
|
#!/usr/bin/env python3
|
2021-04-20 21:33:02 +02:00
|
|
|
# Copyright (c) 2019-2021 The Dash Core developers
|
2021-03-17 23:36:11 +01:00
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
2024-12-16 09:31:46 +01:00
|
|
|
import random
|
|
|
|
|
2021-03-17 23:36:11 +01:00
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
2024-12-16 09:33:22 +01:00
|
|
|
from test_framework.messages import (
|
|
|
|
COIN,
|
|
|
|
MAX_MONEY,
|
2024-12-16 09:31:46 +01:00
|
|
|
uint256_to_string,
|
2024-12-16 09:33:22 +01:00
|
|
|
)
|
|
|
|
from test_framework.util import (
|
|
|
|
assert_equal,
|
2024-12-16 09:31:46 +01:00
|
|
|
assert_is_hex_string,
|
2024-12-16 09:33:22 +01:00
|
|
|
assert_raises_rpc_error,
|
|
|
|
)
|
2021-03-17 23:36:11 +01:00
|
|
|
|
2024-12-16 09:33:22 +01:00
|
|
|
# See coinjoin/options.h
|
|
|
|
COINJOIN_ROUNDS_DEFAULT = 4
|
|
|
|
COINJOIN_ROUNDS_MAX = 16
|
|
|
|
COINJOIN_ROUNDS_MIN = 2
|
|
|
|
COINJOIN_TARGET_MAX = int(MAX_MONEY / COIN)
|
|
|
|
COINJOIN_TARGET_MIN = 2
|
2021-03-17 23:36:11 +01:00
|
|
|
|
|
|
|
class CoinJoinTest(BitcoinTestFramework):
|
|
|
|
def set_test_params(self):
|
|
|
|
self.num_nodes = 1
|
|
|
|
|
2018-09-13 12:33:15 +02:00
|
|
|
def skip_test_if_missing_module(self):
|
|
|
|
self.skip_if_no_wallet()
|
|
|
|
|
2024-12-16 09:29:31 +01:00
|
|
|
def setup_nodes(self):
|
|
|
|
self.add_nodes(self.num_nodes)
|
|
|
|
self.start_nodes()
|
|
|
|
|
2021-03-17 23:36:11 +01:00
|
|
|
def run_test(self):
|
2024-12-16 09:29:31 +01:00
|
|
|
node = self.nodes[0]
|
|
|
|
|
|
|
|
node.createwallet(wallet_name='w1', blank=True, disable_private_keys=False)
|
|
|
|
w1 = node.get_wallet_rpc('w1')
|
2024-12-16 09:31:46 +01:00
|
|
|
self.test_salt_presence(w1)
|
2024-12-16 09:29:31 +01:00
|
|
|
self.test_coinjoin_start_stop(w1)
|
2024-12-16 08:38:30 +01:00
|
|
|
self.test_setcoinjoinamount(w1)
|
|
|
|
self.test_setcoinjoinrounds(w1)
|
2024-12-16 09:31:46 +01:00
|
|
|
self.test_coinjoinsalt(w1)
|
|
|
|
w1.unloadwallet()
|
|
|
|
|
|
|
|
node.createwallet(wallet_name='w2', blank=True, disable_private_keys=True)
|
|
|
|
w2 = node.get_wallet_rpc('w2')
|
|
|
|
self.test_coinjoinsalt_disabled(w2)
|
|
|
|
w2.unloadwallet()
|
|
|
|
|
|
|
|
def test_salt_presence(self, node):
|
|
|
|
self.log.info('Salt should be automatically generated in new wallet')
|
|
|
|
# Will raise exception if no salt generated
|
|
|
|
assert_is_hex_string(node.coinjoinsalt('get'))
|
2021-03-17 23:36:11 +01:00
|
|
|
|
2024-12-16 09:29:31 +01:00
|
|
|
def test_coinjoin_start_stop(self, node):
|
2024-12-16 09:33:22 +01:00
|
|
|
self.log.info('"coinjoin" subcommands should update mixing status')
|
|
|
|
# Start mix session and ensure it's reported
|
2024-12-16 09:29:31 +01:00
|
|
|
node.coinjoin('start')
|
|
|
|
cj_info = node.getcoinjoininfo()
|
2021-03-17 23:36:11 +01:00
|
|
|
assert_equal(cj_info['enabled'], True)
|
|
|
|
assert_equal(cj_info['running'], True)
|
2024-12-16 09:33:22 +01:00
|
|
|
# Repeated start should yield error
|
|
|
|
assert_raises_rpc_error(-32603, 'Mixing has been started already.', node.coinjoin, 'start')
|
2021-03-17 23:36:11 +01:00
|
|
|
|
2024-12-16 09:33:22 +01:00
|
|
|
# Stop mix session and ensure it's reported
|
2024-12-16 09:29:31 +01:00
|
|
|
node.coinjoin('stop')
|
|
|
|
cj_info = node.getcoinjoininfo()
|
2021-03-17 23:36:11 +01:00
|
|
|
assert_equal(cj_info['enabled'], True)
|
|
|
|
assert_equal(cj_info['running'], False)
|
2024-12-16 09:33:22 +01:00
|
|
|
# Repeated stop should yield error
|
|
|
|
assert_raises_rpc_error(-32603, 'No mix session to stop', node.coinjoin, 'stop')
|
|
|
|
|
|
|
|
# Reset mix session
|
|
|
|
assert_equal(node.coinjoin('reset'), "Mixing was reset")
|
2021-03-17 23:36:11 +01:00
|
|
|
|
2024-12-16 08:38:30 +01:00
|
|
|
def test_setcoinjoinamount(self, node):
|
2024-12-16 09:33:22 +01:00
|
|
|
self.log.info('"setcoinjoinamount" should update mixing target')
|
2024-12-16 08:38:30 +01:00
|
|
|
# Test normal and large values
|
2024-12-16 09:33:22 +01:00
|
|
|
for value in [COINJOIN_TARGET_MIN, 50, 1200000, COINJOIN_TARGET_MAX]:
|
2024-12-16 08:38:30 +01:00
|
|
|
node.setcoinjoinamount(value)
|
|
|
|
assert_equal(node.getcoinjoininfo()['max_amount'], value)
|
2024-12-16 09:33:22 +01:00
|
|
|
# Test values below minimum and above maximum
|
|
|
|
for value in [COINJOIN_TARGET_MIN - 1, COINJOIN_TARGET_MAX + 1]:
|
|
|
|
assert_raises_rpc_error(-8, "Invalid amount of DASH as mixing goal amount", node.setcoinjoinamount, value)
|
2021-03-17 23:36:11 +01:00
|
|
|
|
2024-12-16 08:38:30 +01:00
|
|
|
def test_setcoinjoinrounds(self, node):
|
2024-12-16 09:33:22 +01:00
|
|
|
self.log.info('"setcoinjoinrounds" should update mixing rounds')
|
|
|
|
# Test acceptable values
|
|
|
|
for value in [COINJOIN_ROUNDS_MIN, COINJOIN_ROUNDS_DEFAULT, COINJOIN_ROUNDS_MAX]:
|
|
|
|
node.setcoinjoinrounds(value)
|
|
|
|
assert_equal(node.getcoinjoininfo()['max_rounds'], value)
|
|
|
|
# Test values below minimum and above maximum
|
|
|
|
for value in [COINJOIN_ROUNDS_MIN - 1, COINJOIN_ROUNDS_MAX + 1]:
|
|
|
|
assert_raises_rpc_error(-8, "Invalid number of rounds", node.setcoinjoinrounds, value)
|
2021-03-17 23:36:11 +01:00
|
|
|
|
2024-12-16 09:31:46 +01:00
|
|
|
def test_coinjoinsalt(self, node):
|
|
|
|
self.log.info('"coinjoinsalt generate" should fail if salt already present')
|
|
|
|
assert_raises_rpc_error(-32600, 'Wallet "w1" already has set CoinJoin salt!', node.coinjoinsalt, 'generate')
|
|
|
|
|
|
|
|
self.log.info('"coinjoinsalt" subcommands should succeed if no balance and not mixing')
|
|
|
|
# 'coinjoinsalt generate' should return a new salt if overwrite enabled
|
|
|
|
s1 = node.coinjoinsalt('get')
|
|
|
|
assert_equal(node.coinjoinsalt('generate', True), True)
|
|
|
|
s2 = node.coinjoinsalt('get')
|
|
|
|
assert s1 != s2
|
|
|
|
|
|
|
|
# 'coinjoinsalt get' should fetch newly generated value (i.e. new salt should persist)
|
|
|
|
node.unloadwallet('w1')
|
|
|
|
node.loadwallet('w1')
|
|
|
|
node = self.nodes[0].get_wallet_rpc('w1')
|
|
|
|
assert_equal(s2, node.coinjoinsalt('get'))
|
|
|
|
|
|
|
|
# 'coinjoinsalt set' should work with random hashes
|
|
|
|
s1 = uint256_to_string(random.getrandbits(256))
|
|
|
|
node.coinjoinsalt('set', s1)
|
|
|
|
assert_equal(s1, node.coinjoinsalt('get'))
|
|
|
|
assert s1 != s2
|
|
|
|
|
|
|
|
# 'coinjoinsalt set' shouldn't work with nonsense values
|
|
|
|
s2 = format(0, '064x')
|
|
|
|
assert_raises_rpc_error(-8, "Invalid CoinJoin salt value", node.coinjoinsalt, 'set', s2, True)
|
|
|
|
s2 = s2[0:63] + 'h'
|
|
|
|
assert_raises_rpc_error(-8, "salt must be hexadecimal string (not '%s')" % s2, node.coinjoinsalt, 'set', s2, True)
|
|
|
|
|
|
|
|
self.log.info('"coinjoinsalt generate" and "coinjoinsalt set" should fail if mixing')
|
|
|
|
# Start mix session
|
|
|
|
node.coinjoin('start')
|
|
|
|
assert_equal(node.getcoinjoininfo()['running'], True)
|
|
|
|
|
|
|
|
# 'coinjoinsalt generate' and 'coinjoinsalt set' should fail when mixing
|
|
|
|
assert_raises_rpc_error(-4, 'Wallet "w1" is currently mixing, cannot change salt!', node.coinjoinsalt, 'generate', True)
|
|
|
|
assert_raises_rpc_error(-4, 'Wallet "w1" is currently mixing, cannot change salt!', node.coinjoinsalt, 'set', s1, True)
|
|
|
|
|
|
|
|
# 'coinjoinsalt get' should still work
|
|
|
|
assert_equal(node.coinjoinsalt('get'), s1)
|
|
|
|
|
|
|
|
# Stop mix session
|
|
|
|
node.coinjoin('stop')
|
|
|
|
assert_equal(node.getcoinjoininfo()['running'], False)
|
|
|
|
|
|
|
|
# 'coinjoinsalt generate' and 'coinjoinsalt set' should start working again
|
|
|
|
assert_equal(node.coinjoinsalt('generate', True), True)
|
|
|
|
assert_equal(node.coinjoinsalt('set', s1, True), True)
|
|
|
|
|
|
|
|
def test_coinjoinsalt_disabled(self, node):
|
|
|
|
self.log.info('"coinjoinsalt" subcommands should fail if private keys disabled')
|
|
|
|
for subcommand in ['generate', 'get']:
|
|
|
|
assert_raises_rpc_error(-32600, 'Wallet "w2" has private keys disabled, cannot perform CoinJoin!', node.coinjoinsalt, subcommand)
|
|
|
|
s1 = uint256_to_string(random.getrandbits(256))
|
|
|
|
assert_raises_rpc_error(-32600, 'Wallet "w2" has private keys disabled, cannot perform CoinJoin!', node.coinjoinsalt, 'set', s1)
|
|
|
|
|
2021-03-17 23:36:11 +01:00
|
|
|
if __name__ == '__main__':
|
|
|
|
CoinJoinTest().main()
|