mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
privatesend|wallet|qt: Improve calculations in CreateDenominated/MakeCollateralAmounts (#3657)
* wallet: Add m_dust_feerate to CCoinControl / Use it in CreateTransaction * privatesend: Introduce CTransactionBuilder/CTransactionBuilderOutput Builder classes for transactions from type Inputs: Defined by CompactTallyItem Outputs: Simple outputs with lose reserve keys This takes fully takes care of fee calculations and makes sure calculations are the same like those happening when actually create the transaction with CreateTransaction. * privatesend: Improve amount/fee calculation in CreateDenominated * privatesend: Improve amount/fee calculation in MakeCollateralAmounts * qt: Fix decomposeTransaction's MakeCollateralAmounts detection Align it with the three cases in CPrivateSendClientSession::MakeCollateralAmounts * Refactor GetFee The fee rate is always coinControl.m_feerate, also it's not used outside so should be a private method * Simplify nBytesOutput calculations * Drop unused GetBytesOutput() * Make Clear(), GetBytesTotal() and GetAmountUsed() private * Drop unused GetCoinControl() * Make ToString() const * Refactor `CTransactionBuilder::Commit()` * Reorder cases in decomposeTransaction * Fix "finished" conditions in CreateDenominated * Fix typo * wallet|privatesend: Refactor CCoinControl's m_dust_feerate -> m_discard_feerate * privatesend: Drop unused member CTransactionBuilder::dustFeeRate * privatesend: Improve CTransactionBuilder's readability * privatesend: Make the static CTransactionBuilder::GetAmountLeft private * wallet: Recover previous code style * Update src/privatesend/privatesend-util.cpp Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com> * Tweak CTransactionBuilder to respect potential compact size diffs * Tweak GetFee param type * Trivial log/comments tweaks * privatesend: Fix countPossibleOutputs - Fix off by one issue - Respect max outputs threshold * privatesend: Use GetSizeOfCompactSizeDiff in CTransactionBuilder * Apply suggestions from code review Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> * privatesend: Rename TryAdd to CouldAdd in CTransactionBuilder * wallet: Reset m_discard_feerate in CCoinControl::SetNull * Respect `-paytxfee` and `settxfee` * privatesend: Check for denominated amount only where really required * qt: Remove obsolete IsCollateralAmount() checks * privatesend: Don't accept negative amounts in CTransactionBuilder * privatesend: Remove unused CConnman parameter * use emplace_back instead of push_back Signed-off-by: pasta <pasta@dashboost.org> * fix typos Signed-off-by: pasta <pasta@dashboost.org> * make GetAmount const Signed-off-by: pasta <pasta@dashboost.org> * privatesend: Explicit capture __func__ in needMoreOutputs lambda * privatesend: Update CTransactionBuilder::UpdateAmount * remove const on parameter in declaration Signed-off-by: pasta <pasta@dashboost.org> * handle unsigned int -> int conversions a bit better Signed-off-by: pasta <pasta@dashboost.org> * explicitly cast to int from unsigned int. estimateSmartFee handles it if negative Signed-off-by: pasta <pasta@dashboost.org> * Make CTransactionBuilderOutput::GetScript const Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com> * privatesend: Update comments to follow doxygen * privatesend: Add class descriptions Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com> Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> Co-authored-by: pasta <pasta@dashboost.org>
This commit is contained in:
parent
ba2dcb0399
commit
9c988d5b5a
@ -859,12 +859,12 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool
|
||||
// there are funds to denominate and denominated balance does not exceed
|
||||
// max amount to mix yet.
|
||||
if (nBalanceAnonimizableNonDenom >= nValueMin + CPrivateSend::GetCollateralAmount() && nBalanceToDenominate > 0) {
|
||||
CreateDenominated(nBalanceToDenominate, connman);
|
||||
CreateDenominated(nBalanceToDenominate);
|
||||
}
|
||||
|
||||
//check if we have the collateral sized inputs
|
||||
if (!GetWallets()[0]->HasCollateralInputs()) {
|
||||
return !GetWallets()[0]->HasCollateralInputs(false) && MakeCollateralAmounts(connman);
|
||||
return !GetWallets()[0]->HasCollateralInputs(false) && MakeCollateralAmounts();
|
||||
}
|
||||
|
||||
if (nSessionID) {
|
||||
@ -1321,7 +1321,7 @@ bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds
|
||||
}
|
||||
|
||||
// Create collaterals by looping through inputs grouped by addresses
|
||||
bool CPrivateSendClientSession::MakeCollateralAmounts(CConnman& connman)
|
||||
bool CPrivateSendClientSession::MakeCollateralAmounts()
|
||||
{
|
||||
if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false;
|
||||
|
||||
@ -1345,13 +1345,13 @@ bool CPrivateSendClientSession::MakeCollateralAmounts(CConnman& connman)
|
||||
|
||||
// First try to use only non-denominated funds
|
||||
for (const auto& item : vecTally) {
|
||||
if (!MakeCollateralAmounts(item, false, connman)) continue;
|
||||
if (!MakeCollateralAmounts(item, false)) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
// There should be at least some denominated funds we should be able to break in pieces to continue mixing
|
||||
for (const auto& item : vecTally) {
|
||||
if (!MakeCollateralAmounts(item, true, connman)) continue;
|
||||
if (!MakeCollateralAmounts(item, true)) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1361,7 +1361,7 @@ bool CPrivateSendClientSession::MakeCollateralAmounts(CConnman& connman)
|
||||
}
|
||||
|
||||
// Split up large inputs or create fee sized inputs
|
||||
bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman)
|
||||
bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(mempool.cs);
|
||||
@ -1369,8 +1369,8 @@ bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& ta
|
||||
|
||||
if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false;
|
||||
|
||||
// Skip way too tiny amounts
|
||||
if (tallyItem.nAmount < CPrivateSend::GetCollateralAmount()) {
|
||||
// Denominated input is always a single one, so we can check its amount directly and return early
|
||||
if (!fTryDenominated && tallyItem.vecOutPoints.size() == 1 && CPrivateSend::IsDenominatedAmount(tallyItem.nAmount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1379,88 +1379,77 @@ bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& ta
|
||||
return false;
|
||||
}
|
||||
|
||||
// denominated input is always a single one, so we can check its amount directly and return early
|
||||
if (!fTryDenominated && tallyItem.vecOutPoints.size() == 1 && CPrivateSend::IsDenominatedAmount(tallyItem.nAmount)) {
|
||||
CTransactionBuilder txBuilder(GetWallets()[0], tallyItem);
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Start %s\n", __func__, txBuilder.ToString());
|
||||
|
||||
// Skip way too tiny amounts. Smallest we want is minimum collateral amount in a one output tx
|
||||
if (!txBuilder.CouldAddOutput(CPrivateSend::GetCollateralAmount())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CWalletTx wtx;
|
||||
CAmount nFeeRet = 0;
|
||||
int nChangePosRet = -1;
|
||||
std::string strFail = "";
|
||||
std::vector<CRecipient> vecSend;
|
||||
int nCase{0}; // Just for debug logs
|
||||
if (txBuilder.CouldAddOutputs({CPrivateSend::GetMaxCollateralAmount(), CPrivateSend::GetCollateralAmount()})) {
|
||||
nCase = 1;
|
||||
// <case1>, see TransactionRecord::decomposeTransaction
|
||||
// Out1 == CPrivateSend::GetMaxCollateralAmount()
|
||||
// Out2 >= CPrivateSend::GetCollateralAmount()
|
||||
|
||||
// make our collateral address
|
||||
CReserveKey reservekeyCollateral(GetWallets()[0]);
|
||||
// make our change address
|
||||
CReserveKey reservekeyChange(GetWallets()[0]);
|
||||
txBuilder.AddOutput(CPrivateSend::GetMaxCollateralAmount());
|
||||
// Note, here we first add a zero amount output to get the remainder after all fees and then assign it
|
||||
CTransactionBuilderOutput* out = txBuilder.AddOutput();
|
||||
CAmount nAmountLeft = txBuilder.GetAmountLeft();
|
||||
// If remainder is denominated add one duff to the fee
|
||||
out->UpdateAmount(CPrivateSend::IsDenominatedAmount(nAmountLeft) ? nAmountLeft - 1 : nAmountLeft);
|
||||
|
||||
CScript scriptCollateral;
|
||||
CPubKey vchPubKey;
|
||||
assert(reservekeyCollateral.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked
|
||||
scriptCollateral = GetScriptForDestination(vchPubKey.GetID());
|
||||
} else if (txBuilder.CouldAddOutputs({CPrivateSend::GetCollateralAmount(), CPrivateSend::GetCollateralAmount()})) {
|
||||
nCase = 2;
|
||||
// <case2>, see TransactionRecord::decomposeTransaction
|
||||
// Out1 CPrivateSend::IsCollateralAmount()
|
||||
// Out2 CPrivateSend::IsCollateralAmount()
|
||||
|
||||
CAmount nCollateralAmount{0};
|
||||
if (tallyItem.nAmount > CPrivateSend::GetMaxCollateralAmount() + CPrivateSend::GetCollateralAmount()*2) {
|
||||
// Change output will be large enough to be valid as a collateral or a source input for another run
|
||||
nCollateralAmount = CPrivateSend::GetMaxCollateralAmount();
|
||||
} else {
|
||||
// Change output might be too small for another collateral if we try to create the largest collateral
|
||||
// here, create a slightly smaller one instead
|
||||
nCollateralAmount = CPrivateSend::GetMaxCollateralAmount() - CPrivateSend::GetCollateralAmount();
|
||||
}
|
||||
vecSend.push_back((CRecipient){scriptCollateral, nCollateralAmount, false});
|
||||
// First add two outputs to get the available value after all fees
|
||||
CTransactionBuilderOutput* out1 = txBuilder.AddOutput();
|
||||
CTransactionBuilderOutput* out2 = txBuilder.AddOutput();
|
||||
|
||||
// try to use non-denominated and not mn-like funds first, select them explicitly
|
||||
CCoinControl coinControl;
|
||||
coinControl.fAllowOtherInputs = false;
|
||||
coinControl.fAllowWatchOnly = false;
|
||||
coinControl.nCoinType = CoinType::ONLY_NONDENOMINATED;
|
||||
// send change to the same address so that we were able create more denoms out of it later
|
||||
coinControl.destChange = tallyItem.txdest;
|
||||
for (const auto& outpoint : tallyItem.vecOutPoints) {
|
||||
coinControl.Select(outpoint);
|
||||
// Create two equal outputs from the available value. This adds one duff to the fee if txBuilder.GetAmountLeft() is odd.
|
||||
CAmount nAmountOutputs = txBuilder.GetAmountLeft() / 2;
|
||||
|
||||
assert(CPrivateSend::IsCollateralAmount(nAmountOutputs));
|
||||
|
||||
out1->UpdateAmount(nAmountOutputs);
|
||||
out2->UpdateAmount(nAmountOutputs);
|
||||
|
||||
} else { // still at least possible to add one CPrivateSend::GetCollateralAmount() output
|
||||
nCase = 3;
|
||||
// <case3>, see TransactionRecord::decomposeTransaction
|
||||
// Out1 CPrivateSend::IsCollateralAmount()
|
||||
// Out2 Skipped
|
||||
CTransactionBuilderOutput* out = txBuilder.AddOutput();
|
||||
out->UpdateAmount(txBuilder.GetAmountLeft());
|
||||
|
||||
assert(CPrivateSend::IsCollateralAmount(out->GetAmount()));
|
||||
}
|
||||
|
||||
bool fSuccess = GetWallets()[0]->CreateTransaction(vecSend, wtx, reservekeyChange,
|
||||
nFeeRet, nChangePosRet, strFail, coinControl);
|
||||
if (!fSuccess) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- ONLY_NONDENOMINATED: %s\n", strFail);
|
||||
// If we failed then most likely there are not enough funds on this address.
|
||||
if (fTryDenominated) {
|
||||
// Try to also use denominated coins (we can't mix denominated without collaterals anyway).
|
||||
coinControl.nCoinType = CoinType::ALL_COINS;
|
||||
if (!GetWallets()[0]->CreateTransaction(vecSend, wtx, reservekeyChange,
|
||||
nFeeRet, nChangePosRet, strFail, coinControl)) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- ALL_COINS Error: %s\n", strFail);
|
||||
reservekeyCollateral.ReturnKey();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Nothing else we can do.
|
||||
reservekeyCollateral.ReturnKey();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Done with case %d: %s\n", __func__, nCase, txBuilder.ToString());
|
||||
|
||||
reservekeyCollateral.KeepKey();
|
||||
assert(txBuilder.IsDust(txBuilder.GetAmountLeft()));
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- txid=%s\n", wtx.GetHash().GetHex());
|
||||
|
||||
// use the same nCachedLastSuccessBlock as for DS mixing to prevent race
|
||||
CValidationState state;
|
||||
if (!GetWallets()[0]->CommitTransaction(wtx, reservekeyChange, &connman, state)) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::MakeCollateralAmounts -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason());
|
||||
std::string strResult;
|
||||
if (!txBuilder.Commit(strResult)) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Commit failed: %s\n", __func__, strResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
privateSendClient.UpdatedSuccessBlock();
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- txid: %s\n", __func__, strResult);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create denominations by looping through inputs grouped by addresses
|
||||
bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, CConnman& connman)
|
||||
bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate)
|
||||
{
|
||||
if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false;
|
||||
|
||||
@ -1485,7 +1474,7 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate,
|
||||
bool fCreateMixingCollaterals = !GetWallets()[0]->HasCollateralInputs();
|
||||
|
||||
for (const auto& item : vecTally) {
|
||||
if (!CreateDenominated(nBalanceToDenominate, item, fCreateMixingCollaterals, connman)) continue;
|
||||
if (!CreateDenominated(nBalanceToDenominate, item, fCreateMixingCollaterals)) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1494,7 +1483,7 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate,
|
||||
}
|
||||
|
||||
// Create denominations
|
||||
bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman)
|
||||
bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(mempool.cs);
|
||||
@ -1502,32 +1491,24 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate,
|
||||
|
||||
if (!privateSendClient.fEnablePrivateSend || !privateSendClient.fPrivateSendRunning) return false;
|
||||
|
||||
std::vector<CRecipient> vecSend;
|
||||
CKeyHolderStorage keyHolderStorageDenom;
|
||||
// denominated input is always a single one, so we can check its amount directly and return early
|
||||
if (tallyItem.vecOutPoints.size() == 1 && CPrivateSend::IsDenominatedAmount(tallyItem.nAmount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CCoinControl coinControl;
|
||||
// Every input will require at least this much fees in duffs
|
||||
const CAmount nInputFee = GetMinimumFee(148, coinControl, ::mempool, ::feeEstimator, nullptr /* feeCalc */);
|
||||
// Every output will require at least this much fees in duffs
|
||||
const CAmount nOutputFee = GetMinimumFee(34, coinControl, ::mempool, ::feeEstimator, nullptr /* feeCalc */);
|
||||
CTransactionBuilder txBuilder(GetWallets()[0], tallyItem);
|
||||
|
||||
CAmount nValueLeft = tallyItem.nAmount;
|
||||
// Leave some room for fees, assuming we are going to spend all the outpoints
|
||||
nValueLeft -= tallyItem.vecOutPoints.size() * nInputFee;
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- 0 - %s nValueLeft: %f\n", EncodeDestination(tallyItem.txdest), (float)nValueLeft / COIN);
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Start %s\n", __func__, txBuilder.ToString());
|
||||
|
||||
// ****** Add an output for mixing collaterals ************ /
|
||||
|
||||
if (fCreateMixingCollaterals) {
|
||||
CScript scriptCollateral = keyHolderStorageDenom.AddKey(GetWallets()[0]);
|
||||
vecSend.push_back((CRecipient){scriptCollateral, CPrivateSend::GetMaxCollateralAmount(), false});
|
||||
nValueLeft -= CPrivateSend::GetMaxCollateralAmount() + nOutputFee;
|
||||
if (fCreateMixingCollaterals && !txBuilder.AddOutput(CPrivateSend::GetMaxCollateralAmount())) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Failed to add collateral output\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ****** Add outputs for denoms ************ /
|
||||
|
||||
int nOutputsTotal = 0;
|
||||
bool fAddFinal = true;
|
||||
std::vector<CAmount> vecStandardDenoms = CPrivateSend::GetStandardDenominations();
|
||||
|
||||
@ -1545,81 +1526,92 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate,
|
||||
// Now, in this system, so long as we don't reach PRIVATESEND_DENOM_OUTPUTS_THRESHOLD outputs the process repeats in
|
||||
// the same transaction, creating up to nPrivateSendDenomsHardCap per denomination in a single transaction.
|
||||
|
||||
while (nValueLeft >= CPrivateSend::GetSmallestDenomination() && nOutputsTotal < PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) {
|
||||
|
||||
while (txBuilder.CouldAddOutput(CPrivateSend::GetSmallestDenomination()) && txBuilder.CountOutputs() < PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) {
|
||||
for (auto it = vecStandardDenoms.rbegin(); it != vecStandardDenoms.rend(); ++it) {
|
||||
CAmount nDenomValue = *it;
|
||||
auto currentDenomIt = mapDenomCount.find(nDenomValue);
|
||||
|
||||
int nOutputs = 0;
|
||||
|
||||
const auto& strFunc = __func__;
|
||||
auto needMoreOutputs = [&]() {
|
||||
bool fRegular = ((nValueLeft >= nDenomValue + nOutputFee) && nBalanceToDenominate >= nDenomValue);
|
||||
bool fFinal = (fAddFinal
|
||||
&& nValueLeft >= nDenomValue + nOutputFee
|
||||
&& nBalanceToDenominate > 0
|
||||
&& nBalanceToDenominate < nDenomValue);
|
||||
if (fFinal) {
|
||||
fAddFinal = false; // add final denom only once, only the smalest possible one
|
||||
LogPrint(BCLog::PRIVATESEND,
|
||||
"CPrivateSendClientSession::CreateDenominated -- 1 - FINAL - nDenomValue: %f, nValueLeft: %f, nBalanceToDenominate: %f\n",
|
||||
(float) nDenomValue / COIN, (float) nValueLeft / COIN, (float) nBalanceToDenominate / COIN);
|
||||
if (txBuilder.CouldAddOutput(nDenomValue)) {
|
||||
if (fAddFinal && nBalanceToDenominate > 0 && nBalanceToDenominate < nDenomValue) {
|
||||
fAddFinal = false; // add final denom only once, only the smalest possible one
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - FINAL - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
|
||||
strFunc, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
|
||||
return true;
|
||||
} else if (nBalanceToDenominate >= nDenomValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return fRegular || fFinal;
|
||||
return false;
|
||||
};
|
||||
|
||||
// add each output up to 11 times or until it can't be added again or until we reach nPrivateSendDenomsGoal
|
||||
while (needMoreOutputs() && nOutputs <= 10 && currentDenomIt->second < privateSendClient.nPrivateSendDenomsGoal) {
|
||||
CScript scriptDenom = keyHolderStorageDenom.AddKey(GetWallets()[0]);
|
||||
// Add output and subtract denomination amount
|
||||
if (txBuilder.AddOutput(nDenomValue)) {
|
||||
++nOutputs;
|
||||
++currentDenomIt->second;
|
||||
nBalanceToDenominate -= nDenomValue;
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
|
||||
__func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
|
||||
} else {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - Error: AddOutput failed for nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
|
||||
__func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
vecSend.push_back((CRecipient) {scriptDenom, nDenomValue, false});
|
||||
|
||||
// increment outputs and subtract denomination amount
|
||||
nOutputs++;
|
||||
currentDenomIt->second++;
|
||||
nValueLeft -= nDenomValue + nOutputFee;
|
||||
nBalanceToDenominate -= nDenomValue;
|
||||
LogPrint(BCLog::PRIVATESEND,
|
||||
"CPrivateSendClientSession::CreateDenominated -- 1 - nDenomValue: %f, totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f, nBalanceToDenominate: %f\n",
|
||||
(float) nDenomValue / COIN, nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float) nValueLeft / COIN, (float) nBalanceToDenominate / COIN);
|
||||
}
|
||||
|
||||
nOutputsTotal += nOutputs;
|
||||
if (nValueLeft == 0 || nBalanceToDenominate <= 0) break;
|
||||
if (txBuilder.GetAmountLeft() == 0 || nBalanceToDenominate <= 0) break;
|
||||
}
|
||||
|
||||
bool finished = true;
|
||||
for (const auto it : mapDenomCount) {
|
||||
// Check if this specific denom could use another loop, check that there aren't nPrivateSendDenomsGoal of this
|
||||
// denom and that our nValueLeft/nBalanceToDenominate is enough to create one of these denoms, if so, loop again.
|
||||
if (it.second < privateSendClient.nPrivateSendDenomsGoal && (nValueLeft >= it.first + nOutputFee) && nBalanceToDenominate > 0) {
|
||||
if (it.second < privateSendClient.nPrivateSendDenomsGoal && txBuilder.CouldAddOutput(it.first) && nBalanceToDenominate > 0) {
|
||||
finished = false;
|
||||
LogPrint(BCLog::PRIVATESEND,
|
||||
"CPrivateSendClientSession::CreateDenominated -- 1 - NOT finished - nDenomValue: %f, count: %d, nValueLeft: %f, nBalanceToDenominate: %f\n",
|
||||
(float) it.first / COIN, it.second, (float) nValueLeft / COIN, (float) nBalanceToDenominate / COIN);
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - NOT finished - nDenomValue: %f, count: %d, nBalanceToDenominate: %f, %s\n",
|
||||
__func__, (float) it.first / COIN, it.second, (float) nBalanceToDenominate / COIN, txBuilder.ToString());
|
||||
break;
|
||||
}
|
||||
LogPrint(BCLog::PRIVATESEND,
|
||||
"CPrivateSendClientSession::CreateDenominated -- 1 - FINSHED - nDenomValue: %f, count: %d, nValueLeft: %f, nBalanceToDenominate: %f\n",
|
||||
(float) it.first / COIN, it.second, (float) nValueLeft / COIN, (float) nBalanceToDenominate / COIN);
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 1 - FINSHED - nDenomValue: %f, count: %d, nBalanceToDenominate: %f, %s\n",
|
||||
__func__, (float) it.first / COIN, it.second, (float) nBalanceToDenominate / COIN, txBuilder.ToString());
|
||||
}
|
||||
|
||||
if (finished) break;
|
||||
}
|
||||
|
||||
// Now that nPrivateSendDenomsGoal worth of each denom have been created or the max number of denoms given the value of the input, do something with the remainder.
|
||||
if ((nValueLeft >= CPrivateSend::GetSmallestDenomination() + nOutputFee) && nBalanceToDenominate >= CPrivateSend::GetSmallestDenomination()
|
||||
&& nOutputsTotal < PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) {
|
||||
|
||||
if (txBuilder.CouldAddOutput(CPrivateSend::GetSmallestDenomination()) && nBalanceToDenominate >= CPrivateSend::GetSmallestDenomination() && txBuilder.CountOutputs() < PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) {
|
||||
CAmount nLargestDenomValue = vecStandardDenoms.front();
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - Process remainder: %s\n", __func__, txBuilder.ToString());
|
||||
|
||||
auto countPossibleOutputs = [&](CAmount nAmount) -> int {
|
||||
std::vector<CAmount> vecOutputs;
|
||||
while (true) {
|
||||
// Create an potential output
|
||||
vecOutputs.push_back(nAmount);
|
||||
if (!txBuilder.CouldAddOutputs(vecOutputs) || txBuilder.CountOutputs() + vecOutputs.size() > PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) {
|
||||
// If its not possible to add it due to insufficient amount left or total number of outputs exceeds
|
||||
// PRIVATESEND_DENOM_OUTPUTS_THRESHOLD drop the output again and stop trying.
|
||||
vecOutputs.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return static_cast<int>(vecOutputs.size());
|
||||
};
|
||||
|
||||
// Go big to small
|
||||
for (auto nDenomValue : vecStandardDenoms) {
|
||||
int nOutputs = 0;
|
||||
|
||||
// Number of denoms we can create given our denom and the amount of funds we have left
|
||||
int denomsToCreateValue = nValueLeft / (nDenomValue + nOutputFee);
|
||||
int denomsToCreateValue = countPossibleOutputs(nDenomValue);
|
||||
// Prefer overshooting the targed balance by larger denoms (hence `+1`) instead of a more
|
||||
// accurate approximation by many smaller denoms. This is ok because when we get here we
|
||||
// should have nPrivateSendDenomsGoal of each smaller denom already. Also, without `+1`
|
||||
@ -1630,78 +1622,51 @@ bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate,
|
||||
int denomsToCreateBal = (nBalanceToDenominate / nDenomValue) + 1;
|
||||
// Use the smaller value
|
||||
int denomsToCreate = denomsToCreateValue > denomsToCreateBal ? denomsToCreateBal : denomsToCreateValue;
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - nBalanceToDenominate: %f, nDenomValue: %f, denomsToCreateValue: %d, denomsToCreateBal: %d\n",
|
||||
__func__, (float) nBalanceToDenominate / COIN, (float) nDenomValue / COIN, denomsToCreateValue, denomsToCreateBal);
|
||||
auto it = mapDenomCount.find(nDenomValue);
|
||||
for (int i = 0; i < denomsToCreate; i++) {
|
||||
// Never go above the cap unless it's the largest denom
|
||||
if (nDenomValue != nLargestDenomValue && it->second >= privateSendClient.nPrivateSendDenomsHardCap) break;
|
||||
|
||||
CScript scriptDenom = keyHolderStorageDenom.AddKey(GetWallets()[0]);
|
||||
vecSend.push_back((CRecipient) {scriptDenom, nDenomValue, false});
|
||||
|
||||
// increment outputs and subtract denomination amount
|
||||
nOutputs++;
|
||||
it->second++;
|
||||
nValueLeft -= nDenomValue + nOutputFee;
|
||||
nBalanceToDenominate -= nDenomValue;
|
||||
LogPrint(BCLog::PRIVATESEND,
|
||||
"CPrivateSendClientSession::CreateDenominated -- 2 - nDenomValue: %f, totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f, nBalanceToDenominate: %f\n",
|
||||
(float) nDenomValue / COIN, nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float) nValueLeft / COIN, (float) nBalanceToDenominate / COIN);
|
||||
if (nOutputs + nOutputsTotal >= PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) break;
|
||||
// Increment helpers, add output and subtract denomination amount
|
||||
if (txBuilder.AddOutput(nDenomValue)) {
|
||||
nOutputs++;
|
||||
it->second++;
|
||||
nBalanceToDenominate -= nDenomValue;
|
||||
} else {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - Error: AddOutput failed at %d/%d, %s\n", __func__, i + 1, denomsToCreate, txBuilder.ToString());
|
||||
break;
|
||||
}
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 2 - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
|
||||
__func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
|
||||
if (txBuilder.CountOutputs() >= PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) break;
|
||||
}
|
||||
nOutputsTotal += nOutputs;
|
||||
if (nOutputsTotal >= PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) break;
|
||||
if (txBuilder.CountOutputs() >= PRIVATESEND_DENOM_OUTPUTS_THRESHOLD) break;
|
||||
}
|
||||
}
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- 3 - nOutputsTotal: %d, nValueLeft: %f, nBalanceToDenominate: %f\n",
|
||||
nOutputsTotal, (float)nValueLeft / COIN, (float)nBalanceToDenominate / COIN);
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 3 - nBalanceToDenominate: %f, %s\n", __func__, (float) nBalanceToDenominate / COIN, txBuilder.ToString());
|
||||
|
||||
for (const auto it : mapDenomCount) {
|
||||
LogPrint(BCLog::PRIVATESEND,
|
||||
"CPrivateSendClientSession::CreateDenominated -- 3 - DONE - nDenomValue: %f, count: %d\n",
|
||||
(float) it.first / COIN, it.second);
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- 3 - DONE - nDenomValue: %f, count: %d\n", __func__, (float) it.first / COIN, it.second);
|
||||
}
|
||||
|
||||
// No reasons to create mixing collaterals if we can't create denoms to mix
|
||||
if (nOutputsTotal == 0) return false;
|
||||
|
||||
// if we have anything left over, it will be automatically send back as change - there is no need to send it manually
|
||||
|
||||
coinControl.fAllowOtherInputs = false;
|
||||
coinControl.fAllowWatchOnly = false;
|
||||
coinControl.nCoinType = CoinType::ONLY_NONDENOMINATED;
|
||||
// send change to the same address so that we were able create more denoms out of it later
|
||||
coinControl.destChange = tallyItem.txdest;
|
||||
for (const auto& outpoint : tallyItem.vecOutPoints) {
|
||||
coinControl.Select(outpoint);
|
||||
}
|
||||
|
||||
CWalletTx wtx;
|
||||
CAmount nFeeRet = 0;
|
||||
int nChangePosRet = -1;
|
||||
std::string strFail = "";
|
||||
// make our change address
|
||||
CReserveKey reservekeyChange(GetWallets()[0]);
|
||||
|
||||
bool fSuccess = GetWallets()[0]->CreateTransaction(vecSend, wtx, reservekeyChange,
|
||||
nFeeRet, nChangePosRet, strFail, coinControl);
|
||||
if (!fSuccess) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- Error: %s\n", strFail);
|
||||
keyHolderStorageDenom.ReturnAll();
|
||||
if ((fCreateMixingCollaterals && txBuilder.CountOutputs() == 1) || txBuilder.CountOutputs() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keyHolderStorageDenom.KeepAll();
|
||||
|
||||
CValidationState state;
|
||||
if (!GetWallets()[0]->CommitTransaction(wtx, reservekeyChange, &connman, state)) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason());
|
||||
std::string strResult;
|
||||
if (!txBuilder.Commit(strResult)) {
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- Commit failed: %s\n", __func__, strResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
// use the same nCachedLastSuccessBlock as for DS mixing to prevent race
|
||||
privateSendClient.UpdatedSuccessBlock();
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex());
|
||||
|
||||
LogPrint(BCLog::PRIVATESEND, "CPrivateSendClientSession::%s -- txid: %s\n", __func__, strResult);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -111,12 +111,12 @@ private:
|
||||
CKeyHolderStorage keyHolderStorage; // storage for keys used in PrepareDenominate
|
||||
|
||||
/// Create denominations
|
||||
bool CreateDenominated(CAmount nBalanceToDenominate, CConnman& connman);
|
||||
bool CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman);
|
||||
bool CreateDenominated(CAmount nBalanceToDenominate);
|
||||
bool CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals);
|
||||
|
||||
/// Split up large inputs or make fee sized inputs
|
||||
bool MakeCollateralAmounts(CConnman& connman);
|
||||
bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman);
|
||||
bool MakeCollateralAmounts();
|
||||
bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated);
|
||||
|
||||
bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman);
|
||||
bool StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman);
|
||||
|
@ -2,7 +2,19 @@
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <consensus/validation.h>
|
||||
#include <policy/fees.h>
|
||||
#include <policy/policy.h>
|
||||
#include <privatesend/privatesend-util.h>
|
||||
#include <script/sign.h>
|
||||
#include <validation.h>
|
||||
#include <wallet/fees.h>
|
||||
|
||||
inline unsigned int GetSizeOfCompactSizeDiff(uint64_t nSizePrev, uint64_t nSizeNew)
|
||||
{
|
||||
assert(nSizePrev <= nSizeNew);
|
||||
return ::GetSizeOfCompactSize(nSizeNew) - ::GetSizeOfCompactSize(nSizePrev);
|
||||
}
|
||||
|
||||
CKeyHolder::CKeyHolder(CWallet* pwallet) :
|
||||
reserveKey(pwallet)
|
||||
@ -70,3 +82,248 @@ void CKeyHolderStorage::ReturnAll()
|
||||
LogPrintf("CKeyHolderStorage::%s -- %lld keys returned\n", __func__, tmp.size());
|
||||
}
|
||||
}
|
||||
|
||||
CTransactionBuilderOutput::CTransactionBuilderOutput(CTransactionBuilder* pTxBuilderIn, CWallet* pwalletIn, CAmount nAmountIn) :
|
||||
pTxBuilder(pTxBuilderIn),
|
||||
key(pwalletIn),
|
||||
nAmount(nAmountIn)
|
||||
{
|
||||
assert(pTxBuilder);
|
||||
CPubKey pubKey;
|
||||
key.GetReservedKey(pubKey, false);
|
||||
script = ::GetScriptForDestination(pubKey.GetID());
|
||||
}
|
||||
|
||||
bool CTransactionBuilderOutput::UpdateAmount(const CAmount nNewAmount)
|
||||
{
|
||||
LOCK(pTxBuilder->cs_outputs);
|
||||
if (nNewAmount <= 0 || nNewAmount - nAmount > pTxBuilder->GetAmountLeft()) {
|
||||
return false;
|
||||
}
|
||||
nAmount = nNewAmount;
|
||||
return true;
|
||||
}
|
||||
|
||||
CTransactionBuilder::CTransactionBuilder(CWallet* pwalletIn, const CompactTallyItem& tallyItemIn) :
|
||||
pwallet(pwalletIn),
|
||||
dummyReserveKey(pwalletIn),
|
||||
tallyItem(tallyItemIn)
|
||||
{
|
||||
// Generate a feerate which will be used to consider if the remainder is dust and will go into fees or not
|
||||
coinControl.m_discard_feerate = ::GetDiscardRate(::feeEstimator);
|
||||
// Generate a feerate which will be used by calculations of this class and also by CWallet::CreateTransaction
|
||||
coinControl.m_feerate = std::max(::feeEstimator.estimateSmartFee((int)::nTxConfirmTarget, nullptr, true), payTxFee);
|
||||
// Change always goes back to origin
|
||||
coinControl.destChange = tallyItemIn.txdest;
|
||||
// Only allow tallyItems inputs for tx creation
|
||||
coinControl.fAllowOtherInputs = false;
|
||||
// Select all tallyItem outputs in the coinControl so that CreateTransaction knows what to use
|
||||
for (const auto& outpoint : tallyItem.vecOutPoints) {
|
||||
coinControl.Select(outpoint);
|
||||
}
|
||||
// Create dummy tx to calculate the exact required fees upfront for accurate amount and fee calculations
|
||||
CMutableTransaction dummyTx;
|
||||
// Get a comparable dummy scriptPubKey
|
||||
CTransactionBuilderOutput dummyOutput(this, pwallet, 0);
|
||||
CScript dummyScript = dummyOutput.GetScript();
|
||||
dummyOutput.ReturnKey();
|
||||
// And create dummy signatures for all inputs
|
||||
SignatureData dummySignature;
|
||||
ProduceSignature(DummySignatureCreator(pwallet), dummyScript, dummySignature);
|
||||
for (auto out : tallyItem.vecOutPoints) {
|
||||
dummyTx.vin.emplace_back(out, dummySignature.scriptSig);
|
||||
}
|
||||
// Calculate required bytes for the dummy tx with tallyItem's inputs only
|
||||
nBytesBase = ::GetSerializeSize(dummyTx, SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Calculate the output size
|
||||
nBytesOutput = ::GetSerializeSize(CTxOut(0, dummyScript), SER_NETWORK, PROTOCOL_VERSION);
|
||||
// Just to make sure..
|
||||
Clear();
|
||||
}
|
||||
|
||||
CTransactionBuilder::~CTransactionBuilder()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void CTransactionBuilder::Clear()
|
||||
{
|
||||
std::vector<std::unique_ptr<CTransactionBuilderOutput>> vecOutputsTmp;
|
||||
{
|
||||
// Don't hold cs_outputs while clearing the outputs which might indirectly call lock cs_wallet
|
||||
LOCK(cs_outputs);
|
||||
std::swap(vecOutputs, vecOutputsTmp);
|
||||
vecOutputs.clear();
|
||||
}
|
||||
|
||||
for (auto& key : vecOutputsTmp) {
|
||||
if (fKeepKeys) {
|
||||
key->KeepKey();
|
||||
} else {
|
||||
key->ReturnKey();
|
||||
}
|
||||
}
|
||||
// Always return this key just to make sure..
|
||||
dummyReserveKey.ReturnKey();
|
||||
}
|
||||
|
||||
bool CTransactionBuilder::CouldAddOutput(CAmount nAmountOutput) const
|
||||
{
|
||||
if (nAmountOutput < 0) {
|
||||
return false;
|
||||
}
|
||||
// Adding another output can change the serialized size of the vout size hence + GetSizeOfCompactSizeDiff()
|
||||
unsigned int nBytes = GetBytesTotal() + nBytesOutput + GetSizeOfCompactSizeDiff(1);
|
||||
return GetAmountLeft(GetAmountInitial(), GetAmountUsed() + nAmountOutput, GetFee(nBytes)) >= 0;
|
||||
}
|
||||
|
||||
bool CTransactionBuilder::CouldAddOutputs(const std::vector<CAmount>& vecOutputAmounts) const
|
||||
{
|
||||
CAmount nAmountAdditional{0};
|
||||
assert(vecOutputAmounts.size() < INT_MAX);
|
||||
int nBytesAdditional = nBytesOutput * (int)vecOutputAmounts.size();
|
||||
for (const auto nAmountOutput : vecOutputAmounts) {
|
||||
if (nAmountOutput < 0) {
|
||||
return false;
|
||||
}
|
||||
nAmountAdditional += nAmountOutput;
|
||||
}
|
||||
// Adding other outputs can change the serialized size of the vout size hence + GetSizeOfCompactSizeDiff()
|
||||
unsigned int nBytes = GetBytesTotal() + nBytesAdditional + GetSizeOfCompactSizeDiff(vecOutputAmounts.size());
|
||||
return GetAmountLeft(GetAmountInitial(), GetAmountUsed() + nAmountAdditional, GetFee(nBytes)) >= 0;
|
||||
}
|
||||
|
||||
CTransactionBuilderOutput* CTransactionBuilder::AddOutput(CAmount nAmountOutput)
|
||||
{
|
||||
LOCK(cs_outputs);
|
||||
if (CouldAddOutput(nAmountOutput)) {
|
||||
vecOutputs.push_back(std::make_unique<CTransactionBuilderOutput>(this, pwallet, nAmountOutput));
|
||||
return vecOutputs.back().get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned int CTransactionBuilder::GetBytesTotal() const
|
||||
{
|
||||
// Adding other outputs can change the serialized size of the vout size hence + GetSizeOfCompactSizeDiff()
|
||||
return nBytesBase + vecOutputs.size() * nBytesOutput + ::GetSizeOfCompactSizeDiff(0, vecOutputs.size());
|
||||
}
|
||||
|
||||
CAmount CTransactionBuilder::GetAmountLeft(const CAmount nAmountInitial, const CAmount nAmountUsed, const CAmount nFee)
|
||||
{
|
||||
return nAmountInitial - nAmountUsed - nFee;
|
||||
}
|
||||
|
||||
CAmount CTransactionBuilder::GetAmountUsed() const
|
||||
{
|
||||
CAmount nAmountUsed{0};
|
||||
for (const auto& out : vecOutputs) {
|
||||
nAmountUsed += out->GetAmount();
|
||||
}
|
||||
return nAmountUsed;
|
||||
}
|
||||
|
||||
CAmount CTransactionBuilder::GetFee(unsigned int nBytes) const
|
||||
{
|
||||
CAmount nFeeCalc = coinControl.m_feerate->GetFee(nBytes);
|
||||
CAmount nRequiredFee = GetRequiredFee(nBytes);
|
||||
if (nRequiredFee > nFeeCalc) {
|
||||
nFeeCalc = nRequiredFee;
|
||||
}
|
||||
if (nFeeCalc > ::maxTxFee) {
|
||||
nFeeCalc = ::maxTxFee;
|
||||
}
|
||||
return nFeeCalc;
|
||||
}
|
||||
|
||||
int CTransactionBuilder::GetSizeOfCompactSizeDiff(size_t nAdd) const
|
||||
{
|
||||
size_t nSize = vecOutputs.size();
|
||||
unsigned int ret = ::GetSizeOfCompactSizeDiff(nSize, nSize + nAdd);
|
||||
assert(ret <= INT_MAX);
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
bool CTransactionBuilder::IsDust(CAmount nAmount) const
|
||||
{
|
||||
return ::IsDust(CTxOut(nAmount, ::GetScriptForDestination(tallyItem.txdest)), coinControl.m_discard_feerate.get());
|
||||
}
|
||||
|
||||
bool CTransactionBuilder::Commit(std::string& strResult)
|
||||
{
|
||||
CWalletTx wtx;
|
||||
CAmount nFeeRet = 0;
|
||||
int nChangePosRet = -1;
|
||||
|
||||
// Transform the outputs to the format CWallet::CreateTransaction requires
|
||||
std::vector<CRecipient> vecSend;
|
||||
{
|
||||
LOCK(cs_outputs);
|
||||
vecSend.reserve(vecOutputs.size());
|
||||
for (const auto& out : vecOutputs) {
|
||||
vecSend.push_back((CRecipient){out->GetScript(), out->GetAmount(), false});
|
||||
}
|
||||
}
|
||||
|
||||
if (!pwallet->CreateTransaction(vecSend, wtx, dummyReserveKey, nFeeRet, nChangePosRet, strResult, coinControl)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CAmount nAmountLeft = GetAmountLeft();
|
||||
bool fDust = IsDust(nAmountLeft);
|
||||
// If there is a either remainder which is considered to be dust (will be added to fee in this case) or no amount left there should be no change output, return if there is a change output.
|
||||
if (nChangePosRet != -1 && fDust) {
|
||||
strResult = strprintf("Unexpected change output %s at position %d", wtx.tx->vout[nChangePosRet].ToString(), nChangePosRet);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is a remainder which is not considered to be dust it should end up in a change output, return if not.
|
||||
if (nChangePosRet == -1 && !fDust) {
|
||||
strResult = strprintf("Change output missing: %d", nAmountLeft);
|
||||
return false;
|
||||
}
|
||||
|
||||
CAmount nFeeAdditional{0};
|
||||
unsigned int nBytesAdditional{0};
|
||||
|
||||
if (fDust) {
|
||||
nFeeAdditional = nAmountLeft;
|
||||
} else {
|
||||
// Add a change output and GetSizeOfCompactSizeDiff(1) as another output can changes the serialized size of the vout size in CTransaction
|
||||
nBytesAdditional = nBytesOutput + GetSizeOfCompactSizeDiff(1);
|
||||
}
|
||||
|
||||
// If the calculated fee does not match the fee returned by CreateTransaction aka if this check fails something is wrong!
|
||||
CAmount nFeeCalc = GetFee(GetBytesTotal() + nBytesAdditional) + nFeeAdditional;
|
||||
if (nFeeRet != nFeeCalc) {
|
||||
strResult = strprintf("Fee validation failed -> nFeeRet: %d, nFeeCalc: %d, nFeeAdditional: %d, nBytesAdditional: %d, %s", nFeeRet, nFeeCalc, nFeeAdditional, nBytesAdditional, ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
CValidationState state;
|
||||
if (!pwallet->CommitTransaction(wtx, dummyReserveKey, g_connman.get(), state)) {
|
||||
strResult = state.GetRejectReason();
|
||||
return false;
|
||||
}
|
||||
|
||||
fKeepKeys = true;
|
||||
|
||||
strResult = wtx.GetHash().ToString();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CTransactionBuilder::ToString() const
|
||||
{
|
||||
return strprintf("CTransactionBuilder(Amount initial: %d, Amount left: %d, Bytes base: %d, Bytes output: %d, Bytes total: %d, Amount used: %d, Outputs: %d, Fee rate: %d, Discard fee rate: %d, Fee: %d)",
|
||||
GetAmountInitial(),
|
||||
GetAmountLeft(),
|
||||
nBytesBase,
|
||||
nBytesOutput,
|
||||
GetBytesTotal(),
|
||||
GetAmountUsed(),
|
||||
CountOutputs(),
|
||||
coinControl.m_feerate->GetFeePerK(),
|
||||
coinControl.m_discard_feerate->GetFeePerK(),
|
||||
GetFee(GetBytesTotal()));
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
class CTransactionBuilder;
|
||||
|
||||
class CKeyHolder
|
||||
{
|
||||
private:
|
||||
@ -34,4 +36,105 @@ public:
|
||||
void KeepAll();
|
||||
void ReturnAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Used by CTransactionBuilder to represent its transaction outputs.
|
||||
* It creates a CReserveKey for the given CWallet as destination.
|
||||
*/
|
||||
class CTransactionBuilderOutput
|
||||
{
|
||||
/// Used for amount updates
|
||||
CTransactionBuilder* pTxBuilder{nullptr};
|
||||
/// Reserve key where the amount of this output will end up
|
||||
CReserveKey key;
|
||||
/// Amount this output will receive
|
||||
CAmount nAmount{0};
|
||||
/// ScriptPubKey of this output
|
||||
CScript script;
|
||||
|
||||
public:
|
||||
CTransactionBuilderOutput(CTransactionBuilder* pTxBuilderIn, CWallet* pwalletIn, CAmount nAmountIn);
|
||||
CTransactionBuilderOutput(CTransactionBuilderOutput&&) = delete;
|
||||
CTransactionBuilderOutput& operator=(CTransactionBuilderOutput&&) = delete;
|
||||
/// Get the scriptPubKey of this output
|
||||
CScript GetScript() const { return script; }
|
||||
/// Get the amount of this output
|
||||
CAmount GetAmount() const { return nAmount; }
|
||||
/// Try update the amount of this output. Returns true if it was successful and false if not (e.g. insufficient amount left).
|
||||
bool UpdateAmount(CAmount nAmount);
|
||||
/// Tell the wallet to remove the key used by this output from the keypool
|
||||
void KeepKey() { key.KeepKey(); }
|
||||
/// Tell the wallet to return the key used by this output to the keypool
|
||||
void ReturnKey() { key.ReturnKey(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enables simple transaction generation for a given CWallet object. The resulting
|
||||
* transaction's inputs are defined by the given CompactTallyItem. The outputs are
|
||||
* defined by CTransactionBuilderOutput.
|
||||
*/
|
||||
class CTransactionBuilder
|
||||
{
|
||||
/// Wallet the transaction will be build for
|
||||
CWallet* pwallet{nullptr};
|
||||
/// See CTransactionBuilder() for initialization
|
||||
CCoinControl coinControl;
|
||||
/// Dummy since we anyway use tallyItem's destination as change destination in coincontrol.
|
||||
/// Its a member just to make sure ReturnKey can be called in destructor just in case it gets generated/kept
|
||||
/// somewhere in CWallet code.
|
||||
CReserveKey dummyReserveKey;
|
||||
/// Contains all utxos available to generate this transactions. They are all from the same address.
|
||||
CompactTallyItem tallyItem;
|
||||
/// Contains the number of bytes required for a transaction with only the inputs of tallyItems, no outputs
|
||||
int nBytesBase{0};
|
||||
/// Contains the number of bytes required to add one output
|
||||
int nBytesOutput{0};
|
||||
/// Call KeepKey for all keys in destructor if fKeepKeys is true, call ReturnKey for all key if its false.
|
||||
bool fKeepKeys{false};
|
||||
/// Protect vecOutputs
|
||||
mutable CCriticalSection cs_outputs;
|
||||
/// Contains all outputs already added to the transaction
|
||||
std::vector<std::unique_ptr<CTransactionBuilderOutput>> vecOutputs;
|
||||
/// Needed by CTransactionBuilderOutput::UpdateAmount to lock cs_outputs
|
||||
friend class CTransactionBuilderOutput;
|
||||
|
||||
public:
|
||||
CTransactionBuilder(CWallet* pwalletIn, const CompactTallyItem& tallyItemIn);
|
||||
~CTransactionBuilder();
|
||||
/// Check it would be possible to add a single output with the amount nAmount. Returns true if its possible and false if not.
|
||||
bool CouldAddOutput(CAmount nAmountOutput) const;
|
||||
/// Check if its possible to add multiple outputs as vector of amounts. Returns true if its possible to add all of them and false if not.
|
||||
bool CouldAddOutputs(const std::vector<CAmount>& vecOutputAmounts) const;
|
||||
/// Add an output with the amount nAmount. Returns a pointer to the output if it could be added and nullptr if not due to insufficient amount left.
|
||||
CTransactionBuilderOutput* AddOutput(CAmount nAmountOutput = 0);
|
||||
/// Get amount we had available when we started
|
||||
CAmount GetAmountInitial() const { return tallyItem.nAmount; }
|
||||
/// Get the amount currently left to add more outputs. Does respect fees.
|
||||
CAmount GetAmountLeft() const { return GetAmountInitial() - GetAmountUsed() - GetFee(GetBytesTotal()); }
|
||||
/// Check if an amounts should be considered as dust
|
||||
bool IsDust(CAmount nAmount) const;
|
||||
/// Get the total number of added outputs
|
||||
int CountOutputs() const { return vecOutputs.size(); }
|
||||
/// Create and Commit the transaction to the wallet
|
||||
bool Commit(std::string& strResult);
|
||||
/// Convert to a string
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
/// Clear the output vector and keep/return the included keys depending on the value of fKeepKeys
|
||||
void Clear();
|
||||
/// Get the total number of bytes used already by this transaction
|
||||
unsigned int GetBytesTotal() const;
|
||||
/// Helper to calculate static amount left by simply subtracting an used amount and a fee from a provided initial amount.
|
||||
static CAmount GetAmountLeft(CAmount nAmountInitial, CAmount nAmountUsed, CAmount nFee);
|
||||
/// Get the amount currently used by added outputs. Does not include fees.
|
||||
CAmount GetAmountUsed() const;
|
||||
/// Get fees based on the number of bytes and the feerate set in CoinControl.
|
||||
/// NOTE: To get the total transaction fee this should only be called once with the total number of bytes for the transaction to avoid
|
||||
/// calling CFeeRate::GetFee multiple times with subtotals as this may add rounding errors with each further call.
|
||||
CAmount GetFee(unsigned int nBytes) const;
|
||||
/// Helper to get GetSizeOfCompactSizeDiff(vecOutputs.size(), vecOutputs.size() + nAdd)
|
||||
int GetSizeOfCompactSizeDiff(size_t nAdd) const;
|
||||
};
|
||||
|
||||
#endif //PRIVATESENDUTIL_H
|
||||
|
@ -152,13 +152,16 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
|
||||
} else {
|
||||
bool fMakeCollateral{false};
|
||||
if (wtx.tx->vout.size() == 2) {
|
||||
CAmount nMaxCollateralAmount = CPrivateSend::GetMaxCollateralAmount();
|
||||
CAmount nPreMaxCollateralAmount = nMaxCollateralAmount - CPrivateSend::GetCollateralAmount();
|
||||
fMakeCollateral =
|
||||
wtx.tx->vout[0].nValue == nMaxCollateralAmount ||
|
||||
wtx.tx->vout[1].nValue == nMaxCollateralAmount ||
|
||||
(wtx.tx->vout[0].nValue == nPreMaxCollateralAmount && CPrivateSend::IsCollateralAmount(wtx.tx->vout[1].nValue)) ||
|
||||
(wtx.tx->vout[1].nValue == nPreMaxCollateralAmount && CPrivateSend::IsCollateralAmount(wtx.tx->vout[0].nValue));
|
||||
CAmount nAmount0 = wtx.tx->vout[0].nValue;
|
||||
CAmount nAmount1 = wtx.tx->vout[1].nValue;
|
||||
// <case1>, see CPrivateSendClientSession::MakeCollateralAmounts
|
||||
fMakeCollateral = (nAmount0 == CPrivateSend::GetMaxCollateralAmount() && !CPrivateSend::IsDenominatedAmount(nAmount1) && nAmount1 >= CPrivateSend::GetCollateralAmount()) ||
|
||||
(nAmount1 == CPrivateSend::GetMaxCollateralAmount() && !CPrivateSend::IsDenominatedAmount(nAmount0) && nAmount0 >= CPrivateSend::GetCollateralAmount()) ||
|
||||
// <case2>, see CPrivateSendClientSession::MakeCollateralAmounts
|
||||
(nAmount0 == nAmount1 && CPrivateSend::IsCollateralAmount(nAmount0));
|
||||
} else if (wtx.tx->vout.size() == 1) {
|
||||
// <case3>, see CPrivateSendClientSession::MakeCollateralAmounts
|
||||
fMakeCollateral = CPrivateSend::IsCollateralAmount(wtx.tx->vout[0].nValue);
|
||||
}
|
||||
if (fMakeCollateral) {
|
||||
sub.type = TransactionRecord::PrivateSendMakeCollaterals;
|
||||
|
@ -39,6 +39,8 @@ public:
|
||||
bool fOverrideFeeRate;
|
||||
//! Override the default payTxFee if set
|
||||
boost::optional<CFeeRate> m_feerate;
|
||||
//! Override the discard feerate estimation with m_discard_feerate in CreateTransaction if set
|
||||
boost::optional<CFeeRate> m_discard_feerate;
|
||||
//! Override the default confirmation target if set
|
||||
boost::optional<unsigned int> m_confirm_target;
|
||||
//! Fee estimation mode to control arguments to estimateSmartFee
|
||||
@ -59,6 +61,7 @@ public:
|
||||
fAllowWatchOnly = false;
|
||||
setSelected.clear();
|
||||
m_feerate.reset();
|
||||
m_discard_feerate.reset();
|
||||
fOverrideFeeRate = false;
|
||||
m_confirm_target.reset();
|
||||
m_fee_mode = FeeEstimateMode::UNSET;
|
||||
|
@ -3716,6 +3716,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
|
||||
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
|
||||
FeeCalculation feeCalc;
|
||||
CFeeRate discard_rate = coin_control.m_discard_feerate ? *coin_control.m_discard_feerate : GetDiscardRate(::feeEstimator);
|
||||
CAmount nFeeNeeded;
|
||||
unsigned int nBytes;
|
||||
{
|
||||
@ -3758,7 +3759,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
|
||||
CTxOut change_prototype_txout(0, scriptChange);
|
||||
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
|
||||
|
||||
CFeeRate discard_rate = GetDiscardRate(::feeEstimator);
|
||||
nFeeRet = 0;
|
||||
bool pick_new_inputs = true;
|
||||
CAmount nValueIn = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user