Skip to content

Commit 6ea9cde

Browse files
JohnnyLawDGBclaude
andcommitted
fix: add UTXO auto-consolidation to Qt wallet mint path
The RPC mintdigidollar handler had auto-consolidation logic (PR DigiByte-Core#391) that detects when a mint fails due to UTXO fragmentation (>400 inputs needed) and automatically consolidates UTXOs before retrying. The Qt wallet's mintDigiDollar in walletmodel.cpp was missing this logic entirely — it called BuildMintTransaction once and returned the error on failure. This caused Qt GUI users with many small coinbase UTXOs (e.g. ~1,078 DGB each from mining) to fail mints that required more than ~431,400 DGB (400 inputs * ~1,078 DGB), while the same mint via RPC would succeed. Reported by Aussie and DanGB on RC29 testnet. Port the multi-pass consolidation from src/rpc/digidollar.cpp into the Qt mint path with identical parameters (1400 inputs/pass, 10 max passes). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f41f163 commit 6ea9cde

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

src/qt/walletmodel.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <util/translation.h>
2828
#include <util/strencodings.h> // for strprintf
2929
#include <wallet/coincontrol.h>
30+
#include <wallet/spend.h> // for AvailableCoins, CreateTransaction
3031
#include <wallet/wallet.h> // for CRecipient
3132
#include <univalue.h>
3233
#include <wallet/digidollarwallet.h> // for DigiDollarWallet
@@ -923,6 +924,143 @@ WalletModel::DigiDollarMintResult WalletModel::mintDigiDollar(CAmount ddAmount,
923924

924925
DigiDollar::TxBuilderResult result = builder.BuildMintTransaction(params);
925926

927+
// Auto-consolidate if mint failed due to UTXO fragmentation
928+
// (mirrors the RPC mintdigidollar consolidation logic)
929+
std::string consolidation_txid;
930+
if (!result.success && result.error.find("Too many small UTXOs") != std::string::npos) {
931+
LogPrintf("DigiDollar Qt: UTXO fragmentation detected (%zu UTXOs). Auto-consolidating...\n",
932+
availableUtxos.size());
933+
934+
CAmount minRequired = result.collateralRequired + 20000000; // collateral + 0.2 DGB margin
935+
if (totalAvailable < minRequired) {
936+
return DigiDollarMintResult(AmountExceedsBalance, "", "",
937+
QString("Insufficient funds for collateral. Need %1 DGB, have %2 DGB.")
938+
.arg(result.collateralRequired / 100000000.0, 0, 'f', 2)
939+
.arg(totalAvailable / 100000000.0, 0, 'f', 2));
940+
}
941+
942+
CTxDestination consolidationDest;
943+
{
944+
LOCK(pWallet->cs_wallet);
945+
auto op_dest = pWallet->GetNewChangeDestination(OutputType::BECH32);
946+
if (!op_dest) {
947+
return DigiDollarMintResult(TransactionCreationFailed, "", "",
948+
"Failed to get consolidation address");
949+
}
950+
consolidationDest = *op_dest;
951+
}
952+
953+
// Multi-pass consolidation: MAX_STANDARD_TX_WEIGHT is 400k WU.
954+
// P2WPKH input ~271 WU. Conservative limit: 1400 inputs per pass.
955+
static const size_t MAX_CONSOLIDATION_INPUTS = 1400;
956+
static const int MAX_CONSOLIDATION_PASSES = 10;
957+
int pass = 0;
958+
959+
while (availableUtxos.size() > MAX_CONSOLIDATION_INPUTS && pass < MAX_CONSOLIDATION_PASSES) {
960+
++pass;
961+
size_t batch_size = std::min(availableUtxos.size(), MAX_CONSOLIDATION_INPUTS);
962+
LogPrintf("DigiDollar Qt: Consolidation pass %d — sweeping %zu of %zu UTXOs\n",
963+
pass, batch_size, availableUtxos.size());
964+
965+
wallet::CCoinControl coin_control;
966+
CAmount batchTotal = 0;
967+
for (size_t i = 0; i < batch_size; ++i) {
968+
coin_control.Select(availableUtxos[i]);
969+
batchTotal += utxoValues[availableUtxos[i]];
970+
}
971+
coin_control.m_allow_other_inputs = false;
972+
973+
wallet::CRecipient recipient{consolidationDest, batchTotal, /*subtract_fee=*/true};
974+
std::vector<wallet::CRecipient> recipients = {recipient};
975+
976+
auto consolidation_result = wallet::CreateTransaction(*pWallet, recipients, /*change_pos=*/-1, coin_control, /*sign=*/true);
977+
if (!consolidation_result) {
978+
return DigiDollarMintResult(TransactionCreationFailed, "", "",
979+
QString("Auto-consolidation pass %1 failed: %2")
980+
.arg(pass)
981+
.arg(QString::fromStdString(util::ErrorString(consolidation_result).original)));
982+
}
983+
984+
const CTransactionRef& consolidation_tx = consolidation_result->tx;
985+
consolidation_txid = consolidation_tx->GetHash().GetHex();
986+
{
987+
LOCK(pWallet->cs_wallet);
988+
pWallet->CommitTransaction(consolidation_tx, {}, {});
989+
}
990+
991+
LogPrintf("DigiDollar Qt: Consolidation pass %d tx: %s (swept %.2f DGB from %zu inputs)\n",
992+
pass, consolidation_txid, batchTotal / 100000000.0, batch_size);
993+
994+
// Refresh UTXO set after consolidation
995+
availableUtxos.clear();
996+
utxoValues.clear();
997+
totalAvailable = 0;
998+
{
999+
LOCK(pWallet->cs_wallet);
1000+
wallet::CoinsResult coins = wallet::AvailableCoins(*pWallet);
1001+
for (const wallet::COutput& coin : coins.All()) {
1002+
availableUtxos.push_back(coin.outpoint);
1003+
utxoValues[coin.outpoint] = coin.txout.nValue;
1004+
totalAvailable += coin.txout.nValue;
1005+
}
1006+
}
1007+
LogPrintf("DigiDollar Qt: After pass %d: %zu UTXOs available\n", pass, availableUtxos.size());
1008+
}
1009+
1010+
// Single-pass consolidation for <= 1400 UTXOs
1011+
if (consolidation_txid.empty() && availableUtxos.size() <= MAX_CONSOLIDATION_INPUTS) {
1012+
wallet::CCoinControl coin_control;
1013+
CAmount batchTotal = 0;
1014+
for (const auto& utxo : availableUtxos) {
1015+
coin_control.Select(utxo);
1016+
batchTotal += utxoValues[utxo];
1017+
}
1018+
coin_control.m_allow_other_inputs = false;
1019+
1020+
wallet::CRecipient recipient{consolidationDest, batchTotal, /*subtract_fee=*/true};
1021+
std::vector<wallet::CRecipient> recipients = {recipient};
1022+
1023+
auto consolidation_result = wallet::CreateTransaction(*pWallet, recipients, /*change_pos=*/-1, coin_control, /*sign=*/true);
1024+
if (!consolidation_result) {
1025+
return DigiDollarMintResult(TransactionCreationFailed, "", "",
1026+
QString("Auto-consolidation failed: %1")
1027+
.arg(QString::fromStdString(util::ErrorString(consolidation_result).original)));
1028+
}
1029+
1030+
const CTransactionRef& consolidation_tx = consolidation_result->tx;
1031+
consolidation_txid = consolidation_tx->GetHash().GetHex();
1032+
{
1033+
LOCK(pWallet->cs_wallet);
1034+
pWallet->CommitTransaction(consolidation_tx, {}, {});
1035+
}
1036+
1037+
LogPrintf("DigiDollar Qt: Single-pass consolidation tx: %s (swept %.2f DGB from %zu inputs)\n",
1038+
consolidation_txid, batchTotal / 100000000.0, availableUtxos.size());
1039+
1040+
// Refresh UTXO set after consolidation
1041+
availableUtxos.clear();
1042+
utxoValues.clear();
1043+
totalAvailable = 0;
1044+
{
1045+
LOCK(pWallet->cs_wallet);
1046+
wallet::CoinsResult coins = wallet::AvailableCoins(*pWallet);
1047+
for (const wallet::COutput& coin : coins.All()) {
1048+
availableUtxos.push_back(coin.outpoint);
1049+
utxoValues[coin.outpoint] = coin.txout.nValue;
1050+
totalAvailable += coin.txout.nValue;
1051+
}
1052+
}
1053+
}
1054+
1055+
LogPrintf("DigiDollar Qt: After consolidation: %zu UTXOs available (passes: %d)\n",
1056+
availableUtxos.size(), pass);
1057+
1058+
// Retry mint with consolidated UTXOs
1059+
QtMintTxBuilder retryBuilder(Params(), currentHeight, oraclePrice, utxoValues);
1060+
params.utxos = availableUtxos;
1061+
result = retryBuilder.BuildMintTransaction(params);
1062+
}
1063+
9261064
if (!result.success) {
9271065
LogPrintf("DigiDollar Qt: ERROR - BuildMintTransaction failed: %s\n", result.error);
9281066
return DigiDollarMintResult(TransactionCreationFailed, "", "",

0 commit comments

Comments
 (0)