From 6bc85e803ffa48efd7b11a8bb20a3d2f6ba7660b Mon Sep 17 00:00:00 2001 From: Levon Petrosyan Date: Mon, 18 Jan 2021 04:17:36 +0400 Subject: [PATCH] JSplit fee estimation and calculation optimized --- src/qt/coincontroldialog.cpp | 109 +++++++++++++----------- src/wallet/lelantusjoinsplitbuilder.cpp | 5 +- src/wallet/wallet.cpp | 45 +++++----- src/wallet/wallet.h | 2 +- 4 files changed, 86 insertions(+), 75 deletions(-) diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 1bad6cf932..3b60a4ffbd 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -523,62 +523,67 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog, bool a // calculation if (nQuantity > 0) { - // Bytes - nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here - if (fWitness) - { - // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. - // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. - // also, the witness stack size value value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. - nBytes += 2; // account for the serialized marker and flag bytes - nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. - } - - // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate - if (CoinControlDialog::fSubtractFeeFromAmount) - if (nAmount - nPayAmount == 0) - nBytes -= 34; - - // Fee - nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); - if (nPayFee > 0 && coinControl->nMinimumTotalFee > nPayFee) - nPayFee = coinControl->nMinimumTotalFee; - - - // Allow free? (require at least hard-coded threshold and default to that if no estimate) - double mempoolEstimatePriority = mempool.estimateSmartPriority(nTxConfirmTarget); - dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority) - double dPriorityNeeded = std::max(mempoolEstimatePriority, AllowFreeThreshold()); - fAllowFree = (dPriority >= dPriorityNeeded); - - if (fSendFreeTransactions) - if (fAllowFree && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) - nPayFee = 0; - - if (nPayAmount > 0) - { - nChange = nAmount - nPayAmount; - if (!CoinControlDialog::fSubtractFeeFromAmount) - nChange -= nPayFee; - - // Never create dust outputs; if we would, just add the dust to the fee. - if (nChange > 0 && nChange < MIN_CHANGE) + if(anonymousMode){ + std::tie(nPayFee, nBytes) = model->getWallet()->EstimateJoinSplitFee(nPayAmount,CoinControlDialog::fSubtractFeeFromAmount, coinControl); + if (nPayAmount > 0) { + nChange = nAmount - nPayAmount; + if (!CoinControlDialog::fSubtractFeeFromAmount) + nChange -= nPayFee; + } + } else { + // Bytes + nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + if (fWitness) { - CTxOut txout(nChange, (CScript)std::vector(24, 0)); - if (txout.IsDust(dustRelayFee)) - { - if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust - nChange = txout.GetDustThreshold(dustRelayFee); - else - { - nPayFee += nChange; - nChange = 0; + // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. + // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. + // also, the witness stack size value value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. + nBytes += 2; // account for the serialized marker and flag bytes + nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. + } + + // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate + if (CoinControlDialog::fSubtractFeeFromAmount) + if (nAmount - nPayAmount == 0) + nBytes -= 34; + + // Fee + nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); + if (nPayFee > 0 && coinControl->nMinimumTotalFee > nPayFee) + nPayFee = coinControl->nMinimumTotalFee; + + // Allow free? (require at least hard-coded threshold and default to that if no estimate) + double mempoolEstimatePriority = mempool.estimateSmartPriority(nTxConfirmTarget); + dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * + 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority) + double dPriorityNeeded = std::max(mempoolEstimatePriority, AllowFreeThreshold()); + fAllowFree = (dPriority >= dPriorityNeeded); + + if (fSendFreeTransactions) + if (fAllowFree && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) + nPayFee = 0; + + if (nPayAmount > 0) { + nChange = nAmount - nPayAmount; + if (!CoinControlDialog::fSubtractFeeFromAmount) + nChange -= nPayFee; + + // Never create dust outputs; if we would, just add the dust to the fee. + if (nChange > 0 && nChange < MIN_CHANGE) { + CTxOut txout(nChange, (CScript) std::vector(24, 0)); + if (txout.IsDust(dustRelayFee)) { + if (CoinControlDialog::fSubtractFeeFromAmount) // dust-change will be raised until no dust + nChange = txout.GetDustThreshold(dustRelayFee); + else { + nPayFee += nChange; + nChange = 0; + } } } - } - if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount) - nBytes -= 34; + if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount) + nBytes -= 34; + } } // after fee diff --git a/src/wallet/lelantusjoinsplitbuilder.cpp b/src/wallet/lelantusjoinsplitbuilder.cpp index 55633d3e88..98644597f5 100644 --- a/src/wallet/lelantusjoinsplitbuilder.cpp +++ b/src/wallet/lelantusjoinsplitbuilder.cpp @@ -119,7 +119,10 @@ CWalletTx LelantusJoinSplitBuilder::Build( nCountNextUse = pwalletMain->zwallet->GetCount(); } - for (fee = payTxFee.GetFeePerK();;) { + std::tie(fee, std::ignore) = wallet.EstimateJoinSplitFee(vOut + mint, recipientsToSubtractFee, coinControl); + + + for (;;) { // In case of not enough fee, reset mint seed counter if (pwalletMain->zwallet) { pwalletMain->zwallet->SetCount(nCountNextUse); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b7fc2bfe1b..5d748f4969 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -6844,19 +6844,24 @@ CWalletTx CWallet::CreateLelantusJoinSplitTransaction( return tx; } -CAmount CWallet::EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmount, const CCoinControl *coinControl) { +std::pair CWallet::EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmount, const CCoinControl *coinControl) { CAmount fee; - + unsigned size; std::vector spendCoins; std::vector sigmaSpendCoins; + std::list coins = this->GetAvailableCoins(coinControl, false, true); + CAmount availableSigmaBalance(0); + for (auto coin : coins) { + availableSigmaBalance += coin.get_denomination_value(); + } + for (fee = payTxFee.GetFeePerK();;) { CAmount currentRequired = required; if (!subtractFeeFromAmount) currentRequired += fee; - spendCoins.clear(); sigmaSpendCoins.clear(); auto &consensusParams = Params().GetConsensus(); @@ -6864,15 +6869,10 @@ CAmount CWallet::EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmou std::vector denomChanges; try { - std::list coins = this->GetAvailableCoins(coinControl, false, true); - CAmount availableBalance(0); - for (auto coin : coins) { - availableBalance += coin.get_denomination_value(); - } - if (availableBalance > 0) { + if (availableSigmaBalance > 0) { CAmount inputFromSigma; - if (currentRequired > availableBalance) - inputFromSigma = availableBalance; + if (currentRequired > availableSigmaBalance) + inputFromSigma = availableSigmaBalance; else inputFromSigma = currentRequired; @@ -6881,20 +6881,20 @@ CAmount CWallet::EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmou consensusParams.nMaxValueLelantusSpendPerTransaction, coinControl, true); currentRequired -= inputFromSigma; } - } catch (std::runtime_error) { - } - if (currentRequired > 0) { - if (!this->GetCoinsToJoinSplit(currentRequired, spendCoins, changeToMint, - consensusParams.nMaxLelantusInputPerTransaction, - consensusParams.nMaxValueLelantusSpendPerTransaction, coinControl, true)) { - throw InsufficientFunds(); + if (currentRequired > 0) { + if (!this->GetCoinsToJoinSplit(currentRequired, spendCoins, changeToMint, + consensusParams.nMaxLelantusInputPerTransaction, + consensusParams.nMaxValueLelantusSpendPerTransaction, coinControl, true)) { + return std::make_pair(0, 0); + } } + } catch (std::runtime_error) { } - // 9560 is constant part, mainly Schnorr and Range proof, 2560 is for each sigma/aux data + // 956 is constant part, mainly Schnorr and Range proof, 2560 is for each sigma/aux data // 179 other parts of tx, assuming 1 utxo and 1 jmint - unsigned size = 956 + 2560 * (spendCoins.size() + sigmaSpendCoins.size()) + 179; + size = 956 + 2560 * (spendCoins.size() + sigmaSpendCoins.size()) + 179; CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); if (fee >= feeNeeded) { @@ -6902,9 +6902,12 @@ CAmount CWallet::EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmou } fee = feeNeeded; + + if(subtractFeeFromAmount) + break; } - return fee; + return std::make_pair(fee, size); } bool CWallet::CommitLelantusTransaction(CWalletTx& wtxNew, std::vector& spendCoins, std::vector& mintCoins) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index bf9bb703bd..4b85cc98d7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1042,7 +1042,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result); - CAmount EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmount, const CCoinControl *coinControl); + std::pair EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmount, const CCoinControl *coinControl); bool GetMint(const uint256& hashSerial, CSigmaEntry& zerocoin, bool forEstimation = false) const;