2021-12-30 19:36:57 +02:00
// Copyright (c) 2017-2021 The Bitcoin Core developers
2018-03-09 23:18:18 -05:00
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
2021-09-11 10:29:00 +08:00
# include <consensus/amount.h>
2020-04-16 13:11:54 -04:00
# include <node/context.h>
2018-05-14 09:51:03 +02:00
# include <primitives/transaction.h>
# include <random.h>
2019-11-05 15:18:59 -05:00
# include <test/util/setup_common.h>
2021-06-23 16:54:13 -04:00
# include <util/translation.h>
2020-04-16 13:11:54 -04:00
# include <wallet/coincontrol.h>
# include <wallet/coinselection.h>
2021-02-12 18:01:22 -05:00
# include <wallet/spend.h>
2018-05-14 09:51:03 +02:00
# include <wallet/test/wallet_test_fixture.h>
2020-04-16 13:11:54 -04:00
# include <wallet/wallet.h>
2018-03-09 23:18:18 -05:00
2020-11-16 14:31:45 -05:00
# include <algorithm>
2018-03-09 23:18:18 -05:00
# include <boost/test/unit_test.hpp>
# include <random>
2021-11-12 11:13:29 -05:00
namespace wallet {
2018-03-18 18:52:30 +01:00
BOOST_FIXTURE_TEST_SUITE ( coinselector_tests , WalletTestingSetup )
2018-03-09 23:18:18 -05:00
2018-03-10 00:39:29 -05:00
// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
# define RUN_TESTS 100
// some tests fail 1% of the time due to bad luck.
// we repeat those tests this many times and only complain if all iterations of the test fail
# define RANDOM_REPEATS 5
2022-01-17 17:18:31 -05:00
typedef std : : set < COutput > CoinSet ;
2018-03-09 23:18:18 -05:00
2021-10-12 18:50:47 -04:00
static const CoinEligibilityFilter filter_standard ( 1 , 6 , 0 ) ;
static const CoinEligibilityFilter filter_confirmed ( 1 , 1 , 0 ) ;
static const CoinEligibilityFilter filter_standard_extra ( 6 , 6 , 0 ) ;
2020-11-09 17:23:57 -05:00
static int nextLockTime = 0 ;
2018-03-12 19:43:32 -04:00
2022-01-17 17:18:31 -05:00
static void add_coin ( const CAmount & nValue , int nInput , std : : vector < COutput > & set )
2018-03-09 23:18:18 -05:00
{
CMutableTransaction tx ;
tx . vout . resize ( nInput + 1 ) ;
tx . vout [ nInput ] . nValue = nValue ;
2020-11-09 17:23:57 -05:00
tx . nLockTime = nextLockTime + + ; // so all transactions get different hashes
2022-01-17 17:18:31 -05:00
set . emplace_back ( COutPoint ( tx . GetHash ( ) , nInput ) , tx . vout . at ( nInput ) , /*depth=*/ 1 , /*input_bytes=*/ - 1 , /*spendable=*/ true , /*solvable=*/ true , /*safe=*/ true , /*time=*/ 0 , /*from_me=*/ false ) ;
2018-03-09 23:18:18 -05:00
}
2021-11-23 15:46:04 -05:00
static void add_coin ( const CAmount & nValue , int nInput , SelectionResult & result )
{
CMutableTransaction tx ;
tx . vout . resize ( nInput + 1 ) ;
tx . vout [ nInput ] . nValue = nValue ;
tx . nLockTime = nextLockTime + + ; // so all transactions get different hashes
2022-01-17 17:18:31 -05:00
COutput output ( COutPoint ( tx . GetHash ( ) , nInput ) , tx . vout . at ( nInput ) , /*depth=*/ 1 , /*input_bytes=*/ - 1 , /*spendable=*/ true , /*solvable=*/ true , /*safe=*/ true , /*time=*/ 0 , /*from_me=*/ false ) ;
2021-11-23 15:46:04 -05:00
OutputGroup group ;
2022-01-17 17:18:31 -05:00
group . Insert ( output , /*ancestors=*/ 0 , /*descendants=*/ 0 , /*positive_only=*/ true ) ;
2021-11-23 15:46:04 -05:00
result . AddInput ( group ) ;
}
2021-06-11 16:59:18 -04:00
static void add_coin ( const CAmount & nValue , int nInput , CoinSet & set , CAmount fee = 0 , CAmount long_term_fee = 0 )
2018-03-09 23:18:18 -05:00
{
CMutableTransaction tx ;
tx . vout . resize ( nInput + 1 ) ;
tx . vout [ nInput ] . nValue = nValue ;
2020-11-09 17:23:57 -05:00
tx . nLockTime = nextLockTime + + ; // so all transactions get different hashes
2022-01-17 17:18:31 -05:00
COutput coin ( COutPoint ( tx . GetHash ( ) , nInput ) , tx . vout . at ( nInput ) , /*depth=*/ 1 , /*input_bytes=*/ - 1 , /*spendable=*/ true , /*solvable=*/ true , /*safe=*/ true , /*time=*/ 0 , /*from_me=*/ false ) ;
2021-06-11 16:59:18 -04:00
coin . effective_value = nValue - fee ;
2022-01-17 17:18:31 -05:00
coin . fee = fee ;
coin . long_term_fee = long_term_fee ;
2021-06-11 16:59:18 -04:00
set . insert ( coin ) ;
2018-03-09 23:18:18 -05:00
}
2021-10-12 18:50:47 -04:00
static void add_coin ( std : : vector < COutput > & coins , CWallet & wallet , const CAmount & nValue , int nAge = 6 * 24 , bool fIsFromMe = false , int nInput = 0 , bool spendable = false )
2018-03-10 00:39:29 -05:00
{
CMutableTransaction tx ;
tx . nLockTime = nextLockTime + + ; // so all transactions get different hashes
tx . vout . resize ( nInput + 1 ) ;
tx . vout [ nInput ] . nValue = nValue ;
2019-10-25 18:10:13 -04:00
if ( spendable ) {
CTxDestination dest ;
2021-06-23 16:54:13 -04:00
bilingual_str error ;
2020-12-05 10:50:20 +00:00
const bool destination_ok = wallet . GetNewDestination ( OutputType : : BECH32 , " " , dest , error ) ;
assert ( destination_ok ) ;
2019-10-25 18:10:13 -04:00
tx . vout [ nInput ] . scriptPubKey = GetScriptForDestination ( dest ) ;
}
2021-10-22 16:26:18 -04:00
uint256 txid = tx . GetHash ( ) ;
LOCK ( wallet . cs_wallet ) ;
2021-02-16 22:36:26 -05:00
auto ret = wallet . mapWallet . emplace ( std : : piecewise_construct , std : : forward_as_tuple ( txid ) , std : : forward_as_tuple ( MakeTransactionRef ( std : : move ( tx ) ) , TxStateInactive { } ) ) ;
2021-10-22 16:26:18 -04:00
assert ( ret . second ) ;
CWalletTx & wtx = ( * ret . first ) . second ;
2022-01-17 16:05:16 -05:00
coins . emplace_back ( COutPoint ( wtx . GetHash ( ) , nInput ) , wtx . tx - > vout . at ( nInput ) , nAge , GetTxSpendSize ( wallet , wtx , nInput ) , /*spendable=*/ true , /*solvable=*/ true , /*safe=*/ true , wtx . GetTxTime ( ) , fIsFromMe ) ;
2018-03-10 00:39:29 -05:00
}
2020-11-16 14:31:45 -05:00
/** Check if SelectionResult a is equivalent to SelectionResult b.
* Equivalent means same input values , but maybe different inputs ( i . e . same value , different prevout ) */
static bool EquivalentResult ( const SelectionResult & a , const SelectionResult & b )
2020-11-09 17:23:57 -05:00
{
std : : vector < CAmount > a_amts ;
std : : vector < CAmount > b_amts ;
2020-11-16 14:31:45 -05:00
for ( const auto & coin : a . GetInputSet ( ) ) {
2020-11-09 17:23:57 -05:00
a_amts . push_back ( coin . txout . nValue ) ;
}
2020-11-16 14:31:45 -05:00
for ( const auto & coin : b . GetInputSet ( ) ) {
2020-11-09 17:23:57 -05:00
b_amts . push_back ( coin . txout . nValue ) ;
}
std : : sort ( a_amts . begin ( ) , a_amts . end ( ) ) ;
std : : sort ( b_amts . begin ( ) , b_amts . end ( ) ) ;
2020-11-16 14:31:45 -05:00
std : : pair < std : : vector < CAmount > : : iterator , std : : vector < CAmount > : : iterator > ret = std : : mismatch ( a_amts . begin ( ) , a_amts . end ( ) , b_amts . begin ( ) ) ;
2020-11-09 17:23:57 -05:00
return ret . first = = a_amts . end ( ) & & ret . second = = b_amts . end ( ) ;
}
2020-11-16 15:36:47 -05:00
/** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */
static bool EqualResult ( const SelectionResult & a , const SelectionResult & b )
2018-03-09 23:18:18 -05:00
{
2022-03-23 14:38:14 -04:00
std : : pair < CoinSet : : iterator , CoinSet : : iterator > ret = std : : mismatch ( a . GetInputSet ( ) . begin ( ) , a . GetInputSet ( ) . end ( ) , b . GetInputSet ( ) . begin ( ) ,
[ ] ( const COutput & a , const COutput & b ) {
return a . outpoint = = b . outpoint ;
} ) ;
2020-11-16 15:36:47 -05:00
return ret . first = = a . GetInputSet ( ) . end ( ) & & ret . second = = b . GetInputSet ( ) . end ( ) ;
2018-03-09 23:18:18 -05:00
}
2022-01-17 17:18:31 -05:00
static CAmount make_hard_case ( int utxos , std : : vector < COutput > & utxo_pool )
2018-03-09 23:18:18 -05:00
{
utxo_pool . clear ( ) ;
CAmount target = 0 ;
for ( int i = 0 ; i < utxos ; + + i ) {
target + = ( CAmount ) 1 < < ( utxos + i ) ;
add_coin ( ( CAmount ) 1 < < ( utxos + i ) , 2 * i , utxo_pool ) ;
add_coin ( ( ( CAmount ) 1 < < ( utxos + i ) ) + ( ( CAmount ) 1 < < ( utxos - 1 - i ) ) , 2 * i + 1 , utxo_pool ) ;
}
return target ;
}
2018-07-19 11:45:26 +09:00
inline std : : vector < OutputGroup > & GroupCoins ( const std : : vector < COutput > & coins )
{
static std : : vector < OutputGroup > static_groups ;
static_groups . clear ( ) ;
2020-08-31 15:10:49 -04:00
for ( auto & coin : coins ) {
static_groups . emplace_back ( ) ;
2022-01-17 17:18:31 -05:00
static_groups . back ( ) . Insert ( coin , /*ancestors=*/ 0 , /*descendants=*/ 0 , /*positive_only=*/ false ) ;
2020-08-31 15:10:49 -04:00
}
2018-07-19 11:45:26 +09:00
return static_groups ;
}
2021-10-12 18:50:47 -04:00
inline std : : vector < OutputGroup > & KnapsackGroupOutputs ( const std : : vector < COutput > & coins , CWallet & wallet , const CoinEligibilityFilter & filter )
2021-07-02 17:28:27 -04:00
{
2022-03-14 15:22:42 +01:00
FastRandomContext rand { } ;
CoinSelectionParams coin_selection_params {
rand ,
/* change_output_size= */ 0 ,
/* change_spend_size= */ 0 ,
/* effective_feerate= */ CFeeRate ( 0 ) ,
/* long_term_feerate= */ CFeeRate ( 0 ) ,
/* discard_feerate= */ CFeeRate ( 0 ) ,
/* tx_noinputs_size= */ 0 ,
/* avoid_partial= */ false ,
} ;
2021-07-02 17:28:27 -04:00
static std : : vector < OutputGroup > static_groups ;
2021-09-14 16:56:34 +02:00
static_groups = GroupOutputs ( wallet , coins , coin_selection_params , filter , /*positive_only=*/ false ) ;
2021-07-02 17:28:27 -04:00
return static_groups ;
}
2018-03-09 23:18:18 -05:00
// Branch and bound coin selection tests
BOOST_AUTO_TEST_CASE ( bnb_search_test )
{
2022-03-14 15:22:42 +01:00
FastRandomContext rand { } ;
2018-03-09 23:18:18 -05:00
// Setup
2022-01-17 17:18:31 -05:00
std : : vector < COutput > utxo_pool ;
2020-11-16 14:48:00 -05:00
SelectionResult expected_result ( CAmount ( 0 ) ) ;
2018-03-09 23:18:18 -05:00
/////////////////////////
// Known Outcome tests //
/////////////////////////
// Empty utxo pool
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 1 * CENT , 0.5 * CENT ) ) ;
2018-03-09 23:18:18 -05:00
// Add utxos
add_coin ( 1 * CENT , 1 , utxo_pool ) ;
add_coin ( 2 * CENT , 2 , utxo_pool ) ;
add_coin ( 3 * CENT , 3 , utxo_pool ) ;
add_coin ( 4 * CENT , 4 , utxo_pool ) ;
// Select 1 Cent
2021-11-23 15:46:04 -05:00
add_coin ( 1 * CENT , 1 , expected_result ) ;
2020-11-16 14:31:45 -05:00
const auto result1 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 1 * CENT , 0.5 * CENT ) ;
BOOST_CHECK ( result1 ) ;
BOOST_CHECK ( EquivalentResult ( expected_result , * result1 ) ) ;
BOOST_CHECK_EQUAL ( result1 - > GetSelectedValue ( ) , 1 * CENT ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2018-03-09 23:18:18 -05:00
// Select 2 Cent
2021-11-23 15:46:04 -05:00
add_coin ( 2 * CENT , 2 , expected_result ) ;
2020-11-16 14:31:45 -05:00
const auto result2 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 2 * CENT , 0.5 * CENT ) ;
BOOST_CHECK ( result2 ) ;
BOOST_CHECK ( EquivalentResult ( expected_result , * result2 ) ) ;
BOOST_CHECK_EQUAL ( result2 - > GetSelectedValue ( ) , 2 * CENT ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2018-03-09 23:18:18 -05:00
// Select 5 Cent
2021-11-23 15:46:04 -05:00
add_coin ( 4 * CENT , 4 , expected_result ) ;
add_coin ( 1 * CENT , 1 , expected_result ) ;
2020-11-16 14:31:45 -05:00
const auto result3 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 5 * CENT , 0.5 * CENT ) ;
BOOST_CHECK ( result3 ) ;
BOOST_CHECK ( EquivalentResult ( expected_result , * result3 ) ) ;
BOOST_CHECK_EQUAL ( result3 - > GetSelectedValue ( ) , 5 * CENT ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2018-03-09 23:18:18 -05:00
// Select 11 Cent, not possible
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 11 * CENT , 0.5 * CENT ) ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2018-03-09 23:18:18 -05:00
2020-02-20 14:59:22 -06:00
// Cost of change is greater than the difference between target value and utxo sum
2021-11-23 15:46:04 -05:00
add_coin ( 1 * CENT , 1 , expected_result ) ;
2020-11-16 14:31:45 -05:00
const auto result4 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 0.9 * CENT , 0.5 * CENT ) ;
BOOST_CHECK ( result4 ) ;
BOOST_CHECK_EQUAL ( result4 - > GetSelectedValue ( ) , 1 * CENT ) ;
BOOST_CHECK ( EquivalentResult ( expected_result , * result4 ) ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2020-02-20 14:59:22 -06:00
// Cost of change is less than the difference between target value and utxo sum
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 0.9 * CENT , 0 ) ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2020-02-20 14:59:22 -06:00
2018-03-09 23:18:18 -05:00
// Select 10 Cent
add_coin ( 5 * CENT , 5 , utxo_pool ) ;
2021-11-23 15:46:04 -05:00
add_coin ( 5 * CENT , 5 , expected_result ) ;
add_coin ( 4 * CENT , 4 , expected_result ) ;
add_coin ( 1 * CENT , 1 , expected_result ) ;
2020-11-16 14:31:45 -05:00
const auto result5 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 10 * CENT , 0.5 * CENT ) ;
BOOST_CHECK ( result5 ) ;
BOOST_CHECK ( EquivalentResult ( expected_result , * result5 ) ) ;
BOOST_CHECK_EQUAL ( result5 - > GetSelectedValue ( ) , 10 * CENT ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2018-03-09 23:18:18 -05:00
// Negative effective value
// Select 10 Cent but have 1 Cent not be possible because too small
2021-11-23 15:46:04 -05:00
add_coin ( 5 * CENT , 5 , expected_result ) ;
add_coin ( 3 * CENT , 3 , expected_result ) ;
add_coin ( 2 * CENT , 2 , expected_result ) ;
2020-11-16 14:31:45 -05:00
const auto result6 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 10 * CENT , 5000 ) ;
BOOST_CHECK ( result6 ) ;
BOOST_CHECK_EQUAL ( result6 - > GetSelectedValue ( ) , 10 * CENT ) ;
2018-05-11 20:17:55 -07:00
// FIXME: this test is redundant with the above, because 1 Cent is selected, not "too small"
2020-11-16 14:31:45 -05:00
// BOOST_CHECK(EquivalentResult(expected_result, *result));
2018-03-09 23:18:18 -05:00
// Select 0.25 Cent, not possible
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 0.25 * CENT , 0.5 * CENT ) ) ;
2020-11-16 14:48:00 -05:00
expected_result . Clear ( ) ;
2018-03-09 23:18:18 -05:00
// Iteration exhaustion test
CAmount target = make_hard_case ( 17 , utxo_pool ) ;
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( utxo_pool ) , target , 0 ) ) ; // Should exhaust
2018-03-09 23:18:18 -05:00
target = make_hard_case ( 14 , utxo_pool ) ;
2020-11-16 14:31:45 -05:00
const auto result7 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , target , 0 ) ; // Should not exhaust
BOOST_CHECK ( result7 ) ;
2018-03-09 23:18:18 -05:00
// Test same value early bailout optimization
2018-05-11 20:17:55 -07:00
utxo_pool . clear ( ) ;
2021-11-23 15:46:04 -05:00
add_coin ( 7 * CENT , 7 , expected_result ) ;
add_coin ( 7 * CENT , 7 , expected_result ) ;
add_coin ( 7 * CENT , 7 , expected_result ) ;
add_coin ( 7 * CENT , 7 , expected_result ) ;
add_coin ( 2 * CENT , 7 , expected_result ) ;
2018-03-09 23:18:18 -05:00
add_coin ( 7 * CENT , 7 , utxo_pool ) ;
add_coin ( 7 * CENT , 7 , utxo_pool ) ;
add_coin ( 7 * CENT , 7 , utxo_pool ) ;
add_coin ( 7 * CENT , 7 , utxo_pool ) ;
add_coin ( 2 * CENT , 7 , utxo_pool ) ;
for ( int i = 0 ; i < 50000 ; + + i ) {
add_coin ( 5 * CENT , 7 , utxo_pool ) ;
}
2020-11-16 14:31:45 -05:00
const auto result8 = SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 30 * CENT , 5000 ) ;
BOOST_CHECK ( result8 ) ;
BOOST_CHECK_EQUAL ( result8 - > GetSelectedValue ( ) , 30 * CENT ) ;
BOOST_CHECK ( EquivalentResult ( expected_result , * result8 ) ) ;
2018-03-09 23:18:18 -05:00
////////////////////
// Behavior tests //
////////////////////
// Select 1 Cent with pool of only greater than 5 Cent
utxo_pool . clear ( ) ;
for ( int i = 5 ; i < = 20 ; + + i ) {
add_coin ( i * CENT , i , utxo_pool ) ;
}
// Run 100 times, to make sure it is never finding a solution
for ( int i = 0 ; i < 100 ; + + i ) {
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( utxo_pool ) , 1 * CENT , 2 * CENT ) ) ;
2018-03-09 23:18:18 -05:00
}
2021-05-20 15:21:15 -04:00
// Make sure that effective value is working in AttemptSelection when BnB is used
2022-03-14 15:22:42 +01:00
CoinSelectionParams coin_selection_params_bnb {
rand ,
/* change_output_size= */ 0 ,
/* change_spend_size= */ 0 ,
/* effective_feerate= */ CFeeRate ( 3000 ) ,
/* long_term_feerate= */ CFeeRate ( 1000 ) ,
/* discard_feerate= */ CFeeRate ( 1000 ) ,
/* tx_noinputs_size= */ 0 ,
/* avoid_partial= */ false ,
} ;
2019-10-25 18:10:13 -04:00
{
2021-09-09 07:53:16 +02:00
std : : unique_ptr < CWallet > wallet = std : : make_unique < CWallet > ( m_node . chain . get ( ) , " " , m_args , CreateMockWalletDatabase ( ) ) ;
2020-12-18 17:45:11 +01:00
wallet - > LoadWallet ( ) ;
2019-10-25 18:10:13 -04:00
LOCK ( wallet - > cs_wallet ) ;
2021-10-12 20:43:12 -04:00
wallet - > SetWalletFlag ( WALLET_FLAG_DESCRIPTORS ) ;
wallet - > SetupDescriptorScriptPubKeyMans ( ) ;
2021-10-12 18:50:47 -04:00
std : : vector < COutput > coins ;
add_coin ( coins , * wallet , 1 ) ;
2022-03-16 14:38:34 -04:00
coins . at ( 0 ) . input_bytes = 40 ; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
2020-11-16 14:31:45 -05:00
BOOST_CHECK ( ! SelectCoinsBnB ( GroupCoins ( coins ) , 1 * CENT , coin_selection_params_bnb . m_cost_of_change ) ) ;
2021-10-12 18:50:47 -04:00
// Test fees subtracted from output:
coins . clear ( ) ;
add_coin ( coins , * wallet , 1 * CENT ) ;
2022-03-16 14:38:34 -04:00
coins . at ( 0 ) . input_bytes = 40 ;
2021-10-12 18:50:47 -04:00
coin_selection_params_bnb . m_subtract_fee_outputs = true ;
2020-11-16 14:31:45 -05:00
const auto result9 = SelectCoinsBnB ( GroupCoins ( coins ) , 1 * CENT , coin_selection_params_bnb . m_cost_of_change ) ;
BOOST_CHECK ( result9 ) ;
BOOST_CHECK_EQUAL ( result9 - > GetSelectedValue ( ) , 1 * CENT ) ;
2021-10-12 18:50:47 -04:00
}
{
2021-09-09 07:53:16 +02:00
std : : unique_ptr < CWallet > wallet = std : : make_unique < CWallet > ( m_node . chain . get ( ) , " " , m_args , CreateMockWalletDatabase ( ) ) ;
2021-10-12 18:50:47 -04:00
wallet - > LoadWallet ( ) ;
LOCK ( wallet - > cs_wallet ) ;
2021-10-12 20:43:12 -04:00
wallet - > SetWalletFlag ( WALLET_FLAG_DESCRIPTORS ) ;
wallet - > SetupDescriptorScriptPubKeyMans ( ) ;
2021-10-12 18:50:47 -04:00
std : : vector < COutput > coins ;
add_coin ( coins , * wallet , 5 * CENT , 6 * 24 , false , 0 , true ) ;
add_coin ( coins , * wallet , 3 * CENT , 6 * 24 , false , 0 , true ) ;
add_coin ( coins , * wallet , 2 * CENT , 6 * 24 , false , 0 , true ) ;
2019-10-25 18:10:13 -04:00
CCoinControl coin_control ;
coin_control . fAllowOtherInputs = true ;
2022-01-18 19:08:42 -05:00
coin_control . Select ( coins . at ( 0 ) . outpoint ) ;
2021-03-16 16:19:03 -04:00
coin_selection_params_bnb . m_effective_feerate = CFeeRate ( 0 ) ;
2021-05-21 18:55:21 -04:00
const auto result10 = SelectCoins ( * wallet , coins , 10 * CENT , coin_control , coin_selection_params_bnb ) ;
BOOST_CHECK ( result10 ) ;
2019-10-25 18:10:13 -04:00
}
2018-03-12 19:43:32 -04:00
}
2018-03-10 00:39:29 -05:00
BOOST_AUTO_TEST_CASE ( knapsack_solver_test )
{
2022-03-14 15:22:42 +01:00
FastRandomContext rand { } ;
const auto temp1 { [ & rand ] ( std : : vector < OutputGroup > & g , const CAmount & v ) { return KnapsackSolver ( g , v , rand ) ; } } ;
const auto KnapsackSolver { temp1 } ;
2021-09-09 07:53:16 +02:00
std : : unique_ptr < CWallet > wallet = std : : make_unique < CWallet > ( m_node . chain . get ( ) , " " , m_args , CreateMockWalletDatabase ( ) ) ;
2021-10-12 18:50:47 -04:00
wallet - > LoadWallet ( ) ;
LOCK ( wallet - > cs_wallet ) ;
2021-10-12 20:43:12 -04:00
wallet - > SetWalletFlag ( WALLET_FLAG_DESCRIPTORS ) ;
wallet - > SetupDescriptorScriptPubKeyMans ( ) ;
2021-10-12 18:50:47 -04:00
std : : vector < COutput > coins ;
2018-03-10 00:39:29 -05:00
// test multiple times to allow for differences in the shuffle order
for ( int i = 0 ; i < RUN_TESTS ; i + + )
{
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
2018-03-10 00:39:29 -05:00
// with an empty wallet we can't even pay one cent
2020-11-16 15:36:47 -05:00
BOOST_CHECK ( ! KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard ) , 1 * CENT ) ) ;
2018-03-10 00:39:29 -05:00
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 1 * CENT , 4 ) ; // add a new 1 cent coin
2018-03-10 00:39:29 -05:00
// with a new 1 cent coin, we still can't find a mature 1 cent
2020-11-16 15:36:47 -05:00
BOOST_CHECK ( ! KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard ) , 1 * CENT ) ) ;
2018-03-10 00:39:29 -05:00
// but we can find a new 1 cent
2020-11-16 15:36:47 -05:00
const auto result1 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 1 * CENT ) ;
BOOST_CHECK ( result1 ) ;
BOOST_CHECK_EQUAL ( result1 - > GetSelectedValue ( ) , 1 * CENT ) ;
2018-03-10 00:39:29 -05:00
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 2 * CENT ) ; // add a mature 2 cent coin
2018-03-10 00:39:29 -05:00
// we can't make 3 cents of mature coins
2020-11-16 15:36:47 -05:00
BOOST_CHECK ( ! KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard ) , 3 * CENT ) ) ;
2018-03-10 00:39:29 -05:00
// we can make 3 cents of new coins
2020-11-16 15:36:47 -05:00
const auto result2 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 3 * CENT ) ;
BOOST_CHECK ( result2 ) ;
BOOST_CHECK_EQUAL ( result2 - > GetSelectedValue ( ) , 3 * CENT ) ;
2018-03-10 00:39:29 -05:00
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 5 * CENT ) ; // add a mature 5 cent coin,
add_coin ( coins , * wallet , 10 * CENT , 3 , true ) ; // a new 10 cent coin sent from one of our own addresses
add_coin ( coins , * wallet , 20 * CENT ) ; // and a mature 20 cent coin
2018-03-10 00:39:29 -05:00
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
2020-11-16 15:36:47 -05:00
BOOST_CHECK ( ! KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard ) , 38 * CENT ) ) ;
2018-03-10 00:39:29 -05:00
// we can't even make 37 cents if we don't allow new coins even if they're from us
2020-11-16 15:36:47 -05:00
BOOST_CHECK ( ! KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard_extra ) , 38 * CENT ) ) ;
2018-03-10 00:39:29 -05:00
// but we can make 37 cents if we accept new coins from ourself
2020-11-16 15:36:47 -05:00
const auto result3 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard ) , 37 * CENT ) ;
BOOST_CHECK ( result3 ) ;
BOOST_CHECK_EQUAL ( result3 - > GetSelectedValue ( ) , 37 * CENT ) ;
2018-03-10 00:39:29 -05:00
// and we can make 38 cents if we accept all new coins
2020-11-16 15:36:47 -05:00
const auto result4 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 38 * CENT ) ;
BOOST_CHECK ( result4 ) ;
BOOST_CHECK_EQUAL ( result4 - > GetSelectedValue ( ) , 38 * CENT ) ;
2018-03-10 00:39:29 -05:00
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
2020-11-16 15:36:47 -05:00
const auto result5 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 34 * CENT ) ;
BOOST_CHECK ( result5 ) ;
BOOST_CHECK_EQUAL ( result5 - > GetSelectedValue ( ) , 35 * CENT ) ; // but 35 cents is closest
BOOST_CHECK_EQUAL ( result5 - > GetInputSet ( ) . size ( ) , 3U ) ; // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
2018-03-10 00:39:29 -05:00
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
2020-11-16 15:36:47 -05:00
const auto result6 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 7 * CENT ) ;
BOOST_CHECK ( result6 ) ;
BOOST_CHECK_EQUAL ( result6 - > GetSelectedValue ( ) , 7 * CENT ) ;
BOOST_CHECK_EQUAL ( result6 - > GetInputSet ( ) . size ( ) , 2U ) ;
2018-03-10 00:39:29 -05:00
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
2020-11-16 15:36:47 -05:00
const auto result7 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 8 * CENT ) ;
BOOST_CHECK ( result7 ) ;
BOOST_CHECK ( result7 - > GetSelectedValue ( ) = = 8 * CENT ) ;
BOOST_CHECK_EQUAL ( result7 - > GetInputSet ( ) . size ( ) , 3U ) ;
2018-03-10 00:39:29 -05:00
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
2020-11-16 15:36:47 -05:00
const auto result8 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 9 * CENT ) ;
BOOST_CHECK ( result8 ) ;
BOOST_CHECK_EQUAL ( result8 - > GetSelectedValue ( ) , 10 * CENT ) ;
BOOST_CHECK_EQUAL ( result8 - > GetInputSet ( ) . size ( ) , 1U ) ;
2018-03-10 00:39:29 -05:00
// now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
2018-03-10 00:39:29 -05:00
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 6 * CENT ) ;
add_coin ( coins , * wallet , 7 * CENT ) ;
add_coin ( coins , * wallet , 8 * CENT ) ;
add_coin ( coins , * wallet , 20 * CENT ) ;
add_coin ( coins , * wallet , 30 * CENT ) ; // now we have 6+7+8+20+30 = 71 cents total
2018-03-10 00:39:29 -05:00
// check that we have 71 and not 72
2020-11-16 15:36:47 -05:00
const auto result9 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 71 * CENT ) ;
BOOST_CHECK ( result9 ) ;
BOOST_CHECK ( ! KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 72 * CENT ) ) ;
2018-03-10 00:39:29 -05:00
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
2020-11-16 15:36:47 -05:00
const auto result10 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 16 * CENT ) ;
BOOST_CHECK ( result10 ) ;
BOOST_CHECK_EQUAL ( result10 - > GetSelectedValue ( ) , 20 * CENT ) ; // we should get 20 in one coin
BOOST_CHECK_EQUAL ( result10 - > GetInputSet ( ) . size ( ) , 1U ) ;
2018-03-10 00:39:29 -05:00
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 5 * CENT ) ; // now we have 5+6+7+8+20+30 = 75 cents total
2018-03-10 00:39:29 -05:00
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
2020-11-16 15:36:47 -05:00
const auto result11 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 16 * CENT ) ;
BOOST_CHECK ( result11 ) ;
BOOST_CHECK_EQUAL ( result11 - > GetSelectedValue ( ) , 18 * CENT ) ; // we should get 18 in 3 coins
BOOST_CHECK_EQUAL ( result11 - > GetInputSet ( ) . size ( ) , 3U ) ;
2018-03-10 00:39:29 -05:00
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 18 * CENT ) ; // now we have 5+6+7+8+18+20+30
2018-03-10 00:39:29 -05:00
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
2020-11-16 15:36:47 -05:00
const auto result12 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 16 * CENT ) ;
BOOST_CHECK ( result12 ) ;
BOOST_CHECK_EQUAL ( result12 - > GetSelectedValue ( ) , 18 * CENT ) ; // we should get 18 in 1 coin
BOOST_CHECK_EQUAL ( result12 - > GetInputSet ( ) . size ( ) , 1U ) ; // because in the event of a tie, the biggest coin wins
2018-03-10 00:39:29 -05:00
// now try making 11 cents. we should get 5+6
2020-11-16 15:36:47 -05:00
const auto result13 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 11 * CENT ) ;
BOOST_CHECK ( result13 ) ;
BOOST_CHECK_EQUAL ( result13 - > GetSelectedValue ( ) , 11 * CENT ) ;
BOOST_CHECK_EQUAL ( result13 - > GetInputSet ( ) . size ( ) , 2U ) ;
2018-03-10 00:39:29 -05:00
// check that the smallest bigger coin is used
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 1 * COIN ) ;
add_coin ( coins , * wallet , 2 * COIN ) ;
add_coin ( coins , * wallet , 3 * COIN ) ;
add_coin ( coins , * wallet , 4 * COIN ) ; // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
2020-11-16 15:36:47 -05:00
const auto result14 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 95 * CENT ) ;
BOOST_CHECK ( result14 ) ;
BOOST_CHECK_EQUAL ( result14 - > GetSelectedValue ( ) , 1 * COIN ) ; // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL ( result14 - > GetInputSet ( ) . size ( ) , 1U ) ;
2018-03-10 00:39:29 -05:00
2020-11-16 15:36:47 -05:00
const auto result15 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 195 * CENT ) ;
BOOST_CHECK ( result15 ) ;
BOOST_CHECK_EQUAL ( result15 - > GetSelectedValue ( ) , 2 * COIN ) ; // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL ( result15 - > GetInputSet ( ) . size ( ) , 1U ) ;
2018-03-10 00:39:29 -05:00
// empty the wallet and start again, now with fractions of a cent, to test small change avoidance
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 1 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 2 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 3 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 4 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 5 / 10 ) ;
2018-03-10 00:39:29 -05:00
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
// we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
2020-11-16 15:36:47 -05:00
const auto result16 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , MIN_CHANGE ) ;
BOOST_CHECK ( result16 ) ;
BOOST_CHECK_EQUAL ( result16 - > GetSelectedValue ( ) , MIN_CHANGE ) ;
2018-03-10 00:39:29 -05:00
// but if we add a bigger coin, small change is avoided
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 1111 * MIN_CHANGE ) ;
2018-03-10 00:39:29 -05:00
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
2020-11-16 15:36:47 -05:00
const auto result17 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 1 * MIN_CHANGE ) ;
BOOST_CHECK ( result17 ) ;
BOOST_CHECK_EQUAL ( result17 - > GetSelectedValue ( ) , 1 * MIN_CHANGE ) ; // we should get the exact amount
2018-03-10 00:39:29 -05:00
// if we add more small coins:
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , MIN_CHANGE * 6 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 7 / 10 ) ;
2018-03-10 00:39:29 -05:00
// and try again to make 1.0 * MIN_CHANGE
2020-11-16 15:36:47 -05:00
const auto result18 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 1 * MIN_CHANGE ) ;
BOOST_CHECK ( result18 ) ;
BOOST_CHECK_EQUAL ( result18 - > GetSelectedValue ( ) , 1 * MIN_CHANGE ) ; // we should get the exact amount
2018-03-10 00:39:29 -05:00
2020-12-30 21:49:12 -05:00
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
2018-03-10 00:39:29 -05:00
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
2018-03-10 00:39:29 -05:00
for ( int j = 0 ; j < 20 ; j + + )
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 50000 * COIN ) ;
2018-03-10 00:39:29 -05:00
2020-11-16 15:36:47 -05:00
const auto result19 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 500000 * COIN ) ;
BOOST_CHECK ( result19 ) ;
BOOST_CHECK_EQUAL ( result19 - > GetSelectedValue ( ) , 500000 * COIN ) ; // we should get the exact amount
BOOST_CHECK_EQUAL ( result19 - > GetInputSet ( ) . size ( ) , 10U ) ; // in ten coins
2018-03-10 00:39:29 -05:00
// if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0),
// we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 5 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 6 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 7 / 10 ) ;
add_coin ( coins , * wallet , 1111 * MIN_CHANGE ) ;
2020-11-16 15:36:47 -05:00
const auto result20 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 1 * MIN_CHANGE ) ;
BOOST_CHECK ( result20 ) ;
BOOST_CHECK_EQUAL ( result20 - > GetSelectedValue ( ) , 1111 * MIN_CHANGE ) ; // we get the bigger coin
BOOST_CHECK_EQUAL ( result20 - > GetInputSet ( ) . size ( ) , 1U ) ;
2018-03-10 00:39:29 -05:00
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 4 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 6 / 10 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 8 / 10 ) ;
add_coin ( coins , * wallet , 1111 * MIN_CHANGE ) ;
2020-11-16 15:36:47 -05:00
const auto result21 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , MIN_CHANGE ) ;
BOOST_CHECK ( result21 ) ;
BOOST_CHECK_EQUAL ( result21 - > GetSelectedValue ( ) , MIN_CHANGE ) ; // we should get the exact amount
BOOST_CHECK_EQUAL ( result21 - > GetInputSet ( ) . size ( ) , 2U ) ; // in two coins 0.4+0.6
2018-03-10 00:39:29 -05:00
// test avoiding small change
2021-10-12 18:50:47 -04:00
coins . clear ( ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 5 / 100 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 1 ) ;
add_coin ( coins , * wallet , MIN_CHANGE * 100 ) ;
2018-03-10 00:39:29 -05:00
// trying to make 100.01 from these three coins
2020-11-16 15:36:47 -05:00
const auto result22 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , MIN_CHANGE * 10001 / 100 ) ;
BOOST_CHECK ( result22 ) ;
BOOST_CHECK_EQUAL ( result22 - > GetSelectedValue ( ) , MIN_CHANGE * 10105 / 100 ) ; // we should get all coins
BOOST_CHECK_EQUAL ( result22 - > GetInputSet ( ) . size ( ) , 3U ) ;
2018-03-10 00:39:29 -05:00
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
2020-11-16 15:36:47 -05:00
const auto result23 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , MIN_CHANGE * 9990 / 100 ) ;
BOOST_CHECK ( result23 ) ;
BOOST_CHECK_EQUAL ( result23 - > GetSelectedValue ( ) , 101 * MIN_CHANGE ) ;
BOOST_CHECK_EQUAL ( result23 - > GetInputSet ( ) . size ( ) , 2U ) ;
2021-10-12 18:50:47 -04:00
}
// test with many inputs
for ( CAmount amt = 1500 ; amt < COIN ; amt * = 10 ) {
coins . clear ( ) ;
// Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input)
for ( uint16_t j = 0 ; j < 676 ; j + + )
add_coin ( coins , * wallet , amt ) ;
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for ( int i = 0 ; i < RUN_TESTS ; i + + ) {
2020-11-16 15:36:47 -05:00
const auto result24 = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_confirmed ) , 2000 ) ;
BOOST_CHECK ( result24 ) ;
2021-10-12 18:50:47 -04:00
if ( amt - 2000 < MIN_CHANGE ) {
// needs more than one input:
uint16_t returnSize = std : : ceil ( ( 2000.0 + MIN_CHANGE ) / amt ) ;
CAmount returnValue = amt * returnSize ;
2020-11-16 15:36:47 -05:00
BOOST_CHECK_EQUAL ( result24 - > GetSelectedValue ( ) , returnValue ) ;
BOOST_CHECK_EQUAL ( result24 - > GetInputSet ( ) . size ( ) , returnSize ) ;
2021-10-12 18:50:47 -04:00
} else {
// one input is sufficient:
2020-11-16 15:36:47 -05:00
BOOST_CHECK_EQUAL ( result24 - > GetSelectedValue ( ) , amt ) ;
BOOST_CHECK_EQUAL ( result24 - > GetInputSet ( ) . size ( ) , 1U ) ;
2021-10-12 18:50:47 -04:00
}
}
}
// test randomness
{
coins . clear ( ) ;
for ( int i2 = 0 ; i2 < 100 ; i2 + + )
add_coin ( coins , * wallet , COIN ) ;
// Again, we only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for ( int i = 0 ; i < RUN_TESTS ; i + + ) {
2018-03-10 00:39:29 -05:00
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
2020-11-16 15:36:47 -05:00
const auto result25 = KnapsackSolver ( GroupCoins ( coins ) , 50 * COIN ) ;
BOOST_CHECK ( result25 ) ;
const auto result26 = KnapsackSolver ( GroupCoins ( coins ) , 50 * COIN ) ;
BOOST_CHECK ( result26 ) ;
BOOST_CHECK ( ! EqualResult ( * result25 , * result26 ) ) ;
2018-03-10 00:39:29 -05:00
int fails = 0 ;
for ( int j = 0 ; j < RANDOM_REPEATS ; j + + )
{
2019-10-30 16:08:55 -04:00
// Test that the KnapsackSolver selects randomly from equivalent coins (same value and same input size).
// When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice
// which will cause it to fail.
// To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail
2020-11-16 15:36:47 -05:00
const auto result27 = KnapsackSolver ( GroupCoins ( coins ) , COIN ) ;
BOOST_CHECK ( result27 ) ;
const auto result28 = KnapsackSolver ( GroupCoins ( coins ) , COIN ) ;
BOOST_CHECK ( result28 ) ;
if ( EqualResult ( * result27 , * result28 ) )
2018-03-10 00:39:29 -05:00
fails + + ;
}
BOOST_CHECK_NE ( fails , RANDOM_REPEATS ) ;
2021-10-12 18:50:47 -04:00
}
// add 75 cents in small change. not enough to make 90 cents,
// then try making 90 cents. there are multiple competing "smallest bigger" coins,
// one of which should be picked at random
add_coin ( coins , * wallet , 5 * CENT ) ;
add_coin ( coins , * wallet , 10 * CENT ) ;
add_coin ( coins , * wallet , 15 * CENT ) ;
add_coin ( coins , * wallet , 20 * CENT ) ;
add_coin ( coins , * wallet , 25 * CENT ) ;
for ( int i = 0 ; i < RUN_TESTS ; i + + ) {
2018-05-12 09:06:38 -07:00
int fails = 0 ;
2018-03-10 00:39:29 -05:00
for ( int j = 0 ; j < RANDOM_REPEATS ; j + + )
{
2020-11-16 15:36:47 -05:00
const auto result29 = KnapsackSolver ( GroupCoins ( coins ) , 90 * CENT ) ;
BOOST_CHECK ( result29 ) ;
const auto result30 = KnapsackSolver ( GroupCoins ( coins ) , 90 * CENT ) ;
BOOST_CHECK ( result30 ) ;
if ( EqualResult ( * result29 , * result30 ) )
2018-03-10 00:39:29 -05:00
fails + + ;
}
BOOST_CHECK_NE ( fails , RANDOM_REPEATS ) ;
2021-10-12 18:50:47 -04:00
}
}
2018-03-10 00:39:29 -05:00
}
BOOST_AUTO_TEST_CASE ( ApproximateBestSubset )
{
2022-03-14 15:22:42 +01:00
FastRandomContext rand { } ;
2021-09-09 07:53:16 +02:00
std : : unique_ptr < CWallet > wallet = std : : make_unique < CWallet > ( m_node . chain . get ( ) , " " , m_args , CreateMockWalletDatabase ( ) ) ;
2021-10-12 18:50:47 -04:00
wallet - > LoadWallet ( ) ;
LOCK ( wallet - > cs_wallet ) ;
2021-10-12 20:43:12 -04:00
wallet - > SetWalletFlag ( WALLET_FLAG_DESCRIPTORS ) ;
wallet - > SetupDescriptorScriptPubKeyMans ( ) ;
2021-10-12 18:50:47 -04:00
std : : vector < COutput > coins ;
2018-03-10 00:39:29 -05:00
// Test vValue sort order
for ( int i = 0 ; i < 1000 ; i + + )
2021-10-12 18:50:47 -04:00
add_coin ( coins , * wallet , 1000 * COIN ) ;
add_coin ( coins , * wallet , 3 * COIN ) ;
2018-03-10 00:39:29 -05:00
2022-03-14 15:22:42 +01:00
const auto result = KnapsackSolver ( KnapsackGroupOutputs ( coins , * wallet , filter_standard ) , 1003 * COIN , rand ) ;
2020-11-16 15:36:47 -05:00
BOOST_CHECK ( result ) ;
BOOST_CHECK_EQUAL ( result - > GetSelectedValue ( ) , 1003 * COIN ) ;
BOOST_CHECK_EQUAL ( result - > GetInputSet ( ) . size ( ) , 2U ) ;
2018-03-10 00:39:29 -05:00
}
2018-03-05 16:42:49 -05:00
// Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value
BOOST_AUTO_TEST_CASE ( SelectCoins_test )
{
2021-09-09 07:53:16 +02:00
std : : unique_ptr < CWallet > wallet = std : : make_unique < CWallet > ( m_node . chain . get ( ) , " " , m_args , CreateMockWalletDatabase ( ) ) ;
2021-10-12 18:50:47 -04:00
wallet - > LoadWallet ( ) ;
LOCK ( wallet - > cs_wallet ) ;
2021-10-12 20:43:12 -04:00
wallet - > SetWalletFlag ( WALLET_FLAG_DESCRIPTORS ) ;
wallet - > SetupDescriptorScriptPubKeyMans ( ) ;
2019-10-07 14:11:34 -04:00
2018-03-05 16:42:49 -05:00
// Random generator stuff
std : : default_random_engine generator ;
std : : exponential_distribution < double > distribution ( 100 ) ;
FastRandomContext rand ;
// Run this test 100 times
for ( int i = 0 ; i < 100 ; + + i )
{
2021-10-12 18:50:47 -04:00
std : : vector < COutput > coins ;
CAmount balance { 0 } ;
2018-03-05 16:42:49 -05:00
// Make a wallet with 1000 exponentially distributed random inputs
for ( int j = 0 ; j < 1000 ; + + j )
{
2021-10-12 18:50:47 -04:00
CAmount val = distribution ( generator ) * 10000000 ;
add_coin ( coins , * wallet , val ) ;
balance + = val ;
2018-03-05 16:42:49 -05:00
}
// Generate a random fee rate in the range of 100 - 400
CFeeRate rate ( rand . randrange ( 300 ) + 100 ) ;
// Generate a random target value between 1000 and wallet balance
2018-04-12 08:25:45 +02:00
CAmount target = rand . randrange ( balance - 1000 ) + 1000 ;
2018-03-05 16:42:49 -05:00
// Perform selection
2022-03-14 15:22:42 +01:00
CoinSelectionParams cs_params {
rand ,
/* change_output_size= */ 34 ,
/* change_spend_size= */ 148 ,
/* effective_feerate= */ CFeeRate ( 0 ) ,
/* long_term_feerate= */ CFeeRate ( 0 ) ,
/* discard_feerate= */ CFeeRate ( 0 ) ,
/* tx_noinputs_size= */ 0 ,
/* avoid_partial= */ false ,
} ;
2020-11-16 16:57:29 -05:00
CCoinControl cc ;
2021-05-21 18:55:21 -04:00
const auto result = SelectCoins ( * wallet , coins , target , cc , cs_params ) ;
BOOST_CHECK ( result ) ;
BOOST_CHECK_GE ( result - > GetSelectedValue ( ) , target ) ;
2018-03-05 16:42:49 -05:00
}
}
2021-06-11 16:59:18 -04:00
BOOST_AUTO_TEST_CASE ( waste_test )
{
CoinSet selection ;
const CAmount fee { 100 } ;
const CAmount change_cost { 125 } ;
const CAmount fee_diff { 40 } ;
const CAmount in_amt { 3 * COIN } ;
const CAmount target { 2 * COIN } ;
const CAmount excess { in_amt - fee * 2 - target } ;
// Waste with change is the change cost and difference between fee and long term fee
add_coin ( 1 * COIN , 1 , selection , fee , fee - fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee - fee_diff ) ;
const CAmount waste1 = GetSelectionWaste ( selection , change_cost , target ) ;
BOOST_CHECK_EQUAL ( fee_diff * 2 + change_cost , waste1 ) ;
selection . clear ( ) ;
// Waste without change is the excess and difference between fee and long term fee
add_coin ( 1 * COIN , 1 , selection , fee , fee - fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee - fee_diff ) ;
const CAmount waste_nochange1 = GetSelectionWaste ( selection , 0 , target ) ;
BOOST_CHECK_EQUAL ( fee_diff * 2 + excess , waste_nochange1 ) ;
selection . clear ( ) ;
// Waste with change and fee == long term fee is just cost of change
add_coin ( 1 * COIN , 1 , selection , fee , fee ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee ) ;
BOOST_CHECK_EQUAL ( change_cost , GetSelectionWaste ( selection , change_cost , target ) ) ;
selection . clear ( ) ;
// Waste without change and fee == long term fee is just the excess
add_coin ( 1 * COIN , 1 , selection , fee , fee ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee ) ;
BOOST_CHECK_EQUAL ( excess , GetSelectionWaste ( selection , 0 , target ) ) ;
selection . clear ( ) ;
// Waste will be greater when fee is greater, but long term fee is the same
add_coin ( 1 * COIN , 1 , selection , fee * 2 , fee - fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee * 2 , fee - fee_diff ) ;
const CAmount waste2 = GetSelectionWaste ( selection , change_cost , target ) ;
BOOST_CHECK_GT ( waste2 , waste1 ) ;
selection . clear ( ) ;
// Waste with change is the change cost and difference between fee and long term fee
// With long term fee greater than fee, waste should be less than when long term fee is less than fee
add_coin ( 1 * COIN , 1 , selection , fee , fee + fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee + fee_diff ) ;
const CAmount waste3 = GetSelectionWaste ( selection , change_cost , target ) ;
BOOST_CHECK_EQUAL ( fee_diff * - 2 + change_cost , waste3 ) ;
BOOST_CHECK_LT ( waste3 , waste1 ) ;
selection . clear ( ) ;
// Waste without change is the excess and difference between fee and long term fee
// With long term fee greater than fee, waste should be less than when long term fee is less than fee
add_coin ( 1 * COIN , 1 , selection , fee , fee + fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee + fee_diff ) ;
const CAmount waste_nochange2 = GetSelectionWaste ( selection , 0 , target ) ;
BOOST_CHECK_EQUAL ( fee_diff * - 2 + excess , waste_nochange2 ) ;
BOOST_CHECK_LT ( waste_nochange2 , waste_nochange1 ) ;
selection . clear ( ) ;
2021-09-10 15:47:37 +05:30
// No Waste when fee == long_term_fee, no change, and no excess
2021-06-11 16:59:18 -04:00
add_coin ( 1 * COIN , 1 , selection , fee , fee ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee ) ;
2021-09-10 15:47:37 +05:30
const CAmount exact_target { in_amt - fee * 2 } ;
2021-09-14 16:56:34 +02:00
BOOST_CHECK_EQUAL ( 0 , GetSelectionWaste ( selection , /*change_cost=*/ 0 , exact_target ) ) ;
2021-09-10 15:47:37 +05:30
selection . clear ( ) ;
2021-06-11 16:59:18 -04:00
2021-09-10 15:47:37 +05:30
// No Waste when (fee - long_term_fee) == (-cost_of_change), and no excess
const CAmount new_change_cost { fee_diff * 2 } ;
add_coin ( 1 * COIN , 1 , selection , fee , fee + fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee + fee_diff ) ;
BOOST_CHECK_EQUAL ( 0 , GetSelectionWaste ( selection , new_change_cost , target ) ) ;
selection . clear ( ) ;
// No Waste when (fee - long_term_fee) == (-excess), no change cost
const CAmount new_target { in_amt - fee * 2 - fee_diff * 2 } ;
add_coin ( 1 * COIN , 1 , selection , fee , fee + fee_diff ) ;
add_coin ( 2 * COIN , 2 , selection , fee , fee + fee_diff ) ;
BOOST_CHECK_EQUAL ( 0 , GetSelectionWaste ( selection , /* change cost */ 0 , new_target ) ) ;
2021-06-11 16:59:18 -04:00
}
2018-03-09 23:18:18 -05:00
BOOST_AUTO_TEST_SUITE_END ( )
2021-11-12 11:13:29 -05:00
} // namespace wallet