Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
595a0e1
wallet: Add m_dust_feerate to CCoinControl / Use it in CreateTransaction
xdustinface Aug 7, 2020
75196c6
privatesend: Introduce CTransactionBuilder/CTransactionBuilderOutput
xdustinface Aug 6, 2020
6a0dfa3
privatesend: Improve amount/fee calculation in CreateDenominated
xdustinface Aug 5, 2020
5705bf7
privatesend: Improve amount/fee calculation in MakeCollateralAmounts
xdustinface Aug 5, 2020
c3afce7
qt: Fix decomposeTransaction's MakeCollateralAmounts detection
xdustinface Aug 6, 2020
fdf36e6
Refactor GetFee
UdjinM6 Aug 8, 2020
580712b
Simplify nBytesOutput calculations
UdjinM6 Aug 8, 2020
97645f2
Drop unused GetBytesOutput()
UdjinM6 Aug 8, 2020
03bda06
Make Clear(), GetBytesTotal() and GetAmountUsed() private
UdjinM6 Aug 8, 2020
f1402b1
Drop unused GetCoinControl()
UdjinM6 Aug 8, 2020
c99a357
Make ToString() const
UdjinM6 Aug 8, 2020
9e4d101
Refactor `CTransactionBuilder::Commit()`
UdjinM6 Aug 8, 2020
2c05c39
Reorder cases in decomposeTransaction
UdjinM6 Aug 8, 2020
ff6f4de
Fix "finished" conditions in CreateDenominated
UdjinM6 Aug 9, 2020
24d2826
Fix typo
UdjinM6 Aug 9, 2020
3c47306
wallet|privatesend: Refactor CCoinControl's m_dust_feerate -> m_disca…
xdustinface Aug 10, 2020
8e76541
privatesend: Drop unused member CTransactionBuilder::dustFeeRate
xdustinface Aug 10, 2020
068a0f9
privatesend: Improve CTransactionBuilder's readability
xdustinface Aug 11, 2020
85ce8a0
privatesend: Make the static CTransactionBuilder::GetAmountLeft private
xdustinface Aug 11, 2020
8b04015
wallet: Recover previous code style
xdustinface Aug 11, 2020
1be40dc
Update src/privatesend/privatesend-util.cpp
xdustinface Aug 11, 2020
8ecd6c9
Tweak CTransactionBuilder to respect potential compact size diffs
UdjinM6 Aug 13, 2020
70b4a39
Tweak GetFee param type
UdjinM6 Aug 13, 2020
91b988c
Trivial log/comments tweaks
UdjinM6 Aug 13, 2020
55c6a8c
privatesend: Fix countPossibleOutputs
xdustinface Aug 14, 2020
295965e
privatesend: Use GetSizeOfCompactSizeDiff in CTransactionBuilder
xdustinface Aug 14, 2020
5009c1f
Apply suggestions from code review
xdustinface Aug 19, 2020
375522e
privatesend: Rename TryAdd to CouldAdd in CTransactionBuilder
xdustinface Aug 18, 2020
1b9eb93
wallet: Reset m_discard_feerate in CCoinControl::SetNull
xdustinface Aug 18, 2020
74afa53
Respect `-paytxfee` and `settxfee`
UdjinM6 Aug 14, 2020
1183f29
privatesend: Check for denominated amount only where really required
xdustinface Aug 24, 2020
f98740c
qt: Remove obsolete IsCollateralAmount() checks
xdustinface Aug 24, 2020
f730f97
privatesend: Don't accept negative amounts in CTransactionBuilder
xdustinface Aug 25, 2020
0e6218c
privatesend: Remove unused CConnman parameter
xdustinface Aug 26, 2020
2543a4c
use emplace_back instead of push_back
PastaPastaPasta Aug 29, 2020
bcdcb13
fix typos
PastaPastaPasta Aug 29, 2020
87b7431
make GetAmount const
PastaPastaPasta Aug 29, 2020
aacb056
privatesend: Explicit capture __func__ in needMoreOutputs lambda
xdustinface Aug 29, 2020
7814ff3
privatesend: Update CTransactionBuilder::UpdateAmount
xdustinface Aug 29, 2020
531a917
remove const on parameter in declaration
PastaPastaPasta Aug 29, 2020
a7d81d9
handle unsigned int -> int conversions a bit better
PastaPastaPasta Aug 29, 2020
f740612
explicitly cast to int from unsigned int.
PastaPastaPasta Aug 29, 2020
9a7b36a
Make CTransactionBuilderOutput::GetScript const
xdustinface Aug 30, 2020
c30df0d
privatesend: Update comments to follow doxygen
xdustinface Aug 31, 2020
fb2c6d0
privatesend: Add class descriptions
xdustinface Aug 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 144 additions & 179 deletions src/privatesend/privatesend-client.cpp

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/privatesend/privatesend-client.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,12 @@ class CPrivateSendClientSession : public CPrivateSendBaseSession
CWallet* mixingWallet;

/// 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);
Expand Down
257 changes: 257 additions & 0 deletions src/privatesend/privatesend-util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) :
Comment thread
xdustinface marked this conversation as resolved.
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
Comment thread
xdustinface marked this conversation as resolved.
{
// 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;
}
Comment on lines +212 to +215
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the point of this function at this point is it's just a glorified subtraction function...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jup its just a subtraction helper. https://github.com/dashpay/dash/blob/e7dc9da05c9e6cf6be4173ad65aecbc611cd1992/src/privatesend/privatesend-util.h#L119 imo it improves the readability of e.g.

https://github.com/dashpay/dash/blob/e7dc9da05c9e6cf6be4173ad65aecbc611cd1992/src/privatesend/privatesend-util.cpp#L169-L174

As it wraps the subtrahends in GetAmountLeft() so you can just see by reading the wrappers name what the point of the return is. I don't see any drawback by doing it like this as the compiler should be smart enough to take care of it, but i neither will fight for keeping it lol.. so if you see any problems with this, just complain again.


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();

Comment on lines +308 to +312
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say remove a few of these empty lines, probably line 300 at least

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()));
}
Loading