@@ -58,15 +58,17 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
5858 result.AddInput (group);
5959}
6060
61- static void add_coin (const CAmount& nValue, int nInput, CoinSet& set , CAmount fee = 0 , CAmount long_term_fee = 0 )
61+ static void add_coin (const CAmount& nValue, int nInput, SelectionResult& result , CAmount fee, CAmount long_term_fee)
6262{
6363 CMutableTransaction tx;
6464 tx.vout .resize (nInput + 1 );
6565 tx.vout [nInput].nValue = nValue;
6666 tx.nLockTime = nextLockTime++; // so all transactions get different hashes
67- COutput coin (COutPoint (tx.GetHash (), nInput), tx.vout .at (nInput), /* depth=*/ 1 , /* input_bytes=*/ 148 , /* spendable=*/ true , /* solvable=*/ true , /* safe=*/ true , /* time=*/ 0 , /* from_me=*/ false , fee);
68- coin.long_term_fee = long_term_fee;
69- set.insert (std::make_shared<COutput>(coin));
67+ std::shared_ptr<COutput> coin = std::make_shared<COutput>(COutPoint (tx.GetHash (), nInput), tx.vout .at (nInput), /* depth=*/ 1 , /* input_bytes=*/ 148 , /* spendable=*/ true , /* solvable=*/ true , /* safe=*/ true , /* time=*/ 0 , /* from_me=*/ false , fee);
68+ OutputGroup group;
69+ group.Insert (coin, /* ancestors=*/ 0 , /* descendants=*/ 0 );
70+ coin->long_term_fee = long_term_fee; // group.Insert() will modify long_term_fee, so we need to set it afterwards
71+ result.AddInput (group);
7072}
7173
7274static void add_coin (CoinsResult& available_coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0 ), int nAge = 6*24, bool fIsFromMe = false, int nInput =0, bool spendable = false, int custom_size = 0)
@@ -827,100 +829,129 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
827829
828830BOOST_AUTO_TEST_CASE (waste_test)
829831{
830- CoinSet selection;
831832 const CAmount fee{100 };
832833 const CAmount change_cost{125 };
833834 const CAmount fee_diff{40 };
834835 const CAmount in_amt{3 * COIN};
835836 const CAmount target{2 * COIN};
836837 const CAmount excess{in_amt - fee * 2 - target};
837838
838- // Waste with change is the change cost and difference between fee and long term fee
839- add_coin (1 * COIN, 1 , selection, fee, fee - fee_diff);
840- add_coin (2 * COIN, 2 , selection, fee, fee - fee_diff);
841- const CAmount waste1 = GetSelectionWaste (selection, change_cost, target);
842- BOOST_CHECK_EQUAL (fee_diff * 2 + change_cost, waste1);
843- selection.clear ();
844-
845- // Waste without change is the excess and difference between fee and long term fee
846- add_coin (1 * COIN, 1 , selection, fee, fee - fee_diff);
847- add_coin (2 * COIN, 2 , selection, fee, fee - fee_diff);
848- const CAmount waste_nochange1 = GetSelectionWaste (selection, 0 , target);
849- BOOST_CHECK_EQUAL (fee_diff * 2 + excess, waste_nochange1);
850- selection.clear ();
851-
852- // Waste with change and fee == long term fee is just cost of change
853- add_coin (1 * COIN, 1 , selection, fee, fee);
854- add_coin (2 * COIN, 2 , selection, fee, fee);
855- BOOST_CHECK_EQUAL (change_cost, GetSelectionWaste (selection, change_cost, target));
856- selection.clear ();
857-
858- // Waste without change and fee == long term fee is just the excess
859- add_coin (1 * COIN, 1 , selection, fee, fee);
860- add_coin (2 * COIN, 2 , selection, fee, fee);
861- BOOST_CHECK_EQUAL (excess, GetSelectionWaste (selection, 0 , target));
862- selection.clear ();
863-
864- // Waste will be greater when fee is greater, but long term fee is the same
865- add_coin (1 * COIN, 1 , selection, fee * 2 , fee - fee_diff);
866- add_coin (2 * COIN, 2 , selection, fee * 2 , fee - fee_diff);
867- const CAmount waste2 = GetSelectionWaste (selection, change_cost, target);
868- BOOST_CHECK_GT (waste2, waste1);
869- selection.clear ();
870-
871- // Waste with change is the change cost and difference between fee and long term fee
872- // With long term fee greater than fee, waste should be less than when long term fee is less than fee
873- add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
874- add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
875- const CAmount waste3 = GetSelectionWaste (selection, change_cost, target);
876- BOOST_CHECK_EQUAL (fee_diff * -2 + change_cost, waste3);
877- BOOST_CHECK_LT (waste3, waste1);
878- selection.clear ();
879-
880- // Waste without change is the excess and difference between fee and long term fee
881- // With long term fee greater than fee, waste should be less than when long term fee is less than fee
882- add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
883- add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
884- const CAmount waste_nochange2 = GetSelectionWaste (selection, 0 , target);
885- BOOST_CHECK_EQUAL (fee_diff * -2 + excess, waste_nochange2);
886- BOOST_CHECK_LT (waste_nochange2, waste_nochange1);
887- selection.clear ();
888-
889- // No Waste when fee == long_term_fee, no change, and no excess
890- add_coin (1 * COIN, 1 , selection, fee, fee);
891- add_coin (2 * COIN, 2 , selection, fee, fee);
892- const CAmount exact_target{in_amt - fee * 2 };
893- BOOST_CHECK_EQUAL (0 , GetSelectionWaste (selection, /* change_cost=*/ 0 , exact_target));
894- selection.clear ();
895-
896- // No Waste when (fee - long_term_fee) == (-cost_of_change), and no excess
897- const CAmount new_change_cost{fee_diff * 2 };
898- add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
899- add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
900- BOOST_CHECK_EQUAL (0 , GetSelectionWaste (selection, new_change_cost, target));
901- selection.clear ();
902-
903- // No Waste when (fee - long_term_fee) == (-excess), no change cost
904- const CAmount new_target{in_amt - fee * 2 - fee_diff * 2 };
905- add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
906- add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
907- BOOST_CHECK_EQUAL (0 , GetSelectionWaste (selection, /* change_cost=*/ 0 , new_target));
908- selection.clear ();
909-
910- // Negative waste when the long term fee is greater than the current fee and the selected value == target
911- const CAmount exact_target1{3 * COIN - 2 * fee};
912- const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
913- add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
914- add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
915- BOOST_CHECK_EQUAL (target_waste1, GetSelectionWaste (selection, /* change_cost=*/ 0 , exact_target1));
916- selection.clear ();
917-
918- // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
919- const CAmount large_fee_diff{90 };
920- const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
921- add_coin (1 * COIN, 1 , selection, fee, fee + large_fee_diff);
922- add_coin (2 * COIN, 2 , selection, fee, fee + large_fee_diff);
923- BOOST_CHECK_EQUAL (target_waste2, GetSelectionWaste (selection, change_cost, target));
839+ // The following tests that the waste is calculated correctly in various scenarios.
840+ // ComputeAndSetWaste will first determine the size of the change output. We don't really
841+ // care about the change and just want to use the variant that always includes the change_cost,
842+ // so min_viable_change and change_fee are set to 0 to ensure that.
843+ {
844+ // Waste with change is the change cost and difference between fee and long term fee
845+ SelectionResult selection1{target, SelectionAlgorithm::MANUAL};
846+ add_coin (1 * COIN, 1 , selection1, fee, fee - fee_diff);
847+ add_coin (2 * COIN, 2 , selection1, fee, fee - fee_diff);
848+ selection1.ComputeAndSetWaste (/* min_viable_change=*/ 0 , change_cost, /* change_fee=*/ 0 );
849+ BOOST_CHECK_EQUAL (fee_diff * 2 + change_cost, selection1.GetWaste ());
850+
851+ // Waste will be greater when fee is greater, but long term fee is the same
852+ SelectionResult selection2{target, SelectionAlgorithm::MANUAL};
853+ add_coin (1 * COIN, 1 , selection2, fee * 2 , fee - fee_diff);
854+ add_coin (2 * COIN, 2 , selection2, fee * 2 , fee - fee_diff);
855+ selection2.ComputeAndSetWaste (/* min_viable_change=*/ 0 , change_cost, /* change_fee=*/ 0 );
856+ BOOST_CHECK_GT (selection2.GetWaste (), selection1.GetWaste ());
857+
858+ // Waste with change is the change cost and difference between fee and long term fee
859+ // With long term fee greater than fee, waste should be less than when long term fee is less than fee
860+ SelectionResult selection3{target, SelectionAlgorithm::MANUAL};
861+ add_coin (1 * COIN, 1 , selection3, fee, fee + fee_diff);
862+ add_coin (2 * COIN, 2 , selection3, fee, fee + fee_diff);
863+ selection3.ComputeAndSetWaste (/* min_viable_change=*/ 0 , change_cost, /* change_fee=*/ 0 );
864+ BOOST_CHECK_EQUAL (fee_diff * -2 + change_cost, selection3.GetWaste ());
865+ BOOST_CHECK_LT (selection3.GetWaste (), selection1.GetWaste ());
866+ }
867+
868+ {
869+ // Waste without change is the excess and difference between fee and long term fee
870+ SelectionResult selection_nochange1{target, SelectionAlgorithm::MANUAL};
871+ add_coin (1 * COIN, 1 , selection_nochange1, fee, fee - fee_diff);
872+ add_coin (2 * COIN, 2 , selection_nochange1, fee, fee - fee_diff);
873+ selection_nochange1.ComputeAndSetWaste (/* min_viable_change=*/ 0 , /* change_cost=*/ 0 , /* change_fee=*/ 0 );
874+ BOOST_CHECK_EQUAL (fee_diff * 2 + excess, selection_nochange1.GetWaste ());
875+
876+ // Waste without change is the excess and difference between fee and long term fee
877+ // With long term fee greater than fee, waste should be less than when long term fee is less than fee
878+ SelectionResult selection_nochange2{target, SelectionAlgorithm::MANUAL};
879+ add_coin (1 * COIN, 1 , selection_nochange2, fee, fee + fee_diff);
880+ add_coin (2 * COIN, 2 , selection_nochange2, fee, fee + fee_diff);
881+ selection_nochange2.ComputeAndSetWaste (/* min_viable_change=*/ 0 , /* change_cost=*/ 0 , /* change_fee=*/ 0 );
882+ BOOST_CHECK_EQUAL (fee_diff * -2 + excess, selection_nochange2.GetWaste ());
883+ BOOST_CHECK_LT (selection_nochange2.GetWaste (), selection_nochange1.GetWaste ());
884+ }
885+
886+ {
887+ // Waste with change and fee == long term fee is just cost of change
888+ SelectionResult selection{target, SelectionAlgorithm::MANUAL};
889+ add_coin (1 * COIN, 1 , selection, fee, fee);
890+ add_coin (2 * COIN, 2 , selection, fee, fee);
891+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , change_cost, /* change_fee=*/ 0 );
892+ BOOST_CHECK_EQUAL (change_cost, selection.GetWaste ());
893+ }
894+
895+ {
896+ // Waste without change and fee == long term fee is just the excess
897+ SelectionResult selection{target, SelectionAlgorithm::MANUAL};
898+ add_coin (1 * COIN, 1 , selection, fee, fee);
899+ add_coin (2 * COIN, 2 , selection, fee, fee);
900+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , /* change_cost=*/ 0 , /* change_fee=*/ 0 );
901+ BOOST_CHECK_EQUAL (excess, selection.GetWaste ());
902+ }
903+
904+ {
905+ // No Waste when fee == long_term_fee, no change, and no excess
906+ const CAmount exact_target{in_amt - fee * 2 };
907+ SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};
908+ add_coin (1 * COIN, 1 , selection, fee, fee);
909+ add_coin (2 * COIN, 2 , selection, fee, fee);
910+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , /* change_cost=*/ 0 , /* change_fee=*/ 0 );
911+ BOOST_CHECK_EQUAL (0 , selection.GetWaste ());
912+ }
913+
914+ {
915+ // No Waste when (fee - long_term_fee) == (-cost_of_change), and no excess
916+ SelectionResult selection{target, SelectionAlgorithm::MANUAL};
917+ const CAmount new_change_cost{fee_diff * 2 };
918+ add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
919+ add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
920+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , new_change_cost, /* change_fee=*/ 0 );
921+ BOOST_CHECK_EQUAL (0 , selection.GetWaste ());
922+ }
923+
924+ {
925+ // No Waste when (fee - long_term_fee) == (-excess), no change cost
926+ const CAmount new_target{in_amt - fee * 2 - fee_diff * 2 };
927+ SelectionResult selection{new_target, SelectionAlgorithm::MANUAL};
928+ add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
929+ add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
930+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , /* change_cost=*/ 0 , /* change_fee=*/ 0 );
931+ BOOST_CHECK_EQUAL (0 , selection.GetWaste ());
932+ }
933+
934+ {
935+ // Negative waste when the long term fee is greater than the current fee and the selected value == target
936+ const CAmount exact_target{3 * COIN - 2 * fee};
937+ SelectionResult selection{exact_target, SelectionAlgorithm::MANUAL};
938+ const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
939+ add_coin (1 * COIN, 1 , selection, fee, fee + fee_diff);
940+ add_coin (2 * COIN, 2 , selection, fee, fee + fee_diff);
941+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , /* change_cost=*/ 0 , /* change_fee=*/ 0 );
942+ BOOST_CHECK_EQUAL (target_waste1, selection.GetWaste ());
943+ }
944+
945+ {
946+ // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
947+ SelectionResult selection{target, SelectionAlgorithm::MANUAL};
948+ const CAmount large_fee_diff{90 };
949+ const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
950+ add_coin (1 * COIN, 1 , selection, fee, fee + large_fee_diff);
951+ add_coin (2 * COIN, 2 , selection, fee, fee + large_fee_diff);
952+ selection.ComputeAndSetWaste (/* min_viable_change=*/ 0 , change_cost, /* change_fee=*/ 0 );
953+ BOOST_CHECK_EQUAL (target_waste2, selection.GetWaste ());
954+ }
924955}
925956
926957BOOST_AUTO_TEST_CASE (effective_value_test)
0 commit comments