Skip to content

Commit

Permalink
More staking changes.
Browse files Browse the repository at this point in the history
Staking changes (WIP) and related wallet functionality updates
  • Loading branch information
akyo8 committed Feb 25, 2021
1 parent 5db243b commit 432ef7c
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 32 deletions.
4 changes: 2 additions & 2 deletions src/bench/coin_selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static void CoinSelection(benchmark::Bench& bench)
const CoinEligibilityFilter filter_standard(1, 6, 0);
const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0);
bench.run([&] {
std::set<COutput> setCoinsRet;
std::set<CInputCoin> setCoinsRet;
CAmount nValueRet;
bool bnb_used;
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
Expand All @@ -62,7 +62,7 @@ static void CoinSelection(benchmark::Bench& bench)
});
}

typedef std::set<COutput> CoinSet;
typedef std::set<CInputCoin> CoinSet;
static NodeContext testNode;
static auto testChain = interfaces::MakeChain(testNode);
static CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
Expand Down
1 change: 0 additions & 1 deletion src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ static bool fFeeEstimatesInitialized = false;
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
static const bool fDebug = false;

unsigned int nMinerSleep;

Expand Down
9 changes: 0 additions & 9 deletions src/policy/feerate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,7 @@

#include <tinyformat.h>

CFeeRate::CFeeRate(const CAmount& nFeePaid, size_t nBytes_)
{
assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
int64_t nSize = int64_t(nBytes_);

if (nSize > 0)
nSatoshisPerK = nFeePaid * 1000 / nSize;
else
nSatoshisPerK = 0;
}

CAmount CFeeRate::GetFee(size_t nBytes_) const
{
Expand Down
9 changes: 4 additions & 5 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct {

static const size_t TOTAL_TRIES = 100000;

bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<COutput>& out_set, CAmount& value_ret, CAmount not_input_fees)
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees)
{
out_set.clear();
CAmount curr_value = 0;
Expand Down Expand Up @@ -217,17 +217,17 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
}
}

bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<COutput>& setCoinsRet, CAmount& nValueRet)
bool KnapsackSolverStaking(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<COutput>& setCoinsRet, CAmount& nValueRet)
{
setCoinsRet.clear();
nValueRet = 0;

// List of values less than target
Optional<OutputGroup> lowest_larger;
boost::optional<OutputGroup> lowest_larger;
std::vector<OutputGroup> applicable_groups;
CAmount nTotalLower = 0;

Shuffle(groups.begin(), groups.end(), FastRandomContext());
random_shuffle(groups.begin(), groups.end(), GetRandInt);

for (const OutputGroup& group : groups) {
if (group.m_value == nTargetValue) {
Expand Down Expand Up @@ -291,7 +291,6 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
}
}

return true;
}

Expand Down
14 changes: 5 additions & 9 deletions src/wallet/test/coinselector_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
// we repeat those tests this many times and only complain if all iterations of the test fail
#define RANDOM_REPEATS 5

typedef std::set<COutput> CoinSet;
typedef std::set<CInputCoin> CoinSet;

static std::vector<COutput> vCoins;
static std::vector<CInputCoin> vCoins;
static NodeContext testNode;
static auto testChain = interfaces::MakeChain(testNode);
static CWallet testWallet(testChain.get(), "", CreateDummyWalletDatabase());
Expand All @@ -42,11 +42,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>&
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
std::unique_ptr<CWalletTx> wtx(new CWalletTx(&testWallet, MakeTransactionRef(std::move(tx))));

//COutput output(wtx.get(), nInput, 6 * 24, true /* spendable */, true /* solvable */, true /* safe */);

set.emplace(wtx.get(), nInput, 6 * 24, true /* spendable */, true /* solvable */, true /* safe */);
set.emplace(MakeTransactionRef(tx), nInput);
}

static void add_coin(const CAmount& nValue, int nInput, CoinSet& set)
Expand Down Expand Up @@ -122,7 +118,7 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins
return static_groups;
}

inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
Expand Down Expand Up @@ -297,7 +293,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true);
CCoinControl coin_control;
coin_control.fAllowOtherInputs = true;
coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
coin_control.Select(CInputCoin(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
coin_selection_params_bnb.effective_fee = CFeeRate(0);
BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used));
BOOST_CHECK(bnb_used);
Expand Down
143 changes: 137 additions & 6 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,10 @@
#include <wallet/fees.h>
#include <pow.h>
#include <pos.h>
#include <experimental/filesystem>
#include <univalue.h>

