Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 734979c93b | |||
| 00af636aca | |||
| 9377b3a52f | |||
| 1881ff867e | |||
| caddfb1789 |
+1
-1
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
|
||||
endif()
|
||||
|
||||
project(Triangles
|
||||
VERSION 5.8.1.0
|
||||
VERSION 5.8.2
|
||||
DESCRIPTION "Cryptographic Triangles Wallet"
|
||||
LANGUAGES C CXX
|
||||
)
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# Triangles Modernization Roadmap
|
||||
|
||||
**Goal:** Make TRI faster to sync, safer for wallets, and more useful as a currency — without breaking consensus.
|
||||
|
||||
**Invariant:** Any change that modifies block validation, stake modifier computation, transaction format, or signature verification MUST preserve exact consensus with existing v5.x nodes. When in doubt, test against a synced v5.8.1 node.
|
||||
|
||||
---
|
||||
|
||||
## Priority 1: Faster Syncing (High Impact, Low Risk)
|
||||
|
||||
### 1.1 Update Checkpoints (Easy, Immediate)
|
||||
**Problem:** Last hardcoded checkpoint is at block 2,186,940. `IsInitialBlockDownload()` returns false past this point, causing orphan limit to drop from 4000 to 750 — exactly what caused the fork deadlock.
|
||||
|
||||
**Fix:** Add checkpoints every ~50,000 blocks up to current height (~2,207,000+).
|
||||
```cpp
|
||||
// src/checkpoints.cpp - add recent checkpoints
|
||||
{2190000, uint256("...")},
|
||||
{2195000, uint256("...")},
|
||||
{2200000, uint256("...")},
|
||||
{2205000, uint256("...")},
|
||||
{2210000, uint256("...")},
|
||||
```
|
||||
**Risk:** None — checkpoints are only used for IBD detection and quick rejection of clearly wrong chains.
|
||||
|
||||
### 1.2 Increase Post-Checkpoint Orphan Limit (Easy)
|
||||
**Problem:** 750 orphans after IBD is too low for a low-peer network. During the fork incident, 750 orphans filled up and the node deadlocked.
|
||||
|
||||
**Fix:**
|
||||
```cpp
|
||||
// src/main.h
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS = 2000; // was 750
|
||||
```
|
||||
**Risk:** Slightly more memory usage during forks. Worth it for resilience.
|
||||
|
||||
### 1.3 Parallel Block Download (Medium Effort)
|
||||
**Problem:** Current implementation downloads blocks sequentially from one peer at a time during IBD.
|
||||
|
||||
**Fix:** Increase batch sizes and allow concurrent block downloads from multiple peers:
|
||||
```cpp
|
||||
// src/main.cpp
|
||||
// During IBD, request blocks from multiple peers simultaneously
|
||||
unsigned int nGetDataBatchSize = IsInitialBlockDownload() ? 8000 : 1000; // was 4000
|
||||
```
|
||||
**Risk:** Low — larger batch sizes are already proven in Bitcoin forks.
|
||||
|
||||
### 1.4 Header-First Sync (Medium Effort)
|
||||
**Problem:** Node downloads full blocks before validating headers. A bad peer can waste bandwidth.
|
||||
|
||||
**Fix:** Download and validate all headers first (compact ~80 bytes each), then download full blocks only for the best chain.
|
||||
- Separate `getheaders`/`headers` message handling
|
||||
- Download blocks only for the best header chain
|
||||
- Reduces wasted bandwidth during forks by 95%+
|
||||
|
||||
### 1.5 Bootstrap Over HTTPS with Resume (Easy)
|
||||
**Problem:** Built-in bootstrap (`-bootstrap`) uses raw TCP and can't resume interrupted downloads.
|
||||
|
||||
**Fix:** The existing `bootstrap.cpp` already supports downloading. Add:
|
||||
- Resume support (Range headers)
|
||||
- SHA256 verification of downloaded archive
|
||||
- Better progress reporting
|
||||
- Fallback mirrors
|
||||
|
||||
---
|
||||
|
||||
## Priority 2: Wallet Safety (Critical)
|
||||
|
||||
### 2.1 Automatic Wallet Backup Before Dangerous Operations (Easy)
|
||||
**Problem:** Corrupt wallet = lost funds. No automatic backup before risky operations.
|
||||
|
||||
**Fix:** In `walletdb.cpp`, before any rewrite:
|
||||
```cpp
|
||||
// Before wallet.dat rewrite, copy to wallet.dat.bak
|
||||
if (boost::filesystem::exists(pathWallet)) {
|
||||
boost::filesystem::copy_file(pathWallet, pathWallet + ".bak",
|
||||
boost::filesystem::copy_option::overwrite_if_exists);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Detect and Report BDB Corruption (Easy)
|
||||
**Problem:** BDB corruption silently corrupts wallet. User doesn't know until it's too late.
|
||||
|
||||
**Fix:** Add wallet integrity check on load:
|
||||
```cpp
|
||||
// In CWallet::LoadWallet()
|
||||
// After opening, verify BDB environment is healthy
|
||||
// If DB_RUNRECOVERY, auto-salvage and warn user
|
||||
```
|
||||
|
||||
### 2.3 Wallet.dat Versioning (Medium Effort)
|
||||
**Problem:** Single wallet.dat file. If it corrupts during write, funds are lost.
|
||||
|
||||
**Fix:** Implement copy-on-write wallet saves:
|
||||
- Write new wallet data to `wallet.dat.new`
|
||||
- Atomically rename `wallet.dat` → `wallet.dat.old`, `wallet.dat.new` → `wallet.dat`
|
||||
- Keep last 3 wallet revisions
|
||||
- On load, try wallet.dat first, fall back to wallet.dat.old if corrupt
|
||||
|
||||
### 2.4 Seed Phrase / HD Wallet (High Effort, High Impact)
|
||||
**Problem:** Losing wallet.dat = losing everything. No recovery mechanism.
|
||||
|
||||
**Fix:** Implement BIP39/BIP44 HD wallet as optional upgrade:
|
||||
- Generate 12/24-word seed phrase on new wallet creation
|
||||
- Derive all keys from seed deterministically
|
||||
- Import seed on any device to recover wallet
|
||||
- Keep backward compatibility with existing non-HD wallets
|
||||
|
||||
---
|
||||
|
||||
## Priority 3: Network Resilience (Medium Impact)
|
||||
|
||||
### 3.1 Better Peer Management (Medium Effort)
|
||||
**Problem:** Low peer counts (2-6) lead to fork divergence. No prioritization of reliable peers.
|
||||
|
||||
**Fix:**
|
||||
- Peer reliability scoring (track which peers provide valid blocks)
|
||||
- Prefer peers that are ahead and on the same chain
|
||||
- Automatic disconnection of stale/forked peers
|
||||
- Increase default `maxconnections` from 64 to 128
|
||||
|
||||
### 3.2 Compact Block Relay (High Effort)
|
||||
**Problem:** Full blocks are sent even when the receiver likely already has most transactions.
|
||||
|
||||
**Fix:** Implement BIP 152 compact blocks:
|
||||
- Send block header + short transaction IDs
|
||||
- Receiver fills in from mempool, only requests missing transactions
|
||||
- Reduces bandwidth by ~90% during normal operation
|
||||
|
||||
### 3.3 DNS Seed Infrastructure (Easy)
|
||||
**Problem:** `dnsseed=0` when Tor-only means no automatic peer discovery.
|
||||
|
||||
**Fix:** Run a DNS seed server that resolves to known reliable onion addresses:
|
||||
```
|
||||
seed.cryptographic-triangles.org → returns onion addresses of healthy nodes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Priority 4: User Experience (Medium Impact)
|
||||
|
||||
### 4.1 Progress Reporting for IBD (Easy)
|
||||
**Problem:** Users see "downloading blocks..." with no useful progress indicator.
|
||||
|
||||
**Fix:**
|
||||
- Report `headers` vs `blocks` progress separately
|
||||
- Show estimated time remaining based on download speed
|
||||
- Log progress every 1000 blocks (currently every 5000)
|
||||
- Qt wallet: update progress bar more frequently
|
||||
|
||||
### 4.2 Staking Dashboard Improvements (Easy)
|
||||
**Problem:** Qt wallet shows staking info but not clearly.
|
||||
|
||||
**Fix:**
|
||||
- Show expected time to stake more prominently
|
||||
- Display staking weight as percentage of network
|
||||
- Notify when stake is found (system notification)
|
||||
- Show "staking" indicator in system tray
|
||||
|
||||
### 4.3 Transaction Fee Estimation (Medium Effort)
|
||||
**Problem:** No fee estimation. Users guess.
|
||||
|
||||
**Fix:** Track recent block inclusion rates by fee level, provide fee recommendations.
|
||||
|
||||
---
|
||||
|
||||
## Priority 5: Code Modernization (Low Urgency, Good Hygiene)
|
||||
|
||||
### 5.1 C++17/20 Features
|
||||
- Replace raw pointers with smart pointers where safe
|
||||
- Use `std::optional`, `std::string_view`, `std::filesystem`
|
||||
- Replace boost::filesystem with std::filesystem (C++17)
|
||||
|
||||
### 5.2 Build System
|
||||
- CMake is already in place (good)
|
||||
- Add sanitizers (ASAN, UBSAN) to CI
|
||||
- Static analysis with clang-tidy
|
||||
|
||||
### 5.3 Testing
|
||||
- Current test coverage is thin
|
||||
- Add unit tests for:
|
||||
- Checkpoint validation
|
||||
- Stake modifier computation
|
||||
- Bootstrap download/resume
|
||||
- Wallet BDB recovery
|
||||
- Orphan block handling
|
||||
|
||||
---
|
||||
|
||||
## What NOT to Change
|
||||
|
||||
These are consensus-critical and must remain identical:
|
||||
- Block validation rules
|
||||
- Stake modifier computation (`ComputeNextStakeModifier`)
|
||||
- Transaction signature verification
|
||||
- Block reward schedule
|
||||
- PoW/PoS target computation
|
||||
- Chain trust / difficulty adjustment
|
||||
- Message serialization format
|
||||
- Protocol version handshaking
|
||||
|
||||
Any change to these requires a coordinated network upgrade (hard fork).
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **This week:** Update checkpoints (1.1), increase orphan limit (1.2), wallet backup before save (2.1)
|
||||
2. **Next week:** Better progress reporting (4.1), increase batch size (1.3)
|
||||
3. **Month 1:** Wallet versioning (2.3), bootstrap resume (1.5)
|
||||
4. **Month 2:** Header-first sync (1.4), peer reliability (3.1)
|
||||
5. **Month 3+:** HD wallet (2.4), compact blocks (3.2)
|
||||
@@ -27,12 +27,14 @@ Triangles is a privacy-focused cryptocurrency featuring Proof-of-Stake consensus
|
||||
The Triangles network operates exclusively over Tor for privacy:
|
||||
|
||||
**Tor v3 Seeds:**
|
||||
- `gxvrhv3qitnc6kobrhsrse46bmcfitnybapor3or3oczzuxn6hfzxyid.onion:24112`
|
||||
- `futmtrvh6j34t7s6yjdxfia6iwuyfzwh4k5eqfof5kfhoqk3xmi3qoqd.onion:24112`
|
||||
- `jbpfhe7zw3qm67wy3j2ayysp3mnrjobopthnko3b3sgahqtecblwqmid.onion:24112`
|
||||
- `uddaxjbo3lh2zskg7w6gwln4ty5cel7q4c5jbx7fdtv6zf2j47gdlyad.onion:24112`
|
||||
- `el5sirhhleecuctpeeprelzubpqmoqivvra3rzlwbjttinxa4fq3wnid.onion:24112`
|
||||
- `sj5dhybnlp3v4y5niyc5unrnd6s43lyx5ibup7rolyosjbi2u2hsbvyd.onion:24112`
|
||||
- `i3kr5meha7se4ns3wss3h7v46m6uksfzv4wrohdqxpj6n35wyo2bvlid.onion:24112`
|
||||
|
||||
**DNS Seeds:**
|
||||
- `seed1.cryptographic-triangles.org`
|
||||
- `seed2.cryptographic-triangles.org`
|
||||
**HTTP Seed List:**
|
||||
- `seeds.cryptographic-triangles.org/seeds.txt` - Dynamically updated list of active onion peers
|
||||
|
||||
## Building from Source
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace Checkpoints
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
|
||||
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
|
||||
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
|
||||
};
|
||||
|
||||
static MapCheckpoints mapCheckpointsTestnet = {
|
||||
@@ -49,6 +52,9 @@ namespace Checkpoints
|
||||
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
|
||||
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
|
||||
{2186940, uint256("0xbd952e8d4a612e336d840ad924a7e09395e36bcd9d929b302e47e60b5c3098c0")},
|
||||
{2190000, uint256("0x682baf783581468ba18f9967254a7f3944e8b8c4cc7101e7d99b68f4f9dd5271")},
|
||||
{2200000, uint256("0x0a8d0442f031f1258120f713f34e45f4f9a625fb753558e27b89b32ad5a9a740")},
|
||||
{2205000, uint256("0xf7aa893ec012181e321783d6a5487addf6997377908faa3e760c9054a4217d29")},
|
||||
};
|
||||
|
||||
bool CheckHardened(int nHeight, const uint256& hash)
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
|
||||
#define CLIENT_VERSION_MAJOR 5
|
||||
#define CLIENT_VERSION_MINOR 8
|
||||
#define CLIENT_VERSION_REVISION 1
|
||||
#define CLIENT_VERSION_REVISION 2
|
||||
#define CLIENT_VERSION_BUILD 0
|
||||
|
||||
// Converts the parameter X to a string after macro replacement on X has been performed.
|
||||
|
||||
@@ -28,6 +28,7 @@ extern unsigned int nWalletDBUpdated;
|
||||
|
||||
void ThreadFlushWalletDB(void* parg);
|
||||
bool BackupWallet(const CWallet& wallet, const std::string& strDest);
|
||||
bool AutoBackupWallet(const boost::filesystem::path& walletPath);
|
||||
|
||||
|
||||
class CDBEnv
|
||||
|
||||
@@ -952,6 +952,18 @@ bool AppInit2()
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle -reindex: delete the LevelDB block index so it gets rebuilt
|
||||
// from the raw blk*.dat files via FastImportBlockFile().
|
||||
// This recalculates money supply, tx index, and UTXO set from scratch.
|
||||
if (GetBoolArg("-reindex", false))
|
||||
{
|
||||
printf("Reindex requested: removing block index database...\n");
|
||||
uiInterface.InitMessage(_("Removing block index for reindex..."));
|
||||
fs::path txleveldbPath = GetDataDir() / "txleveldb";
|
||||
if (fs::exists(txleveldbPath))
|
||||
fs::remove_all(txleveldbPath);
|
||||
}
|
||||
|
||||
uiInterface.InitMessage(_("Loading block index..."));
|
||||
printf("Loading block index...\n");
|
||||
nStart = GetTimeMillis();
|
||||
@@ -1041,6 +1053,21 @@ bool AppInit2()
|
||||
nStart = GetTimeMillis();
|
||||
bool fFirstRun = true;
|
||||
pwalletMain = new CWallet(strWalletFileName);
|
||||
|
||||
// Auto-backup wallet.dat before loading (protects against corruption during load/flush)
|
||||
{
|
||||
fs::path walletPath = GetDataDir() / strWalletFileName;
|
||||
if (fs::exists(walletPath)) {
|
||||
uintmax_t wsize = fs::file_size(walletPath);
|
||||
printf("Wallet file size: %llu bytes\n", (unsigned long long)wsize);
|
||||
if (wsize < 1024) {
|
||||
strErrors << _("WARNING: wallet.dat is suspiciously small (") << wsize << _(" bytes). It may be corrupt.\n");
|
||||
printf("WARNING: wallet.dat is only %llu bytes - possibly corrupt!\n", (unsigned long long)wsize);
|
||||
}
|
||||
AutoBackupWallet(walletPath);
|
||||
}
|
||||
}
|
||||
|
||||
DBErrors nLoadWalletRet = pwalletMain->LoadWallet(fFirstRun);
|
||||
if (nLoadWalletRet != DB_LOAD_OK)
|
||||
{
|
||||
|
||||
+183
-40
@@ -116,8 +116,10 @@ static CCriticalSection cs_PostIbdWork;
|
||||
static bool fPostIbdWorkStarted = false;
|
||||
|
||||
static const unsigned int MAX_HEADER_SYNC_CACHE = 50000;
|
||||
static const unsigned int HEADER_DOWNLOAD_WINDOW = 128;
|
||||
static const unsigned int HEADER_DOWNLOAD_WINDOW = 512; // Increased from 128 for parallel downloads
|
||||
static const unsigned int HEADER_DOWNLOAD_PER_PEER = 64; // Max blocks to request from each peer
|
||||
static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 30 * 1000000;
|
||||
static const int64_t HEADER_REDUNDANT_REQUEST_MICROS = 10 * 1000000; // Request from another peer after 10s
|
||||
|
||||
static void ThreadPostIbdWork(void* parg)
|
||||
{
|
||||
@@ -405,6 +407,89 @@ static void ContinueHeaderSync(CNode* pfrom, const uint256& hashTip)
|
||||
pfrom->PushMessage("getheaders", locator, uint256(0));
|
||||
}
|
||||
|
||||
// Parallel block downloading: distribute blocks across all available peers
|
||||
static unsigned int QueueHeaderSyncBlocksParallel(unsigned int nWindow)
|
||||
{
|
||||
if (hashBestHeaderSync == 0)
|
||||
return 0;
|
||||
|
||||
const std::vector<uint256> vPath = GetHeaderSyncDownloadPath(hashBestHeaderSync);
|
||||
if (vPath.empty())
|
||||
return 0;
|
||||
|
||||
// Collect eligible peers
|
||||
std::vector<CNode*> vEligiblePeers;
|
||||
{
|
||||
LOCK(cs_vNodes);
|
||||
for (CNode* pnode : vNodes)
|
||||
{
|
||||
if (!pnode->fClient && pnode->nVersion != 0 && !pnode->fDisconnect)
|
||||
vEligiblePeers.push_back(pnode);
|
||||
}
|
||||
}
|
||||
|
||||
if (vEligiblePeers.empty())
|
||||
return 0;
|
||||
|
||||
const int64_t nNow = GetTime() * 1000000;
|
||||
unsigned int nInFlight = CountHeaderSyncInFlight();
|
||||
unsigned int nQueued = 0;
|
||||
unsigned int nPeerIndex = 0;
|
||||
|
||||
// Distribute blocks across peers in round-robin fashion
|
||||
for (std::vector<uint256>::const_iterator it = vPath.begin(); it != vPath.end(); ++it)
|
||||
{
|
||||
if (nInFlight + nQueued >= nWindow)
|
||||
break;
|
||||
|
||||
std::map<uint256, CHeaderSyncNode>::iterator mi = mapHeaderSync.find(*it);
|
||||
if (mi == mapHeaderSync.end())
|
||||
continue;
|
||||
|
||||
// Check if already requested recently
|
||||
bool fNeedsRequest = false;
|
||||
if (!mi->second.fRequested)
|
||||
{
|
||||
// Never requested - request now
|
||||
fNeedsRequest = true;
|
||||
}
|
||||
else if (nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
{
|
||||
// Timeout expired - retry
|
||||
fNeedsRequest = true;
|
||||
}
|
||||
else if (nNow - mi->second.nLastRequestTime >= HEADER_REDUNDANT_REQUEST_MICROS)
|
||||
{
|
||||
// Redundant request: ask another peer if original is slow
|
||||
// This creates parallel downloads for slow blocks
|
||||
fNeedsRequest = true;
|
||||
}
|
||||
|
||||
if (!fNeedsRequest)
|
||||
continue;
|
||||
|
||||
// Round-robin across peers to distribute load
|
||||
CNode* pnode = vEligiblePeers[nPeerIndex % vEligiblePeers.size()];
|
||||
pnode->AskFor(CInv(MSG_BLOCK, *it));
|
||||
|
||||
// Update tracking (only on first request, not redundant)
|
||||
if (!mi->second.fRequested || nNow - mi->second.nLastRequestTime >= HEADER_REQUEST_TIMEOUT_MICROS)
|
||||
{
|
||||
mi->second.fRequested = true;
|
||||
mi->second.nLastRequestTime = nNow;
|
||||
}
|
||||
|
||||
++nQueued;
|
||||
++nPeerIndex;
|
||||
}
|
||||
|
||||
if (nQueued > 0)
|
||||
printf("IBD-DIAG: parallel queue distributed %u blocks across %zu peers (window=%u, inflight=%u)\n",
|
||||
nQueued, vEligiblePeers.size(), nWindow, nInFlight);
|
||||
|
||||
return nQueued;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1322,6 +1407,48 @@ uint256 WantedByOrphan(const CBlock* pblockOrphan)
|
||||
return pblockOrphan->hashPrevBlock;
|
||||
}
|
||||
|
||||
// Evict excess orphan blocks when limit is exceeded
|
||||
// Returns number of orphans evicted
|
||||
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans)
|
||||
{
|
||||
unsigned int nEvicted = 0;
|
||||
while (mapOrphanBlocks.size() > nMaxOrphans)
|
||||
{
|
||||
// Evict a random orphan
|
||||
uint256 randomhash = GetRandHash();
|
||||
auto it = mapOrphanBlocks.lower_bound(randomhash);
|
||||
if (it == mapOrphanBlocks.end())
|
||||
it = mapOrphanBlocks.begin();
|
||||
|
||||
if (it == mapOrphanBlocks.end())
|
||||
break; // No orphans to evict
|
||||
|
||||
CBlock* pblockEvict = it->second;
|
||||
uint256 evictHash = it->first;
|
||||
|
||||
// Remove from by-prev index
|
||||
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
|
||||
range.first != range.second; ++range.first)
|
||||
{
|
||||
if (range.first->second == pblockEvict) {
|
||||
mapOrphanBlocksByPrev.erase(range.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
|
||||
delete pblockEvict;
|
||||
mapOrphanBlocks.erase(evictHash);
|
||||
nEvicted++;
|
||||
}
|
||||
|
||||
if (nEvicted > 0)
|
||||
printf("LimitOrphanBlocks: evicted %u orphan(s), %u remain\n",
|
||||
nEvicted, (unsigned int)mapOrphanBlocks.size());
|
||||
|
||||
return nEvicted;
|
||||
}
|
||||
|
||||
// miner's coin base reward
|
||||
int64_t GetProofOfWorkReward(int64_t nFees)
|
||||
{
|
||||
@@ -1415,9 +1542,13 @@ static unsigned int GetNextTargetRequired_(const CBlockIndex* pindexLast, bool f
|
||||
return bnTargetLimit.GetCompact(); // genesis block
|
||||
|
||||
const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
|
||||
if (pindexPrev == NULL)
|
||||
return bnTargetLimit.GetCompact(); // no previous block of this type
|
||||
if (pindexPrev->pprev == NULL)
|
||||
return bnTargetLimit.GetCompact(); // first block
|
||||
const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
|
||||
if (pindexPrevPrev == NULL)
|
||||
return bnTargetLimit.GetCompact(); // no second previous block of this type
|
||||
if (pindexPrevPrev->pprev == NULL)
|
||||
return bnTargetLimit.GetCompact(); // second block
|
||||
|
||||
@@ -2324,7 +2455,15 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck)
|
||||
|
||||
bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
{
|
||||
printf("REORGANIZE\n");
|
||||
printf("REORGANIZE: Switching chains\n");
|
||||
printf(" Old tip: %s height %d trust %s\n",
|
||||
pindexBest->GetBlockHash().ToString().substr(0,20).c_str(),
|
||||
pindexBest->nHeight,
|
||||
CBigNum(pindexBest->nChainTrust).ToString().c_str());
|
||||
printf(" New tip: %s height %d trust %s\n",
|
||||
pindexNew->GetBlockHash().ToString().substr(0,20).c_str(),
|
||||
pindexNew->nHeight,
|
||||
CBigNum(pindexNew->nChainTrust).ToString().c_str());
|
||||
|
||||
// Find the fork
|
||||
CBlockIndex* pfork = pindexBest;
|
||||
@@ -2351,8 +2490,17 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
vConnect.push_back(pindex);
|
||||
reverse(vConnect.begin(), vConnect.end());
|
||||
|
||||
printf("REORGANIZE: Disconnect %" PRIszu " blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str());
|
||||
printf("REORGANIZE: Connect %" PRIszu " blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str());
|
||||
printf("REORGANIZE: Fork point at height %d: %s\n",
|
||||
pfork->nHeight,
|
||||
pfork->GetBlockHash().ToString().substr(0,20).c_str());
|
||||
printf("REORGANIZE: Disconnect %" PRIszu " blocks (heights %d..%d)\n",
|
||||
vDisconnect.size(),
|
||||
pfork->nHeight + 1,
|
||||
pindexBest->nHeight);
|
||||
printf("REORGANIZE: Connect %" PRIszu " blocks (heights %d..%d)\n",
|
||||
vConnect.size(),
|
||||
pfork->nHeight + 1,
|
||||
pindexNew->nHeight);
|
||||
|
||||
// Disconnect shorter branch
|
||||
vector<CTransaction> vResurrect;
|
||||
@@ -2370,12 +2518,6 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
vResurrect.push_back(tx);
|
||||
}
|
||||
|
||||
// Remove disconnected PoS blocks from setStakeSeen so they don't
|
||||
// block acceptance of valid blocks on the winning chain.
|
||||
for (CBlockIndex* pindex : vDisconnect)
|
||||
if (pindex->IsProofOfStake())
|
||||
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
|
||||
|
||||
// Connect longer branch
|
||||
vector<CTransaction> vDelete;
|
||||
for (unsigned int i = 0; i < vConnect.size(); i++)
|
||||
@@ -2403,19 +2545,37 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
if (!txdb.TxnCommit())
|
||||
return error("Reorganize() : TxnCommit failed");
|
||||
|
||||
// Disconnect shorter branch
|
||||
// ======================================================================
|
||||
// CRITICAL: All operations below this point must be in-memory only and
|
||||
// should never fail. The DB transaction is committed, so we cannot abort.
|
||||
// ======================================================================
|
||||
|
||||
// Disconnect shorter branch (in-memory only)
|
||||
for (CBlockIndex* pindex : vDisconnect)
|
||||
if (pindex->pprev)
|
||||
pindex->pprev->pnext = NULL;
|
||||
|
||||
// Connect longer branch
|
||||
// Connect longer branch (in-memory only)
|
||||
for (CBlockIndex* pindex : vConnect)
|
||||
if (pindex->pprev)
|
||||
pindex->pprev->pnext = pindex;
|
||||
|
||||
// Remove disconnected PoS blocks from setStakeSeen so they don't
|
||||
// block acceptance of valid blocks on the winning chain.
|
||||
// This MUST happen after commit to maintain consistency.
|
||||
for (CBlockIndex* pindex : vDisconnect)
|
||||
if (pindex->IsProofOfStake())
|
||||
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
|
||||
|
||||
// Resurrect memory transactions that were in the disconnected branch
|
||||
unsigned int nResurrected = 0;
|
||||
for (CTransaction& tx : vResurrect)
|
||||
tx.AcceptToMemoryPool(txdb, false);
|
||||
{
|
||||
if (tx.AcceptToMemoryPool(txdb, false))
|
||||
nResurrected++;
|
||||
}
|
||||
if (nResurrected > 0)
|
||||
printf("REORGANIZE: resurrected %u transactions to mempool\n", nResurrected);
|
||||
|
||||
// Delete redundant memory transactions that are in the connected branch
|
||||
for (CTransaction& tx : vDelete) {
|
||||
@@ -2423,7 +2583,8 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
mempool.removeConflicts(tx);
|
||||
}
|
||||
|
||||
printf("REORGANIZE: done\n");
|
||||
printf("REORGANIZE: done (fork at height %d, %zu disconnected, %zu connected)\n",
|
||||
pfork->nHeight, vDisconnect.size(), vConnect.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2613,6 +2774,9 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
|
||||
{
|
||||
printf("*** Initial block download complete at height %d ***\n", nBestHeight);
|
||||
|
||||
// Trim orphan blocks to normal limit now that IBD is done
|
||||
LimitOrphanBlocks(MAX_ORPHAN_BLOCKS);
|
||||
|
||||
// Update wallet best chain locator now that IBD is done
|
||||
const CBlockLocator locator(pindexBest);
|
||||
::SetBestChain(locator);
|
||||
@@ -3144,29 +3308,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
// Allow more orphans during IBD so out-of-order blocks from parallel
|
||||
// downloads don't get evicted and re-requested.
|
||||
unsigned int nMaxOrphans = IsInitialBlockDownload() ? MAX_ORPHAN_BLOCKS_IBD : MAX_ORPHAN_BLOCKS;
|
||||
if (mapOrphanBlocks.size() > nMaxOrphans)
|
||||
{
|
||||
// Evict a random orphan
|
||||
uint256 randomhash = GetRandHash();
|
||||
auto it = mapOrphanBlocks.lower_bound(randomhash);
|
||||
if (it == mapOrphanBlocks.end())
|
||||
it = mapOrphanBlocks.begin();
|
||||
CBlock* pblockEvict = it->second;
|
||||
uint256 evictHash = it->first;
|
||||
// Remove from by-prev index
|
||||
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
|
||||
range.first != range.second; ++range.first)
|
||||
{
|
||||
if (range.first->second == pblockEvict) {
|
||||
mapOrphanBlocksByPrev.erase(range.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
|
||||
delete pblockEvict;
|
||||
mapOrphanBlocks.erase(evictHash);
|
||||
printf("ProcessBlock: orphan eviction, %u orphans remain\n", (unsigned int)mapOrphanBlocks.size());
|
||||
}
|
||||
LimitOrphanBlocks(nMaxOrphans);
|
||||
|
||||
// Ask this guy to fill in what we're missing
|
||||
if (pfrom && pindexBest)
|
||||
@@ -3212,9 +3354,10 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
||||
if (nBestHeight % 5000 == 0 || !IsInitialBlockDownload())
|
||||
printf("ProcessBlock: ACCEPTED block %d\n", nBestHeight);
|
||||
|
||||
if (pfrom && hashBestHeaderSync != 0)
|
||||
if (hashBestHeaderSync != 0)
|
||||
{
|
||||
const unsigned int nQueued = QueueHeaderSyncBlocks(pfrom, HEADER_DOWNLOAD_WINDOW);
|
||||
// Use parallel queue to distribute across all peers
|
||||
const unsigned int nQueued = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
if (nQueued > 0)
|
||||
printf("IBD-DIAG: queued %u more blocks from header planner after accepting %s\n",
|
||||
nQueued, hash.ToString().substr(0,20).c_str());
|
||||
@@ -4523,7 +4666,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
|
||||
|
||||
int nRequested = 0;
|
||||
if (hashBestHeaderSync != 0)
|
||||
nRequested = QueueHeaderSyncBlocks(pfrom, HEADER_DOWNLOAD_WINDOW);
|
||||
nRequested = QueueHeaderSyncBlocksParallel(HEADER_DOWNLOAD_WINDOW);
|
||||
|
||||
if (nNewHeaders > 0 || nRequested > 0)
|
||||
printf("IBD-DIAG: accepted %d new headers, queued %d blocks from %zu headers (peer=%s bestHeader=%s)\n",
|
||||
|
||||
+2
-1
@@ -37,7 +37,7 @@ static const unsigned int MAX_BLOCK_SIZE = 1000000;
|
||||
static const unsigned int MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2;
|
||||
static const unsigned int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50;
|
||||
static const unsigned int MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100;
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS = 750;
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS = 2000;
|
||||
static const unsigned int MAX_ORPHAN_BLOCKS_IBD = 4000;
|
||||
static const unsigned int MAX_INV_SZ = 50000;
|
||||
static const int64_t MIN_TX_FEE = (1 * CENT) / 100;
|
||||
@@ -136,6 +136,7 @@ bool IsInitialBlockDownload();
|
||||
std::string GetWarnings(std::string strFor);
|
||||
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
|
||||
uint256 WantedByOrphan(const CBlock* pblockOrphan);
|
||||
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans);
|
||||
const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake);
|
||||
void StakeMiner(CWallet *pwallet);
|
||||
void ResendWalletTransactions(bool fForce = false);
|
||||
|
||||
@@ -28,6 +28,9 @@ double GetDifficulty(const CBlockIndex* blockindex)
|
||||
blockindex = GetLastBlockIndex(pindexBest, false);
|
||||
}
|
||||
|
||||
if (blockindex == NULL)
|
||||
return 1.0;
|
||||
|
||||
int nShift = (blockindex->nBits >> 24) & 0xff;
|
||||
|
||||
double dDiff =
|
||||
@@ -360,6 +363,49 @@ Value gettxoutsetinfo(const Array& params, bool fHelp)
|
||||
return obj;
|
||||
}
|
||||
|
||||
Value recalculatesupply(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 0)
|
||||
throw runtime_error(
|
||||
"recalculatesupply\n"
|
||||
"Recalculates the money supply by summing all unspent transaction outputs.\n"
|
||||
"Updates the stored money supply value at the chain tip and persists it to disk.\n"
|
||||
"Returns the old and new supply values for comparison.\n"
|
||||
"\nWARNING: This modifies blockchain index state. Only use if money supply is incorrect.");
|
||||
|
||||
if (!pindexBest)
|
||||
throw runtime_error("recalculatesupply: no best block");
|
||||
|
||||
int nUtxoCount = 0;
|
||||
CTxDB txdb;
|
||||
int64_t nCalculatedSupply = txdb.SumUtxoValues(nUtxoCount);
|
||||
int64_t nOldSupply = pindexBest->nMoneySupply;
|
||||
int64_t nDifference = nCalculatedSupply - nOldSupply;
|
||||
|
||||
// Sanity check: difference should be reasonable (not millions of TRI)
|
||||
// Max supply is 2,222,222 TRI, so any difference > 1M TRI is suspicious
|
||||
if (abs64(nDifference) > 1000000 * COIN)
|
||||
throw runtime_error(strprintf(
|
||||
"recalculatesupply: calculated supply differs by %s TRI - this is abnormal, refusing to update",
|
||||
FormatMoney(abs64(nDifference)).c_str()));
|
||||
|
||||
// Update the chain tip's money supply
|
||||
pindexBest->nMoneySupply = nCalculatedSupply;
|
||||
|
||||
// Persist to LevelDB
|
||||
CTxDB txdbWrite;
|
||||
if (!txdbWrite.WriteBlockIndex(CDiskBlockIndex(pindexBest)))
|
||||
throw runtime_error("recalculatesupply: failed to write updated block index");
|
||||
|
||||
Object result;
|
||||
result.push_back(Pair("height", (int)nBestHeight));
|
||||
result.push_back(Pair("old_supply", ValueFromAmount(nOldSupply)));
|
||||
result.push_back(Pair("new_supply", ValueFromAmount(nCalculatedSupply)));
|
||||
result.push_back(Pair("difference", ValueFromAmount(nDifference)));
|
||||
result.push_back(Pair("utxo_count", nUtxoCount));
|
||||
return result;
|
||||
}
|
||||
|
||||
// triangles: get information of sync-checkpoint
|
||||
Value getcheckpoint(const Array& params, bool fHelp)
|
||||
{
|
||||
|
||||
+32
-1
@@ -78,7 +78,8 @@ Value getstakinginfo(const Array& params, bool fHelp)
|
||||
if (fHelp || params.size() != 0)
|
||||
throw runtime_error(
|
||||
"getstakinginfo\n"
|
||||
"Returns an object containing staking-related information.");
|
||||
"Returns an object containing staking-related information.\n"
|
||||
"Includes diagnostic details about why staking may be disabled.");
|
||||
|
||||
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
|
||||
pwalletMain->GetStakeWeight(*pwalletMain, nMinWeight, nMaxWeight, nWeight);
|
||||
@@ -87,6 +88,26 @@ Value getstakinginfo(const Array& params, bool fHelp)
|
||||
bool staking = nLastCoinStakeSearchInterval && nWeight;
|
||||
int nExpectedTime = staking ? (nTargetSpacing * nNetworkWeight / nWeight) : -1;
|
||||
|
||||
// Diagnostic: determine why staking might be disabled
|
||||
Array stakingDisabledReasons;
|
||||
if (!GetBoolArg("-staking", true))
|
||||
stakingDisabledReasons.push_back("staking disabled via -staking=0 flag");
|
||||
|
||||
if (pwalletMain->IsLocked())
|
||||
stakingDisabledReasons.push_back("wallet is locked (use walletpassphrase <pw> <timeout> true)");
|
||||
|
||||
if (vNodes.empty())
|
||||
stakingDisabledReasons.push_back("no network connections (need at least 1 peer)");
|
||||
|
||||
if (IsInitialBlockDownload())
|
||||
stakingDisabledReasons.push_back("initial block download in progress");
|
||||
|
||||
if (nWeight == 0)
|
||||
stakingDisabledReasons.push_back("no mature coins available (coins need 520 confirmations)");
|
||||
|
||||
if (!staking && nLastCoinStakeSearchInterval == 0)
|
||||
stakingDisabledReasons.push_back("stake miner thread not running");
|
||||
|
||||
Object obj;
|
||||
|
||||
obj.push_back(Pair("enabled", GetBoolArg("-staking", true)));
|
||||
@@ -105,6 +126,16 @@ Value getstakinginfo(const Array& params, bool fHelp)
|
||||
|
||||
obj.push_back(Pair("expectedtime", nExpectedTime));
|
||||
|
||||
// Add detailed diagnostics
|
||||
obj.push_back(Pair("walletlocked", pwalletMain->IsLocked()));
|
||||
obj.push_back(Pair("walletunlockedforstakingonly", pwalletMain->fWalletUnlockStakingOnly));
|
||||
obj.push_back(Pair("connections", (int)vNodes.size()));
|
||||
obj.push_back(Pair("initialblockdownload", IsInitialBlockDownload()));
|
||||
obj.push_back(Pair("maturecoins", nWeight > 0));
|
||||
|
||||
if (!stakingDisabledReasons.empty())
|
||||
obj.push_back(Pair("staking_disabled_reasons", stakingDisabledReasons));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -317,6 +317,7 @@ static const CRPCCommand vRPCCommands[] =
|
||||
{ "getchaintips", &getchaintips, true, false },
|
||||
{ "invalidateblock", &invalidateblock, false, false },
|
||||
{ "reconsiderblock", &reconsiderblock, false, false },
|
||||
{ "recalculatesupply", &recalculatesupply, false, false },
|
||||
{ "reservebalance", &reservebalance, false, true},
|
||||
{ "checkwallet", &checkwallet, false, true},
|
||||
{ "repairwallet", &repairwallet, false, true},
|
||||
|
||||
@@ -225,6 +225,7 @@ extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool f
|
||||
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value recalculatesupply(const json_spirit::Array& params, bool fHelp);
|
||||
|
||||
extern json_spirit::Value getaddressbalance(const json_spirit::Array& params, bool fHelp);
|
||||
extern json_spirit::Value getaddressutxos(const json_spirit::Array& params, bool fHelp);
|
||||
|
||||
+35
-1
@@ -162,7 +162,9 @@ bool CTxDB::TxnCommit()
|
||||
delete activeBatch;
|
||||
activeBatch = NULL;
|
||||
if (!status.ok()) {
|
||||
printf("LevelDB batch commit failure: %s\n", status.ToString().c_str());
|
||||
printf("ERROR: LevelDB batch commit failure: %s\n", status.ToString().c_str());
|
||||
printf("ERROR: This may indicate disk full, corruption, or permissions issue.\n");
|
||||
printf("ERROR: Chain state may be inconsistent - immediate investigation required!\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -904,3 +906,35 @@ bool CTxDB::HaveUtxo(const uint256& hash, unsigned int n)
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t CTxDB::SumUtxoValues(int& nCount)
|
||||
{
|
||||
nCount = 0;
|
||||
int64_t nTotal = 0;
|
||||
|
||||
// Seek to the start of UTXO entries (key prefix "u")
|
||||
CDataStream ssKeyPrefix(SER_DISK, CLIENT_VERSION);
|
||||
ssKeyPrefix << make_pair(string("u"), make_pair(uint256(0), (unsigned int)0));
|
||||
std::string strPrefixBegin = ssKeyPrefix.str();
|
||||
|
||||
leveldb::Iterator* it = pdb->NewIterator(leveldb::ReadOptions());
|
||||
for (it->Seek(strPrefixBegin); it->Valid(); it->Next())
|
||||
{
|
||||
// Check key prefix is still "u"
|
||||
CDataStream ssKey(it->key().data(), it->key().data() + it->key().size(), SER_DISK, CLIENT_VERSION);
|
||||
std::string strKeyType;
|
||||
ssKey >> strKeyType;
|
||||
if (strKeyType != "u")
|
||||
break;
|
||||
|
||||
// Deserialize the UTXO entry and sum the value
|
||||
CDataStream ssValue(it->value().data(), it->value().data() + it->value().size(), SER_DISK, CLIENT_VERSION);
|
||||
CUtxoEntry entry;
|
||||
ssValue >> entry;
|
||||
|
||||
nTotal += entry.nValue;
|
||||
nCount++;
|
||||
}
|
||||
delete it;
|
||||
return nTotal;
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ public:
|
||||
bool WriteUtxo(const uint256& hash, unsigned int n, const CUtxoEntry& entry);
|
||||
bool EraseUtxo(const uint256& hash, unsigned int n);
|
||||
bool HaveUtxo(const uint256& hash, unsigned int n);
|
||||
int64_t SumUtxoValues(int& nCount);
|
||||
|
||||
private:
|
||||
bool LoadBlockIndexGuts();
|
||||
|
||||
+2
-2
@@ -41,10 +41,10 @@ const std::string CLIENT_NAME("Cryptographic Triangles");
|
||||
#endif
|
||||
|
||||
#define BUILD_DESC_FROM_COMMIT(maj,min,rev,build,commit) \
|
||||
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) "" commit
|
||||
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "" commit
|
||||
|
||||
#define BUILD_DESC_FROM_UNKNOWN(maj,min,rev,build) \
|
||||
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) "." DO_STRINGIZE(build) ""
|
||||
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min) "." DO_STRINGIZE(rev) ""
|
||||
|
||||
#ifndef BUILD_DESC
|
||||
# ifdef GIT_COMMIT_ID
|
||||
|
||||
@@ -16,6 +16,40 @@ namespace fs = boost::filesystem;
|
||||
static uint64_t nAccountingEntryNumber = 0;
|
||||
extern bool fWalletUnlockStakingOnly;
|
||||
|
||||
//
|
||||
// Auto-backup wallet before flush/rewrite operations.
|
||||
// Copies wallet.dat to wallet.dat.auto.bak if the backup is older than the wallet.
|
||||
// Returns true if backup was created or already up to date.
|
||||
//
|
||||
bool AutoBackupWallet(const fs::path& walletPath)
|
||||
{
|
||||
fs::path backupPath = walletPath.string() + ".auto.bak";
|
||||
try {
|
||||
// Only back up if wallet exists and is non-trivial (>1KB)
|
||||
if (!fs::exists(walletPath))
|
||||
return true;
|
||||
uintmax_t walletSize = fs::file_size(walletPath);
|
||||
if (walletSize < 1024) {
|
||||
printf("AutoBackupWallet: wallet.dat is only %llu bytes (possibly corrupt), skipping auto-backup\n",
|
||||
(unsigned long long)walletSize);
|
||||
return false;
|
||||
}
|
||||
// Skip if backup exists and is same size (already backed up this version)
|
||||
if (fs::exists(backupPath)) {
|
||||
uintmax_t backupSize = fs::file_size(backupPath);
|
||||
if (backupSize == walletSize)
|
||||
return true;
|
||||
}
|
||||
fs::copy_file(walletPath, backupPath, fs::copy_options::overwrite_existing);
|
||||
printf("AutoBackupWallet: backed up wallet.dat (%llu bytes) to wallet.dat.auto.bak\n",
|
||||
(unsigned long long)walletSize);
|
||||
return true;
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
printf("AutoBackupWallet: failed - %s\n", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CWalletDB
|
||||
//
|
||||
@@ -580,6 +614,10 @@ void ThreadFlushWalletDB(void* parg)
|
||||
nLastFlushed = nWalletDBUpdated;
|
||||
int64_t nStart = GetTimeMillis();
|
||||
|
||||
// Auto-backup before flush (protects against corruption)
|
||||
fs::path walletPath = GetDataDir() / strFile;
|
||||
AutoBackupWallet(walletPath);
|
||||
|
||||
// Flush wallet.dat so it's self contained
|
||||
bitdb.CloseDb(strFile);
|
||||
bitdb.CheckpointLSN(strFile);
|
||||
|
||||
Reference in New Issue
Block a user