Skip to content

Commit 0968a00

Browse files
Merge #6492: test: add functional tests for coinjoinsalt RPC
16c2e13 test: add functional tests for `coinjoinsalt` RPC (Kittywhiskers Van Gogh) a1b256b test: extend CoinJoin RPC tests to include more cases, add logging (Kittywhiskers Van Gogh) c6dd3dd test: rename test functions to reflect RPC used, simplify them (Kittywhiskers Van Gogh) ff29c62 test: run CoinJoin RPC tests using blank wallet (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Current suite of tests do not check if restoring salt results in restoring CoinJoin balance. This is because functional tests currently do not test CoinJoin mixing (and thus, routines for the same are not currently present). ## Breaking Changes None expected. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: knst: utACK 16c2e13 UdjinM6: utACK 16c2e13 Tree-SHA512: 0ce4e67f2caf0619cae42e8158cd39fba24c0a86050e061511ea23c4c0bf34a0eede72917516b6039d7ac15f85730ab36ba9ec1c42d0eb271f6cb4341389bcec
2 parents 3d5dc16 + 16c2e13 commit 0968a00

File tree

2 files changed

+130
-37
lines changed

2 files changed

+130
-37
lines changed

test/functional/rpc_coinjoin.py

Lines changed: 130 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,26 @@
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

6-
from test_framework.test_framework import BitcoinTestFramework
7-
from test_framework.util import assert_equal
6+
import random
87

9-
'''
10-
rpc_coinjoin.py
8+
from test_framework.test_framework import BitcoinTestFramework
9+
from test_framework.messages import (
10+
COIN,
11+
MAX_MONEY,
12+
uint256_to_string,
13+
)
14+
from test_framework.util import (
15+
assert_equal,
16+
assert_is_hex_string,
17+
assert_raises_rpc_error,
18+
)
1119

12-
Tests CoinJoin basic RPC
13-
'''
20+
# See coinjoin/options.h
21+
COINJOIN_ROUNDS_DEFAULT = 4
22+
COINJOIN_ROUNDS_MAX = 16
23+
COINJOIN_ROUNDS_MIN = 2
24+
COINJOIN_TARGET_MAX = int(MAX_MONEY / COIN)
25+
COINJOIN_TARGET_MIN = 2
1426

1527
class CoinJoinTest(BitcoinTestFramework):
1628
def set_test_params(self):
@@ -19,45 +31,128 @@ def set_test_params(self):
1931
def skip_test_if_missing_module(self):
2032
self.skip_if_no_wallet()
2133

34+
def setup_nodes(self):
35+
self.add_nodes(self.num_nodes)
36+
self.start_nodes()
37+
2238
def run_test(self):
23-
self.test_coinjoin_start_stop()
24-
self.test_coinjoin_setamount()
25-
self.test_coinjoin_setrounds()
26-
27-
def test_coinjoin_start_stop(self):
28-
# Start Mixing
29-
self.nodes[0].coinjoin("start")
30-
# Get CoinJoin info
31-
cj_info = self.nodes[0].getcoinjoininfo()
32-
# Ensure that it started properly
39+
node = self.nodes[0]
40+
41+
node.createwallet(wallet_name='w1', blank=True, disable_private_keys=False)
42+
w1 = node.get_wallet_rpc('w1')
43+
self.test_salt_presence(w1)
44+
self.test_coinjoin_start_stop(w1)
45+
self.test_setcoinjoinamount(w1)
46+
self.test_setcoinjoinrounds(w1)
47+
self.test_coinjoinsalt(w1)
48+
w1.unloadwallet()
49+
50+
node.createwallet(wallet_name='w2', blank=True, disable_private_keys=True)
51+
w2 = node.get_wallet_rpc('w2')
52+
self.test_coinjoinsalt_disabled(w2)
53+
w2.unloadwallet()
54+
55+
def test_salt_presence(self, node):
56+
self.log.info('Salt should be automatically generated in new wallet')
57+
# Will raise exception if no salt generated
58+
assert_is_hex_string(node.coinjoinsalt('get'))
59+
60+
def test_coinjoin_start_stop(self, node):
61+
self.log.info('"coinjoin" subcommands should update mixing status')
62+
# Start mix session and ensure it's reported
63+
node.coinjoin('start')
64+
cj_info = node.getcoinjoininfo()
3365
assert_equal(cj_info['enabled'], True)
3466
assert_equal(cj_info['running'], True)
67+
# Repeated start should yield error
68+
assert_raises_rpc_error(-32603, 'Mixing has been started already.', node.coinjoin, 'start')
3569

36-
# Stop mixing
37-
self.nodes[0].coinjoin("stop")
38-
# Get CoinJoin info
39-
cj_info = self.nodes[0].getcoinjoininfo()
40-
# Ensure that it stopped properly
70+
# Stop mix session and ensure it's reported
71+
node.coinjoin('stop')
72+
cj_info = node.getcoinjoininfo()
4173
assert_equal(cj_info['enabled'], True)
4274
assert_equal(cj_info['running'], False)
75+
# Repeated stop should yield error
76+
assert_raises_rpc_error(-32603, 'No mix session to stop', node.coinjoin, 'stop')
77+
78+
# Reset mix session
79+
assert_equal(node.coinjoin('reset'), "Mixing was reset")
80+
81+
def test_setcoinjoinamount(self, node):
82+
self.log.info('"setcoinjoinamount" should update mixing target')
83+
# Test normal and large values
84+
for value in [COINJOIN_TARGET_MIN, 50, 1200000, COINJOIN_TARGET_MAX]:
85+
node.setcoinjoinamount(value)
86+
assert_equal(node.getcoinjoininfo()['max_amount'], value)
87+
# Test values below minimum and above maximum
88+
for value in [COINJOIN_TARGET_MIN - 1, COINJOIN_TARGET_MAX + 1]:
89+
assert_raises_rpc_error(-8, "Invalid amount of DASH as mixing goal amount", node.setcoinjoinamount, value)
90+
91+
def test_setcoinjoinrounds(self, node):
92+
self.log.info('"setcoinjoinrounds" should update mixing rounds')
93+
# Test acceptable values
94+
for value in [COINJOIN_ROUNDS_MIN, COINJOIN_ROUNDS_DEFAULT, COINJOIN_ROUNDS_MAX]:
95+
node.setcoinjoinrounds(value)
96+
assert_equal(node.getcoinjoininfo()['max_rounds'], value)
97+
# Test values below minimum and above maximum
98+
for value in [COINJOIN_ROUNDS_MIN - 1, COINJOIN_ROUNDS_MAX + 1]:
99+
assert_raises_rpc_error(-8, "Invalid number of rounds", node.setcoinjoinrounds, value)
100+
101+
def test_coinjoinsalt(self, node):
102+
self.log.info('"coinjoinsalt generate" should fail if salt already present')
103+
assert_raises_rpc_error(-32600, 'Wallet "w1" already has set CoinJoin salt!', node.coinjoinsalt, 'generate')
104+
105+
self.log.info('"coinjoinsalt" subcommands should succeed if no balance and not mixing')
106+
# 'coinjoinsalt generate' should return a new salt if overwrite enabled
107+
s1 = node.coinjoinsalt('get')
108+
assert_equal(node.coinjoinsalt('generate', True), True)
109+
s2 = node.coinjoinsalt('get')
110+
assert s1 != s2
111+
112+
# 'coinjoinsalt get' should fetch newly generated value (i.e. new salt should persist)
113+
node.unloadwallet('w1')
114+
node.loadwallet('w1')
115+
node = self.nodes[0].get_wallet_rpc('w1')
116+
assert_equal(s2, node.coinjoinsalt('get'))
117+
118+
# 'coinjoinsalt set' should work with random hashes
119+
s1 = uint256_to_string(random.getrandbits(256))
120+
node.coinjoinsalt('set', s1)
121+
assert_equal(s1, node.coinjoinsalt('get'))
122+
assert s1 != s2
123+
124+
# 'coinjoinsalt set' shouldn't work with nonsense values
125+
s2 = format(0, '064x')
126+
assert_raises_rpc_error(-8, "Invalid CoinJoin salt value", node.coinjoinsalt, 'set', s2, True)
127+
s2 = s2[0:63] + 'h'
128+
assert_raises_rpc_error(-8, "salt must be hexadecimal string (not '%s')" % s2, node.coinjoinsalt, 'set', s2, True)
129+
130+
self.log.info('"coinjoinsalt generate" and "coinjoinsalt set" should fail if mixing')
131+
# Start mix session
132+
node.coinjoin('start')
133+
assert_equal(node.getcoinjoininfo()['running'], True)
134+
135+
# 'coinjoinsalt generate' and 'coinjoinsalt set' should fail when mixing
136+
assert_raises_rpc_error(-4, 'Wallet "w1" is currently mixing, cannot change salt!', node.coinjoinsalt, 'generate', True)
137+
assert_raises_rpc_error(-4, 'Wallet "w1" is currently mixing, cannot change salt!', node.coinjoinsalt, 'set', s1, True)
43138

44-
def test_coinjoin_setamount(self):
45-
# Try normal values
46-
self.nodes[0].setcoinjoinamount(50)
47-
cj_info = self.nodes[0].getcoinjoininfo()
48-
assert_equal(cj_info['max_amount'], 50)
139+
# 'coinjoinsalt get' should still work
140+
assert_equal(node.coinjoinsalt('get'), s1)
49141

50-
# Try large values
51-
self.nodes[0].setcoinjoinamount(1200000)
52-
cj_info = self.nodes[0].getcoinjoininfo()
53-
assert_equal(cj_info['max_amount'], 1200000)
142+
# Stop mix session
143+
node.coinjoin('stop')
144+
assert_equal(node.getcoinjoininfo()['running'], False)
54145

55-
def test_coinjoin_setrounds(self):
56-
# Try normal values
57-
self.nodes[0].setcoinjoinrounds(5)
58-
cj_info = self.nodes[0].getcoinjoininfo()
59-
assert_equal(cj_info['max_rounds'], 5)
146+
# 'coinjoinsalt generate' and 'coinjoinsalt set' should start working again
147+
assert_equal(node.coinjoinsalt('generate', True), True)
148+
assert_equal(node.coinjoinsalt('set', s1, True), True)
60149

150+
def test_coinjoinsalt_disabled(self, node):
151+
self.log.info('"coinjoinsalt" subcommands should fail if private keys disabled')
152+
for subcommand in ['generate', 'get']:
153+
assert_raises_rpc_error(-32600, 'Wallet "w2" has private keys disabled, cannot perform CoinJoin!', node.coinjoinsalt, subcommand)
154+
s1 = uint256_to_string(random.getrandbits(256))
155+
assert_raises_rpc_error(-32600, 'Wallet "w2" has private keys disabled, cannot perform CoinJoin!', node.coinjoinsalt, 'set', s1)
61156

62157
if __name__ == '__main__':
63158
CoinJoinTest().main()

test/functional/test_runner.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,8 +892,6 @@ def _get_uncovered_rpc_commands(self):
892892
# Consider RPC generate covered, because it is overloaded in
893893
# test_framework/test_node.py and not seen by the coverage check.
894894
covered_cmds = set({'generate'})
895-
# TODO: implement functional tests for coinjoinsalt
896-
covered_cmds.add('coinjoinsalt')
897895
# TODO: implement functional tests for voteraw
898896
covered_cmds.add('voteraw')
899897
# TODO: implement functional tests for getmerkleblocks

0 commit comments

Comments
 (0)