Summary
Yerbas wallets appear to suffer from long-term abnormal wallet.dat growth even when the wallet has little or no direct user transaction activity. This has been a long-standing Yerbas-specific issue and does not appear to reproduce in Crystal Bitoreum / BTRM under comparable wallet usage.
Investigation points to a wallet ownership false-positive path related to Yerbas asset support. Specifically, CAssetOutputEntry contains uninitialized primitive fields, and CWallet::HasMyAssets() reads assetData.nAmount after calling GetDebit(...) even when no asset data was populated.
This can cause unrelated transactions to be incorrectly classified as wallet-related, causing AddToWalletIfInvolvingMe() to persist them into wallet.dat.
Over time, this can balloon normal wallets to extremely large sizes and cause very slow GUI startup, wallet verification, wallet loading, and automatic backup behavior.
Observed behavior
A regular Yerbas wallet with little expected user transaction activity showed:
wallet.dat size: ~1.6 GB
mapWallet.size(): ~1,498,000 wallet transactions
wallet load time: ~28.5 seconds
wallet verify time: ~20–30 seconds
Example startup log pattern:
init message: Verifying wallet(s)...
Using wallet wallet.dat
init message: Loading wallet...
nKeysLeftSinceAutoBackup: 1000
wallet 28512ms
setExternalKeyPool.size() = 1000
setInternalKeyPool.size() = 0
mapWallet.size() = 1497362
This causes a long delay between splash screen and main GUI availability. The issue initially looked like reindex or block index delay, but timing logs showed the main startup cost was wallet verification and wallet loading.
The relevant startup delay was traced to:
CWallet::InitAutoBackup()
CWallet::CreateWalletFromFile()
CWallet::LoadWallet()
CWalletDB::LoadWallet()
CWallet::AddToWalletIfInvolvingMe()
Automatic startup backups make the pain worse because the client copies the entire large wallet.dat before continuing startup, but backup is not the root cause of the growth.
Why this is likely a Yerbas wallet classification bug
The persistent wallet growth path is:
BlockConnected / TransactionAddedToMempool
-> CWallet::SyncTransaction()
-> CWallet::AddToWalletIfInvolvingMe()
-> CWallet::AddToWallet()
-> CWalletDB::WriteTx()
-> wallet.dat grows
AddToWalletIfInvolvingMe() writes a transaction to the wallet if the wallet believes the transaction is mine or from me:
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
{
...
return AddToWallet(wtx, false);
}
Yerbas extends IsFromMe() with asset-aware logic:
bool CWallet::IsFromMe(const CTransaction& tx) const
{
return (GetDebit(tx, ISMINE_ALL) > 0 || HasMyAssets(tx));
}
The suspicious function is:
CAmount CWallet::HasMyAssets(const CTransaction& tx) const
{
for (const CTxIn& txin : tx.vin)
{
CAssetOutputEntry assetData;
GetDebit(txin, ISMINE_ALL, assetData);
if (assetData.nAmount > 0)
return true;
}
return (GetDebit(tx, ISMINE_ALL) > 0);
}
The problem is that CAssetOutputEntry assetData; is default-created without initialization, while the struct contains primitive fields with no constructor or default values:
struct CAssetOutputEntry
{
txnouttype type;
std::string assetName;
CTxDestination destination;
CAmount nAmount;
std::string message;
int64_t expireTime;
int vout;
};
If GetDebit(txin, ISMINE_ALL, assetData) returns without setting asset data, assetData.nAmount may still contain uninitialized stack garbage. A random positive value would cause:
if (assetData.nAmount > 0)
return true;
Then:
Then:
AddToWalletIfInvolvingMe()
writes the unrelated transaction into wallet.dat.
Expected behavior
A wallet should only persist transactions that are actually relevant to that wallet:
- Outputs spendable by the wallet
- Inputs spending wallet-owned outputs
- Watch-only outputs, if applicable
- Legitimate asset transactions involving wallet-owned asset outputs
A normal idle wallet should not accumulate large numbers of unrelated chain transactions.
Actual behavior
Regular Yerbas wallets can grow dramatically over time, even with little or no user transaction activity. This suggests that transactions unrelated to the wallet are being misclassified as wallet-relevant and persisted.
In the observed case, mapWallet.size() reached approximately 1.5 million entries and wallet.dat reached approximately 1.6 GB.
Proposed fix
The attached diff makes two defensive changes.
1. Initialize CAssetOutputEntry primitive fields
struct CAssetOutputEntry
{
- txnouttype type;
+ txnouttype type = TX_NONSTANDARD;
std::string assetName;
CTxDestination destination;
- CAmount nAmount;
+ CAmount nAmount = 0;
std::string message;
- int64_t expireTime;
- int vout;
+ int64_t expireTime = 0;
+ int vout = -1;
};
This prevents uninitialized primitive values from being read anywhere this struct is used.
2. Value-initialize assetData and remove redundant debit fallback in HasMyAssets()
CAmount CWallet::HasMyAssets(const CTransaction& tx) const
{
for (const CTxIn& txin : tx.vin)
{
- CAssetOutputEntry assetData;
+ CAssetOutputEntry assetData{};
GetDebit(txin, ISMINE_ALL, assetData);
if (assetData.nAmount > 0)
- return true;
+ return assetData.nAmount;
}
- return (GetDebit(tx, ISMINE_ALL) > 0);
+ return 0;
}
The removed fallback is redundant because IsFromMe() already checks normal wallet debit before calling HasMyAssets():
return (GetDebit(tx, ISMINE_ALL) > 0 || HasMyAssets(tx));
After this change, HasMyAssets() only reports asset-specific debit and does not duplicate the normal debit test.
Risk assessment
This is a low-risk defensive fix.
It does not change consensus behavior, transaction validation, block validation, network behavior, or asset serialization.
It only changes wallet-side classification safety by:
- Preventing uninitialized primitive reads
- Ensuring asset metadata defaults to safe values
- Avoiding redundant debit logic in
HasMyAssets()
Existing bloated wallets will not shrink automatically. This fix is expected to stop or reduce false-positive wallet growth going forward.
Suggested testing
Fresh wallet idle/sync test
- Backup or move existing
wallet.dat.
- Start Yerbas with a fresh wallet.
- Allow the node to sync or idle normally.
- Monitor:
grep "mapWallet.size" ~/.yerbascore/debug.log | tail -20
ls -lh ~/.yerbascore/wallet.dat
Expected result: mapWallet.size() should not grow rapidly during normal idle syncing unless the wallet actually receives or spends funds/assets.
Existing wallet startup test
With an existing large wallet, compare startup behavior before and after the patch:
grep -E "Verifying wallet|Loading wallet| wallet |mapWallet.size" ~/.yerbascore/debug.log | tail -80
Expected result: existing large wallets may still load slowly because the old unrelated transaction records remain, but new false-positive growth should stop.
Optional debug instrumentation
Temporarily instrument AddToWalletIfInvolvingMe() to confirm whether transactions are being added due to IsMine, normal debit, or asset debit:
bool fMine = IsMine(tx);
bool fDebit = GetDebit(tx, ISMINE_ALL) > 0;
CAmount nAssetDebit = HasMyAssets(tx);
if (!fExisted) {
static std::atomic<int> nWalletGrowthLogs{0};
if (nWalletGrowthLogs.fetch_add(1) < 200) {
LogPrintf("YERBAS-WALLET-GROWTH: tx=%s mine=%d debit=%d assetDebit=%d vin=%u vout=%u height=%d pos=%d\n",
tx.GetHash().ToString(),
fMine,
fDebit,
nAssetDebit,
tx.vin.size(),
tx.vout.size(),
pIndex ? pIndex->nHeight : -1,
posInBlock);
}
}
Expected result after this patch: unrelated chain transactions should not be added with assetDebit caused by uninitialized CAssetOutputEntry::nAmount.
Notes
This issue appears to be separate from automatic wallet backups. Auto-backups make startup slower when wallet.dat is already large, but the root wallet growth appears to come from incorrect transaction classification and persistence.
A follow-up improvement may be to reconsider the default createwalletbackups=10, because copying very large wallet files on every startup is expensive. However, that is secondary to this wallet bloat fix.
Possible Solution
--- /mnt/data/wallet.h.bak 2026-06-28 08:37:30.235643191 +0000
+++ /mnt/data/wallet.h 2026-06-28 08:37:32.966988972 +0000
@@ -204,13 +204,13 @@
/** YERBAS ASSETS START */
struct CAssetOutputEntry
{
- txnouttype type;
+ txnouttype type = TX_NONSTANDARD;
std::string assetName;
CTxDestination destination;
- CAmount nAmount;
+ CAmount nAmount = 0;
std::string message;
- int64_t expireTime;
- int vout;
+ int64_t expireTime = 0;
+ int vout = -1;
};
/** YERBAS ASSETS END */
--- /mnt/data/wallet.cpp.bak 2026-06-28 08:37:30.213351631 +0000
+++ /mnt/data/wallet.cpp 2026-06-28 08:37:32.971675669 +0000
@@ -1832,13 +1832,13 @@
{
for (const CTxIn& txin : tx.vin)
{
- CAssetOutputEntry assetData;
+ CAssetOutputEntry assetData{};
GetDebit(txin, ISMINE_ALL, assetData);
if (assetData.nAmount > 0)
- return true;
+ return assetData.nAmount;
}
- return (GetDebit(tx, ISMINE_ALL) > 0);
+ return 0;
}
CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
Summary
Yerbas wallets appear to suffer from long-term abnormal
wallet.datgrowth even when the wallet has little or no direct user transaction activity. This has been a long-standing Yerbas-specific issue and does not appear to reproduce in Crystal Bitoreum / BTRM under comparable wallet usage.Investigation points to a wallet ownership false-positive path related to Yerbas asset support. Specifically,
CAssetOutputEntrycontains uninitialized primitive fields, andCWallet::HasMyAssets()readsassetData.nAmountafter callingGetDebit(...)even when no asset data was populated.This can cause unrelated transactions to be incorrectly classified as wallet-related, causing
AddToWalletIfInvolvingMe()to persist them intowallet.dat.Over time, this can balloon normal wallets to extremely large sizes and cause very slow GUI startup, wallet verification, wallet loading, and automatic backup behavior.
Observed behavior
A regular Yerbas wallet with little expected user transaction activity showed:
Example startup log pattern:
This causes a long delay between splash screen and main GUI availability. The issue initially looked like reindex or block index delay, but timing logs showed the main startup cost was wallet verification and wallet loading.
The relevant startup delay was traced to:
Automatic startup backups make the pain worse because the client copies the entire large
wallet.datbefore continuing startup, but backup is not the root cause of the growth.Why this is likely a Yerbas wallet classification bug
The persistent wallet growth path is:
AddToWalletIfInvolvingMe()writes a transaction to the wallet if the wallet believes the transaction is mine or from me:Yerbas extends
IsFromMe()with asset-aware logic:The suspicious function is:
The problem is that
CAssetOutputEntry assetData;is default-created without initialization, while the struct contains primitive fields with no constructor or default values:If
GetDebit(txin, ISMINE_ALL, assetData)returns without setting asset data,assetData.nAmountmay still contain uninitialized stack garbage. A random positive value would cause:Then:
IsFromMe(tx) == trueThen:
AddToWalletIfInvolvingMe()writes the unrelated transaction into
wallet.dat.Expected behavior
A wallet should only persist transactions that are actually relevant to that wallet:
A normal idle wallet should not accumulate large numbers of unrelated chain transactions.
Actual behavior
Regular Yerbas wallets can grow dramatically over time, even with little or no user transaction activity. This suggests that transactions unrelated to the wallet are being misclassified as wallet-relevant and persisted.
In the observed case,
mapWallet.size()reached approximately 1.5 million entries andwallet.datreached approximately 1.6 GB.Proposed fix
The attached diff makes two defensive changes.
1. Initialize
CAssetOutputEntryprimitive fieldsstruct CAssetOutputEntry { - txnouttype type; + txnouttype type = TX_NONSTANDARD; std::string assetName; CTxDestination destination; - CAmount nAmount; + CAmount nAmount = 0; std::string message; - int64_t expireTime; - int vout; + int64_t expireTime = 0; + int vout = -1; };This prevents uninitialized primitive values from being read anywhere this struct is used.
2. Value-initialize
assetDataand remove redundant debit fallback inHasMyAssets()CAmount CWallet::HasMyAssets(const CTransaction& tx) const { for (const CTxIn& txin : tx.vin) { - CAssetOutputEntry assetData; + CAssetOutputEntry assetData{}; GetDebit(txin, ISMINE_ALL, assetData); if (assetData.nAmount > 0) - return true; + return assetData.nAmount; } - return (GetDebit(tx, ISMINE_ALL) > 0); + return 0; }The removed fallback is redundant because
IsFromMe()already checks normal wallet debit before callingHasMyAssets():After this change,
HasMyAssets()only reports asset-specific debit and does not duplicate the normal debit test.Risk assessment
This is a low-risk defensive fix.
It does not change consensus behavior, transaction validation, block validation, network behavior, or asset serialization.
It only changes wallet-side classification safety by:
HasMyAssets()Existing bloated wallets will not shrink automatically. This fix is expected to stop or reduce false-positive wallet growth going forward.
Suggested testing
Fresh wallet idle/sync test
wallet.dat.Expected result:
mapWallet.size()should not grow rapidly during normal idle syncing unless the wallet actually receives or spends funds/assets.Existing wallet startup test
With an existing large wallet, compare startup behavior before and after the patch:
Expected result: existing large wallets may still load slowly because the old unrelated transaction records remain, but new false-positive growth should stop.
Optional debug instrumentation
Temporarily instrument
AddToWalletIfInvolvingMe()to confirm whether transactions are being added due toIsMine, normal debit, or asset debit:Expected result after this patch: unrelated chain transactions should not be added with
assetDebitcaused by uninitializedCAssetOutputEntry::nAmount.Notes
This issue appears to be separate from automatic wallet backups. Auto-backups make startup slower when
wallet.datis already large, but the root wallet growth appears to come from incorrect transaction classification and persistence.A follow-up improvement may be to reconsider the default
createwalletbackups=10, because copying very large wallet files on every startup is expensive. However, that is secondary to this wallet bloat fix.Possible Solution