forked from dashpay/dash
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathutil.cpp
More file actions
358 lines (315 loc) · 12.3 KB
/
util.cpp
File metadata and controls
358 lines (315 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// Copyright (c) 2014-2025 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <coinjoin/util.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <util/translation.h>
#include <wallet/fees.h>
#include <wallet/spend.h>
#include <wallet/wallet.h>
#include <wallet/walletutil.h>
#include <numeric>
using wallet::CompactTallyItem;
using wallet::CRecipient;
using wallet::CWallet;
using wallet::FEATURE_COMPRPUBKEY;
using wallet::GetDiscardRate;
using wallet::RANDOM_CHANGE_POSITION;
using wallet::WalletBatch;
inline unsigned int GetSizeOfCompactSizeDiff(uint64_t nSizePrev, uint64_t nSizeNew)
{
assert(nSizePrev <= nSizeNew);
return ::GetSizeOfCompactSize(nSizeNew) - ::GetSizeOfCompactSize(nSizePrev);
}
CKeyHolder::CKeyHolder(CWallet* pwallet) :
reserveDestination(pwallet)
{
auto dest_opt = reserveDestination.GetReservedDestination(false);
assert(dest_opt);
dest = *dest_opt;
}
void CKeyHolder::KeepKey()
{
reserveDestination.KeepDestination();
}
void CKeyHolder::ReturnKey()
{
reserveDestination.ReturnDestination();
}
CScript CKeyHolder::GetScriptForDestination() const
{
return ::GetScriptForDestination(dest);
}
CScript CKeyHolderStorage::AddKey(CWallet* pwallet)
{
auto keyHolderPtr = std::make_unique<CKeyHolder>(pwallet);
auto script = keyHolderPtr->GetScriptForDestination();
LOCK(cs_storage);
storage.emplace_back(std::move(keyHolderPtr));
LogPrint(BCLog::COINJOIN, "CKeyHolderStorage::%s -- storage size %lld\n", __func__, storage.size());
return script;
}
void CKeyHolderStorage::KeepAll()
{
std::vector<std::unique_ptr<CKeyHolder> > tmp;
{
// don't hold cs_storage while calling KeepKey(), which might lock cs_wallet
LOCK(cs_storage);
std::swap(storage, tmp);
}
if (!tmp.empty()) {
for (const auto& key : tmp) {
key->KeepKey();
}
LogPrint(BCLog::COINJOIN, "CKeyHolderStorage::%s -- %lld keys kept\n", __func__, tmp.size());
}
}
void CKeyHolderStorage::ReturnAll()
{
std::vector<std::unique_ptr<CKeyHolder> > tmp;
{
// don't hold cs_storage while calling ReturnKey(), which might lock cs_wallet
LOCK(cs_storage);
std::swap(storage, tmp);
}
if (!tmp.empty()) {
for (const auto& key : tmp) {
key->ReturnKey();
}
LogPrint(BCLog::COINJOIN, "CKeyHolderStorage::%s -- %lld keys returned\n", __func__, tmp.size());
}
}
CTransactionBuilderOutput::CTransactionBuilderOutput(CTransactionBuilder* pTxBuilderIn, CWallet& wallet, CAmount nAmountIn) :
pTxBuilder(pTxBuilderIn),
dest(&wallet),
nAmount(nAmountIn)
{
assert(pTxBuilder);
LOCK(wallet.cs_wallet);
auto dest_opt = dest.GetReservedDestination(false);
assert(dest_opt);
script = ::GetScriptForDestination(*dest_opt);
}
bool CTransactionBuilderOutput::UpdateAmount(const CAmount nNewAmount)
{
if (nNewAmount <= 0 || nNewAmount - nAmount > pTxBuilder->GetAmountLeft()) {
return false;
}
nAmount = nNewAmount;
return true;
}
CTransactionBuilder::CTransactionBuilder(CWallet& wallet, const CompactTallyItem& tallyItemIn) :
m_wallet(wallet),
dummyReserveDestination(&wallet),
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(m_wallet);
// Generate a feerate which will be used by calculations of this class and also by CWallet::CreateTransaction
coinControl.m_feerate = std::max(GetRequiredFeeRate(m_wallet), m_wallet.m_pay_tx_fee);
// If wallet does not have the avoid-reuse feature enabled, keep legacy
// behavior: force change to go back to the origin address. When
// WALLET_FLAG_AVOID_REUSE is enabled, let the wallet select a fresh
// change destination to avoid address reuse.
if (!m_wallet.IsWalletFlagSet(wallet::WALLET_FLAG_AVOID_REUSE)) {
coinControl.destChange = tallyItemIn.txdest;
}
// Only allow tallyItems inputs for tx creation
coinControl.m_allow_other_inputs = false;
// Create dummy tx to calculate the exact required fees upfront for accurate amount and fee calculations
CMutableTransaction dummyTx;
// Select all tallyItem outputs in the coinControl so that CreateTransaction knows what to use
for (const auto& outpoint : tallyItem.outpoints) {
coinControl.Select(outpoint);
dummyTx.vin.emplace_back(outpoint, CScript());
}
// Get a comparable dummy scriptPubKey, avoid writing/flushing to the actual wallet db
CScript dummyScript;
{
LOCK(m_wallet.cs_wallet);
WalletBatch dummyBatch(m_wallet.GetDatabase(), false);
dummyBatch.TxnBegin();
CKey secret;
secret.MakeNewKey(m_wallet.CanSupportFeature(FEATURE_COMPRPUBKEY));
CPubKey dummyPubkey = secret.GetPubKey();
dummyBatch.TxnAbort();
dummyScript = ::GetScriptForDestination(PKHash(dummyPubkey));
// Calculate required bytes for the dummy signed tx with tallyItem's inputs only
nBytesBase = CalculateMaximumSignedTxSize(CTransaction(dummyTx), &m_wallet, /*coin_control=*/nullptr);
}
// Calculate the output size
nBytesOutput = ::GetSerializeSize(CTxOut(0, dummyScript), 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..
dummyReserveDestination.ReturnDestination();
}
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() < std::numeric_limits<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)
{
if (CouldAddOutput(nAmountOutput)) {
LOCK(cs_outputs);
vecOutputs.push_back(std::make_unique<CTransactionBuilderOutput>(this, m_wallet, nAmountOutput));
return vecOutputs.back().get();
}
return nullptr;
}
unsigned int CTransactionBuilder::GetBytesTotal() const
{
LOCK(cs_outputs);
// 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
{
LOCK(cs_outputs);
return std::accumulate(vecOutputs.begin(), vecOutputs.end(), CAmount{0}, [](const CAmount& a, const auto& b){
return a + b->GetAmount();
});
}
CAmount CTransactionBuilder::GetFee(unsigned int nBytes) const
{
CAmount nFeeCalc = coinControl.m_feerate->GetFee(nBytes);
CAmount nRequiredFee = GetRequiredFee(m_wallet, nBytes);
if (nRequiredFee > nFeeCalc) {
nFeeCalc = nRequiredFee;
}
if (nFeeCalc > m_wallet.m_default_max_tx_fee) {
nFeeCalc = m_wallet.m_default_max_tx_fee;
}
return nFeeCalc;
}
int CTransactionBuilder::GetSizeOfCompactSizeDiff(size_t nAdd) const
{
size_t nSize = WITH_LOCK(cs_outputs, return vecOutputs.size());
unsigned int ret = ::GetSizeOfCompactSizeDiff(nSize, nSize + nAdd);
assert(ret <= std::numeric_limits<int>::max());
return int(ret);
}
bool CTransactionBuilder::IsDust(CAmount nAmount) const
{
return ::IsDust(CTxOut(nAmount, ::GetScriptForDestination(tallyItem.txdest)), coinControl.m_discard_feerate.value());
}
bool CTransactionBuilder::Commit(bilingual_str& strResult)
{
CAmount nFeeRet = 0;
int nChangePosRet{RANDOM_CHANGE_POSITION};
// 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});
}
}
CTransactionRef tx;
{
LOCK2(m_wallet.cs_wallet, ::cs_main);
auto ret = wallet::CreateTransaction(m_wallet, vecSend, nChangePosRet, coinControl);
if (ret) {
tx = ret->tx;
nFeeRet = ret->fee;
nChangePosRet = ret->change_pos;
} else {
strResult = util::ErrorString(ret);
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 != RANDOM_CHANGE_POSITION && fDust) {
strResult = Untranslated(strprintf("Unexpected change output %s at position %d", 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 == RANDOM_CHANGE_POSITION && !fDust) {
strResult = Untranslated(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 = Untranslated(strprintf("Fee validation failed -> nFeeRet: %d, nFeeCalc: %d, nFeeAdditional: %d, nBytesAdditional: %d, %s", nFeeRet, nFeeCalc, nFeeAdditional, nBytesAdditional, ToString()));
return false;
}
{
LOCK2(m_wallet.cs_wallet, ::cs_main);
m_wallet.CommitTransaction(tx, {}, {});
}
fKeepKeys = true;
strResult = Untranslated(tx->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()));
}