#include <algorithm>
#include <assert.h>

#include <boost/algorithm/string/replace.hpp>
#include <pos.h>
#include <experimental/filesystem>

using interfaces::FoundBlock;

Expand Down Expand Up @@ -102,6 +97,59 @@ static void UpdateWalletSetting(interfaces::Chain& chain,
}
}

bool CWallet::SelectCoinsMinConfStaking(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<COutput>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
setCoinsRet.clear();
nValueRet = 0;

std::vector<OutputGroup> utxo_pool;
if (coin_selection_params.use_bnb) {
// Get long term estimate
FeeCalculation feeCalc;
CCoinControl temp;
temp.m_confirm_target = 1008;
CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, ::mempool, ::feeEstimator, &feeCalc);

// Calculate cost of change
CAmount cost_of_change = GetDiscardRate(*this, ::feeEstimator).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);

// Filter by the min conf specs and add to utxo_pool and calculate effective value
for (OutputGroup& group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;

group.fee = 0;
group.long_term_fee = 0;
group.effective_value = 0;
for (auto it = group.m_outputs.begin(); it != group.m_outputs.end();) {
const CInputCoin& coin = *it;
CAmount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes));
// Only include outputs that are positive effective value (i.e. not dust)
if (effective_value > 0) {
group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
group.effective_value += effective_value;
++it;
} else {
it = group.Discard(coin);
}
}
if (group.effective_value > 0) utxo_pool.push_back(group);
}
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
bnb_used = true;
return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
} else {
// Filter by the min conf specs and add to utxo_pool
for (const OutputGroup& group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;
utxo_pool.push_back(group);
}
bnb_used = false;
return KnapsackSolverStaking(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
}
}

bool AddWallet(const std::shared_ptr<CWallet>& wallet)
{
LOCK(cs_wallets);
Expand Down Expand Up @@ -2829,6 +2877,89 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
std::set<CInputCoin> setPresetCoins;
CAmount nValueFromPresetInputs = 0;

std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs) {
// For now, don't use BnB if preset inputs are selected. TODO: Enable this later
bnb_used = false;
coin_selection_params.use_bnb = false;

std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
if (it != mapWallet.end()) {
const CWalletTx* pcoin = &it->second;
// Clearly invalid input, fail
if (pcoin->tx->vout.size() <= outpoint.n)
return false;
// Just to calculate the marginal byte size
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}

// remove preset inputs from vCoins
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) {
if (setPresetCoins.count(it->GetInputCoin()))
it = vCoins.erase(it);
else
++it;
}

// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
// Cases where we have 11+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
std::shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
}
std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends);

size_t max_ancestors = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT));
size_t max_descendants = (size_t)std::max<int64_t>(1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT));
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);

bool res = nTargetValue <= nValueFromPresetInputs ||
SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
(m_spend_zero_conf_change && SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors / 3), std::min((size_t)4, max_descendants / 3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors / 2, max_descendants / 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors - 1, max_descendants - 1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConfStaking(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));

// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
util::insert(setCoinsRet, setPresetCoins);

// add preset inputs to the total value selected
nValueRet += nValueFromPresetInputs;

return res;
}

bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
std::vector<COutput> vCoins(vAvailableCoins);

// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) {
// We didn't use BnB here, so set it to false.
bnb_used = false;

for (const COutput& out : vCoins) {
if (!out.fSpendable)
continue;
nValueRet += out.tx->tx->vout[out.i].nValue;
setCoinsRet.insert(out.GetInputCoin());
}
return (nValueRet >= nTargetValue);
}

// calculate value from preset inputs and store them
std::set<CInputCoin> setPresetCoins;
CAmount nValueFromPresetInputs = 0;


std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs)
Expand Down Expand Up @@ -2860,7 +2991,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
}

// remove preset inputs from vCoins
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
for (std::vector<CInputCoin>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{
if (setPresetCoins.count(it->GetInputCoin()))
it = vCoins.erase(it);
Expand Down

0 comments on commit 432ef7c

Please sign in to comment.