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
1527class 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
62157if __name__ == '__main__' :
63158 CoinJoinTest ().main ()
0 commit comments