diff --git a/CMakeLists.txt b/CMakeLists.txt index d18ea49..40e2e2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(POLICY CMP0167) endif() project(Triangles - VERSION 5.9.3 + VERSION 5.9.5 DESCRIPTION "Cryptographic Triangles Wallet" LANGUAGES C CXX ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 26ba6be..fb87988 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,7 @@ set(CORE_SOURCES txdb-base.cpp txdb-leveldb.cpp utxosnapshot.cpp + snapshotnet.cpp lz4/lz4.c tor/onion_v3.cpp tor/tor_process.cpp diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index 5de2f73..ae2be2c 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -42,6 +42,21 @@ namespace Checkpoints {2209000, uint256("0x04c78a6fc863bed918a9364c58c64489943b2e85d84ddb1ac2fba584f390d5dc")}, }; + // Published UTXO snapshot file SHA256, keyed by snapshot height. + // Each entry binds height -> SHA256 of the canonical snapshot file produced by + // UtxoSnapshot::DumpSnapshot at that height. Used by SnapshotNet to verify + // P2P-delivered snapshots without trusting any peer. + // + // Maintainers: after producing a snapshot, sha256 the file and add an entry + // here. The corresponding (height, blockhash) must already exist in + // mapCheckpoints / mapCheckpointsTestnet. + static std::map mapSnapshotHashes = { + // {2186940, uint256("0x...sha256-of-utxo-snapshot.bin...")}, + }; + + static std::map mapSnapshotHashesTestnet = { + }; + static MapCheckpoints mapCheckpointsTestnet = { { 0, hashGenesisBlockTestNet }, { 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")}, @@ -89,6 +104,22 @@ namespace Checkpoints return checkpoints.rbegin()->first; } + int GetBestSnapshotHeight() + { + std::map& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes); + if (snaps.empty()) return 0; + return snaps.rbegin()->first; + } + + bool GetSnapshotHash(int nHeight, uint256& fileHashOut) + { + std::map& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes); + auto it = snaps.find(nHeight); + if (it == snaps.end()) return false; + fileHashOut = it->second; + return true; + } + CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex) { MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints); diff --git a/src/checkpoints.h b/src/checkpoints.h index 24c222b..d91d2e6 100644 --- a/src/checkpoints.h +++ b/src/checkpoints.h @@ -45,6 +45,11 @@ namespace Checkpoints // Return conservative estimate of total number of blocks, 0 if unknown int GetTotalBlocksEstimate(); + // Return the highest checkpoint height that has a published UTXO snapshot + // hash, along with the snapshot's file SHA256. Returns 0 height if none. + int GetBestSnapshotHeight(); + bool GetSnapshotHash(int nHeight, uint256& fileHashOut); + // Returns last CBlockIndex* in mapBlockIndex that is a checkpoint CBlockIndex* GetLastCheckpoint(const std::map& mapBlockIndex); diff --git a/src/clientversion.h b/src/clientversion.h index 833ff3c..c054818 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -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 9 -#define CLIENT_VERSION_REVISION 3 +#define CLIENT_VERSION_REVISION 5 #define CLIENT_VERSION_BUILD 0 // Converts the parameter X to a string after macro replacement on X has been performed. diff --git a/src/init.cpp b/src/init.cpp index 379dd2a..39b919c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -15,6 +15,7 @@ #include "openssl_compat.h" #include "bootstrap.h" #include "utxosnapshot.h" +#include "snapshotnet.h" #include "tor/tor_embedded.h" #include "tor/onion_v3.h" #include "tor/tor_process.h" @@ -108,6 +109,31 @@ bool ShutdownRequested() return fRequestShutdown; } +// P2P UTXO snapshot fetcher. Started from AppInit2 step 11.6 when the chain +// is empty and snapshot mode is enabled. Saves utxo-snapshot.bin on success +// and requests shutdown so a fresh boot can load it via Step 6c. +static void ThreadSnapshotFetch(void* parg) +{ + RenameThread("Triangles-snapfetch"); + // Give peers ~30s to connect and complete version handshake. + for (int i = 0; i < 30 && !fRequestShutdown; ++i) + MilliSleep(1000); + if (fRequestShutdown) return; + + int snapTimeoutSec = (int)GetArg("-snapshottimeout", 600); + printf("SnapshotNet: starting P2P snapshot fetch (timeout=%ds)...\n", snapTimeoutSec); + + std::string err; + if (SnapshotNet::TryFetchSnapshot(GetDataDir(), snapTimeoutSec, err)) { + printf("SnapshotNet: snapshot saved. Shutting down — restart the daemon to load it.\n"); + uiInterface.InitMessage(_("UTXO snapshot saved. Restart the node to load it.")); + StartShutdown(); + } else { + printf("SnapshotNet: P2P snapshot fetch failed: %s\n", err.c_str()); + printf("SnapshotNet: falling back to genesis sync. Use -bootstrap for legacy HTTP fallback.\n"); + } +} + void ThreadDeferredStartup(void* parg) { // Make this thread recognisable as the deferred startup worker. @@ -888,17 +914,25 @@ bool AppInit2() // ********************************************************* Step 6b: bootstrap download (daemon) // Automatic: if data dir has no blockchain, bootstrap without asking. // Can also be forced with -bootstrap flag, or disabled with -nobootstrap. + // + // v5.9.5: P2P UTXO snapshot fetch is the default for fresh installs (Step 11.6). + // The legacy clearnet HTTP bootstrap only runs when the user explicitly requests + // it via -bootstrap, or when -snapshot=0 disables the P2P fetcher. #ifndef QT_GUI { bool wantsBootstrap = GetBoolArg("-bootstrap", false); bool noBootstrap = GetBoolArg("-nobootstrap", false); + bool snapshotMode = GetBoolArg("-snapshot", true); fs::path dataPath = GetDataDir(); bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath); - if (needsBootstrap && !noBootstrap) { + if (needsBootstrap && !noBootstrap && !snapshotMode) { printf("Bootstrap: no blockchain data found — downloading automatically.\n"); printf("Bootstrap: (use -nobootstrap to skip)\n"); wantsBootstrap = true; + } else if (needsBootstrap && snapshotMode && !wantsBootstrap) { + printf("Bootstrap: no blockchain data found — will fetch UTXO snapshot via P2P after network start.\n"); + printf("Bootstrap: (use -bootstrap for legacy clearnet HTTP bootstrap, or -snapshot=0 to disable P2P fetcher)\n"); } if (wantsBootstrap) @@ -1432,6 +1466,24 @@ bool AppInit2() if (fServer) NewThread(ThreadRPCServer, NULL); + // ********************************************************* Step 11.6: P2P UTXO snapshot fetch + // If the chain is empty and snapshot mode is enabled (default), spawn a + // background thread that waits for snapshot-capable peers, downloads the + // canonical snapshot via P2P, and saves it to utxo-snapshot.bin. On + // success, requests a clean shutdown so the user can restart and have + // Step 6c load the snapshot in a fresh boot. + { + bool snapshotMode = GetBoolArg("-snapshot", true); + bool needsSnapshot = (nBestHeight <= 0); + bool haveSnapshotFile = fs::exists(GetDataDir() / "utxo-snapshot.bin"); + + if (snapshotMode && needsSnapshot && !haveSnapshotFile && + Checkpoints::GetBestSnapshotHeight() > 0) + { + NewThread(ThreadSnapshotFetch, NULL); + } + } + { LOCK(cs_DeferredStartup); fDeferredStartupRunning = true; diff --git a/src/main.cpp b/src/main.cpp index 5f6ff60..aaae558 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #endif #include "notificationqueue.h" #include "addressindex.h" +#include "snapshotnet.h" #include #include #include @@ -136,8 +137,9 @@ static CCriticalSection cs_PostIbdWork; static bool fPostIbdWorkStarted = false; static const unsigned int MAX_HEADER_SYNC_CACHE = 15000; -static const unsigned int HEADER_DOWNLOAD_WINDOW = 512; // Increased from 128 for parallel downloads +static const unsigned int HEADER_DOWNLOAD_WINDOW = 1024; // Wider pipeline for multi-peer parallel IBD static const unsigned int HEADER_DOWNLOAD_PER_PEER = 32; // Reduced from 64 for Tor circuit stability +static const size_t HEADER_REDUNDANT_PEER_THRESHOLD = 4; // Only do dual-peer redundancy when peer count is below this static const unsigned int HEADER_SYNC_LOW_WATER = HEADER_DOWNLOAD_WINDOW / 4; static const unsigned int HEADER_SYNC_TARGET_INFLIGHT = HEADER_DOWNLOAD_WINDOW / 2; static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 60 * 1000000; // 60s for Tor latency (was 30s) @@ -177,6 +179,12 @@ static void ThreadPostIbdWork(void* parg) SecureMsgScanBlockChain(); printf("Post-IBD secure message chain scan complete\n"); } + + // If a canonical UTXO snapshot file is present in the data dir and its + // hash matches the compiled-in snapshot hash for this height, advertise + // NODE_SNAPSHOT so other peers can fetch it from us. + if (!fShutdown) + SnapshotNet::EnsureLocalSnapshot(); } catch (std::exception& e) { @@ -662,11 +670,15 @@ static unsigned int QueueHeaderSyncBlocksParallel(unsigned int nWindow) CNode* pnode = vWeightedPeers[nPeerIndex % vWeightedPeers.size()]; pnode->AskFor(CInv(MSG_BLOCK, *it)); - // During IBD with 2+ peers: also request from a second peer immediately. + // During IBD with few peers: also request from a second peer immediately. // Doubles bandwidth but halves worst-case latency when one peer is slow. - // The AlreadyHave() check in getdata construction automatically skips - // the duplicate once the first response arrives. - if (IsInitialBlockDownload() && vWeightedPeers.size() >= 2 && !mi->second.fRequested) + // When peer count is large, skip the redundancy and rely on adaptive-timeout + // retry instead — pure parallel distribution gives higher aggregate throughput + // and avoids burning Tor bandwidth on duplicate fetches. + if (IsInitialBlockDownload() && + vWeightedPeers.size() >= 2 && + vWeightedPeers.size() < HEADER_REDUNDANT_PEER_THRESHOLD && + !mi->second.fRequested) { CNode* pnode2 = vWeightedPeers[(nPeerIndex + 1) % vWeightedPeers.size()]; if (pnode2 != pnode) @@ -770,7 +782,7 @@ void static SetBestChain(const CBlockLocator& loc) pwallet->SetBestChain(loc); } -static bool UpdateAddressIndexSyncState(CTxDB& txdb, const CBlockIndex* pindexNew) +static bool UpdateAddressIndexSyncState(CTxDBBase& txdb, const CBlockIndex* pindexNew) { if (!fAddressIndex || pindexNew == NULL) return true; @@ -896,7 +908,7 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) // CTransaction and CTxIndex // -bool CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet) +bool CTransaction::ReadFromDisk(CTxDBBase& txdb, COutPoint prevout, CTxIndex& txindexRet) { SetNull(); if (!txdb.ReadTxIndex(prevout.hash, txindexRet)) @@ -911,7 +923,7 @@ bool CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txinde return true; } -bool CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout) +bool CTransaction::ReadFromDisk(CTxDBBase& txdb, COutPoint prevout) { CTxIndex txindex; return ReadFromDisk(txdb, prevout, txindex); @@ -1176,7 +1188,7 @@ int64_t CTransaction::GetMinFee(unsigned int nBlockSize, enum GetMinFee_mode mod } -bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, +bool CTxMemPool::accept(CTxDBBase& txdb, CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs) { if (pfMissingInputs) @@ -1338,7 +1350,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, return true; } -bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs) +bool CTransaction::AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs, bool* pfMissingInputs) { return mempool.accept(txdb, *this, fCheckInputs, pfMissingInputs); } @@ -1459,7 +1471,7 @@ int CMerkleTx::GetBlocksToMaturity() const } -bool CMerkleTx::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs) +bool CMerkleTx::AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs) { if (fClient) { @@ -1481,7 +1493,7 @@ bool CMerkleTx::AcceptToMemoryPool() -bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) +bool CWalletTx::AcceptWalletTransaction(CTxDBBase& txdb, bool fCheckInputs) { { @@ -1881,7 +1893,7 @@ void CBlock::UpdateTime(const CBlockIndex* pindexPrev) -bool CTransaction::DisconnectInputs(CTxDB& txdb) +bool CTransaction::DisconnectInputs(CTxDBBase& txdb) { // Remove transaction position index entry. // UTXO undo (restoring spent outputs, removing created outputs) is @@ -1892,7 +1904,7 @@ bool CTransaction::DisconnectInputs(CTxDB& txdb) } -bool CTransaction::FetchInputs(CTxDB& txdb, const MapPrevTx& mapPendingUtxos, +bool CTransaction::FetchInputs(CTxDBBase& txdb, const MapPrevTx& mapPendingUtxos, bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid) { // FetchInputs can return false either because we just haven't seen some inputs @@ -2045,7 +2057,7 @@ unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const return nSigOps; } -bool CTransaction::ConnectInputs(CTxDB& txdb, const MapPrevTx& inputs, +bool CTransaction::ConnectInputs(CTxDBBase& txdb, const MapPrevTx& inputs, const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, std::vector* pvChecks) { @@ -2214,7 +2226,7 @@ static bool GetAddressFromScript(const CScript& script, int& nType, uint160& has return false; } -bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) +bool CBlock::DisconnectBlock(CTxDBBase& txdb, CBlockIndex* pindex) { // Disconnect in reverse order for (int i = vtx.size()-1; i >= 0; i--) @@ -2343,7 +2355,7 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) +bool CBlock::ConnectBlock(CTxDBBase& txdb, CBlockIndex* pindex, bool fJustCheck) { // Check it again in case a previous version let a bad block in, but skip BlockSig checking if (!CheckBlock(!fJustCheck, !fJustCheck, false)) @@ -2672,7 +2684,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) return true; } -bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) +bool static Reorganize(CTxDBBase& txdb, CBlockIndex* pindexNew) { printf("REORGANIZE: Switching chains\n"); printf(" Old tip: %s height %d trust %s\n", @@ -2852,7 +2864,7 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Called from inside SetBestChain: attaches a block to the new best chain being built -bool CBlock::SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew) +bool CBlock::SetBestChainInner(CTxDBBase& txdb, CBlockIndex *pindexNew) { uint256 hash = GetHash(); @@ -2876,7 +2888,7 @@ bool CBlock::SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew) return true; } -bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) +bool CBlock::SetBestChain(CTxDBBase& txdb, CBlockIndex* pindexNew) { uint256 hash = GetHash(); @@ -3091,7 +3103,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) // guaranteed to be in main chain by sync-checkpoint. This rule is // introduced to help nodes establish a consistent view of the coin // age (trust score) of competing branches. -bool CTransaction::GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const +bool CTransaction::GetCoinAge(CTxDBBase& txdb, uint64_t& nCoinAge) const { CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds nCoinAge = 0; @@ -4439,7 +4451,7 @@ string GetWarnings(string strFor) // -bool static AlreadyHave(CTxDB& txdb, const CInv& inv) +bool static AlreadyHave(CTxDBBase& txdb, const CInv& inv) { switch (inv.type) { @@ -5566,6 +5578,13 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } + else if (strCommand == "getsnap" || strCommand == "snap" || + strCommand == "getsnapchunk" || strCommand == "snapchunk") + { + SnapshotNet::ProcessSnapshotMessage(pfrom, strCommand, vRecv); + } + + else if (strCommand == "alert") { CAlert alert; diff --git a/src/main.h b/src/main.h index e2a2e64..0546880 100644 --- a/src/main.h +++ b/src/main.h @@ -110,7 +110,7 @@ extern bool fEnforceCanonical; static const uint64_t nMinDiskSpace = 52428800; class CReserveKey; -class CTxDB; +class CTxDBBase; class CTxIndex; void RegisterWallet(CWallet* pwalletIn); @@ -721,10 +721,10 @@ public: } - bool ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet); - bool ReadFromDisk(CTxDB& txdb, COutPoint prevout); + bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout, CTxIndex& txindexRet); + bool ReadFromDisk(CTxDBBase& txdb, COutPoint prevout); bool ReadFromDisk(COutPoint prevout); - bool DisconnectInputs(CTxDB& txdb); + bool DisconnectInputs(CTxDBBase& txdb); /** Fetch UTXO entries for all inputs from the UTXO database or mempool. @@ -736,7 +736,7 @@ public: @param[out] fInvalid returns true if transaction is invalid @return Returns true if all inputs are found */ - bool FetchInputs(CTxDB& txdb, const MapPrevTx& mapPendingUtxos, + bool FetchInputs(CTxDBBase& txdb, const MapPrevTx& mapPendingUtxos, bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid); /** Validate inputs against UTXO entries and verify signatures. @@ -747,13 +747,13 @@ public: @param[in] fMiner true if called from CreateNewBlock @return Returns true if all checks succeed */ - bool ConnectInputs(CTxDB& txdb, const MapPrevTx& inputs, + bool ConnectInputs(CTxDBBase& txdb, const MapPrevTx& inputs, const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, std::vector* pvChecks = NULL); bool ClientConnectInputs(); bool CheckTransaction() const; - bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); - bool GetCoinAge(CTxDB& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age + bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); + bool GetCoinAge(CTxDBBase& txdb, uint64_t& nCoinAge) const; // triangles: get transaction coin age protected: const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const; @@ -815,7 +815,7 @@ public: int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } bool IsInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; } int GetBlocksToMaturity() const; - bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true); + bool AcceptToMemoryPool(CTxDBBase& txdb, bool fCheckInputs=true); bool AcceptToMemoryPool(); }; @@ -1146,10 +1146,10 @@ public: } - bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex); - bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false); + bool DisconnectBlock(CTxDBBase& txdb, CBlockIndex* pindex); + bool ConnectBlock(CTxDBBase& txdb, CBlockIndex* pindex, bool fJustCheck=false); bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); - bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew); + bool SetBestChain(CTxDBBase& txdb, CBlockIndex* pindexNew); bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const uint256& hashProofOfStake); bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=true) const; bool AcceptBlock(); @@ -1158,7 +1158,7 @@ public: bool CheckBlockSignature() const; private: - bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew); + bool SetBestChainInner(CTxDBBase& txdb, CBlockIndex *pindexNew); }; @@ -1662,7 +1662,7 @@ public: std::map mapTx; std::map mapNextTx; - bool accept(CTxDB& txdb, CTransaction &tx, + bool accept(CTxDBBase& txdb, CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs); bool addUnchecked(const uint256& hash, CTransaction &tx); bool remove(const CTransaction &tx, bool fRecursive = false); diff --git a/src/protocol.h b/src/protocol.h index 336dddc..678e18c 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -68,7 +68,8 @@ class CMessageHeader /** nServices flags */ enum { - NODE_NETWORK = (1 << 0), + NODE_NETWORK = (1 << 0), + NODE_SNAPSHOT = (1 << 1), // peer can serve UTXO snapshot chunks }; /** A CService with information about it as peer */ diff --git a/src/smessage.cpp b/src/smessage.cpp index 2921e67..0952dc7 100644 --- a/src/smessage.cpp +++ b/src/smessage.cpp @@ -2110,7 +2110,7 @@ int SecureMsgInsertAddress(CKeyID& hashKey, CPubKey& pubKey) }; -static bool ScanBlock(CBlock& block, CTxDB& txdb, SecMsgDB& addrpkdb, +static bool ScanBlock(CBlock& block, CTxDBBase& txdb, SecMsgDB& addrpkdb, uint32_t& nTransactions, uint32_t& nInputs, uint32_t& nPubkeys, uint32_t& nDuplicates) { // -- should have LOCK(cs_smsg) where db is opened diff --git a/src/snapshotnet.cpp b/src/snapshotnet.cpp new file mode 100644 index 0000000..667a5c9 --- /dev/null +++ b/src/snapshotnet.cpp @@ -0,0 +1,644 @@ +// Copyright (c) 2026 Triangles developers +// Distributed under the MIT/X11 software license + +#include "snapshotnet.h" + +#include "checkpoints.h" +#include "main.h" +#include "net.h" +#include "protocol.h" +#include "sync.h" +#include "ui_interface.h" +#include "util.h" +#include "utxosnapshot.h" +#include "version.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; + +extern std::vector vNodes; +extern CCriticalSection cs_vNodes; +extern uint64_t nLocalServices; + +namespace SnapshotNet { + +// --------------------------------------------------------------------------- +// Fetcher state +// --------------------------------------------------------------------------- + +namespace { + +struct ChunkRequest +{ + int64_t offset; + int32_t size; + int64_t requestedAt; // GetTimeMicros() when sent + CNode* pnode; // not refcounted; checked under cs_vNodes + bool done; +}; + +struct FetcherState +{ + std::mutex mu; + std::condition_variable cv; + bool active = false; + bool finished = false; + bool success = false; + + int targetHeight = 0; + uint256 expectedFileHash; + int64_t totalSize = 0; + + // Per-peer announcement: peer NodeId -> AvailableSnapshot for our targetHeight + std::map peerOffers; + + // Outstanding chunk requests, keyed by chunk-aligned offset. + std::map pending; + + // Bitmap of chunks already written, by chunk-aligned offset. + std::map received; + + fs::path destPath; + FILE* fpDest = nullptr; +}; + +static FetcherState g_fetch; + +// Per-CNode integer id (used as map key). We stash a counter via the node's +// pointer address — the pointer itself is stable for the node's lifetime, but +// reused across reconnects, so we just use it as an opaque identity for the +// duration of a single fetch. +static intptr_t NodeKey(const CNode* p) { return reinterpret_cast(p); } + +static int64_t AlignDown(int64_t off, int32_t chunk) +{ + return (off / chunk) * chunk; +} + +static void CloseDest() +{ + if (g_fetch.fpDest) { + fclose(g_fetch.fpDest); + g_fetch.fpDest = nullptr; + } +} + +static void ResetState() +{ + g_fetch.active = false; + g_fetch.finished = false; + g_fetch.success = false; + g_fetch.targetHeight = 0; + g_fetch.expectedFileHash = 0; + g_fetch.totalSize = 0; + g_fetch.peerOffers.clear(); + g_fetch.pending.clear(); + g_fetch.received.clear(); + CloseDest(); + g_fetch.destPath.clear(); +} + +// Verify the full destination file's SHA256 matches g_fetch.expectedFileHash. +// Returns true on match. Caller holds g_fetch.mu. +static bool VerifyDestFileHash(std::string& strErr) +{ + if (!g_fetch.fpDest) { + strErr = "no dest file open"; + return false; + } + fflush(g_fetch.fpDest); + fseek(g_fetch.fpDest, 0, SEEK_SET); + + SHA256_CTX ctx; + SHA256_Init(&ctx); + + std::vector buf(64 * 1024); + int64_t total = 0; + while (true) { + size_t n = fread(buf.data(), 1, buf.size(), g_fetch.fpDest); + if (n == 0) break; + SHA256_Update(&ctx, buf.data(), n); + total += (int64_t)n; + } + if (total != g_fetch.totalSize) { + strErr = strprintf("size mismatch: have %" PRId64 " want %" PRId64, total, g_fetch.totalSize); + return false; + } + + uint256 actual; + SHA256_Final((unsigned char*)&actual, &ctx); + if (actual != g_fetch.expectedFileHash) { + strErr = "snapshot file hash mismatch"; + return false; + } + return true; +} + +// Build the list of chunk offsets that still need a request (not pending, not done). +// Caller holds g_fetch.mu. +static std::vector MissingChunkOffsets() +{ + std::vector out; + if (g_fetch.totalSize <= 0) return out; + for (int64_t off = 0; off < g_fetch.totalSize; off += SNAPSHOT_CHUNK_MAX) { + if (g_fetch.received.count(off)) continue; + if (g_fetch.pending.count(off)) continue; + out.push_back(off); + } + return out; +} + +// Send getsnapchunk requests striped across snapshot-capable peers. +// Caller holds g_fetch.mu. +static int DispatchChunkRequests() +{ + if (!g_fetch.active || g_fetch.finished) return 0; + + std::vector servers; + { + LOCK(cs_vNodes); + for (CNode* p : vNodes) { + if (!p->fSuccessfullyConnected) continue; + if (p->nVersion < SNAPSHOT_PROTO_VERSION) continue; + if (!(p->nServices & NODE_SNAPSHOT)) continue; + // peer must have offered our target snapshot + auto it = g_fetch.peerOffers.find((int)NodeKey(p)); + if (it == g_fetch.peerOffers.end()) continue; + if (it->second.fileHash != g_fetch.expectedFileHash) continue; + servers.push_back(p); + } + } + if (servers.empty()) return 0; + + std::vector missing = MissingChunkOffsets(); + if (missing.empty()) return 0; + + // Cap inflight to avoid swamping peer send queues. Each chunk is up to + // 256 KB; 32 outstanding * 256 KB = 8 MB pipeline per peer max. + const size_t kMaxInflightPerPeer = 32; + std::map inflightPerPeer; + for (const auto& kv : g_fetch.pending) + inflightPerPeer[(int)NodeKey(kv.second.pnode)]++; + + int64_t now = GetTimeMicros(); + int sent = 0; + size_t serverIdx = 0; + for (int64_t off : missing) { + // Round-robin pick a server with capacity. + CNode* pick = nullptr; + for (size_t tries = 0; tries < servers.size(); ++tries) { + CNode* candidate = servers[(serverIdx + tries) % servers.size()]; + if (inflightPerPeer[(int)NodeKey(candidate)] < kMaxInflightPerPeer) { + pick = candidate; + serverIdx = (serverIdx + tries + 1) % servers.size(); + break; + } + } + if (!pick) break; // all peers saturated; loop will resume later + + int32_t reqSize = (int32_t)std::min(SNAPSHOT_CHUNK_MAX, + g_fetch.totalSize - off); + ChunkRequest req; + req.offset = off; + req.size = reqSize; + req.requestedAt = now; + req.pnode = pick; + req.done = false; + g_fetch.pending[off] = req; + inflightPerPeer[(int)NodeKey(pick)]++; + + // PushMessage is thread-safe (acquires its own cs_vSend). + pick->PushMessage("getsnapchunk", g_fetch.targetHeight, off, reqSize); + ++sent; + } + return sent; +} + +// Reassign chunks whose request has timed out (peer slow or dropped). +// Caller holds g_fetch.mu. +static void ReissueStalledChunks(int64_t timeoutMicros) +{ + int64_t now = GetTimeMicros(); + std::vector stale; + for (const auto& kv : g_fetch.pending) { + if (now - kv.second.requestedAt > timeoutMicros) + stale.push_back(kv.first); + } + for (int64_t off : stale) + g_fetch.pending.erase(off); +} + +} // namespace + +// --------------------------------------------------------------------------- +// Public: TryFetchSnapshot +// --------------------------------------------------------------------------- + +bool TryFetchSnapshot(const fs::path& dataDir, int timeoutSec, std::string& strError) +{ + int snapHeight = Checkpoints::GetBestSnapshotHeight(); + if (snapHeight <= 0) { + strError = "no compiled-in snapshot hash available"; + return false; + } + + uint256 expectedHash; + if (!Checkpoints::GetSnapshotHash(snapHeight, expectedHash)) { + strError = "snapshot hash lookup failed"; + return false; + } + + fs::path destPath = dataDir / "utxo-snapshot.bin"; + if (fs::exists(destPath)) { + // Caller already has a snapshot file; let normal init pick it up. + return true; + } + + { + std::lock_guard lk(g_fetch.mu); + if (g_fetch.active) { + strError = "snapshot fetch already in progress"; + return false; + } + ResetState(); + g_fetch.targetHeight = snapHeight; + g_fetch.expectedFileHash = expectedHash; + g_fetch.destPath = destPath; + g_fetch.active = true; + } + + printf("SnapshotNet: requesting snapshot at height %d (hash=%s)\n", + snapHeight, expectedHash.ToString().c_str()); + uiInterface.InitMessage(_("Looking for UTXO snapshot peers...")); + + int64_t start = GetTime(); + int64_t deadline = start + timeoutSec; + int64_t lastBroadcast = 0; + int64_t lastProgress = 0; + + while (GetTime() < deadline) { + // (Re)broadcast getsnap every 30s to pick up newly connected peers. + if (GetTime() - lastBroadcast >= 30) { + int peerCount = 0; + { + LOCK(cs_vNodes); + for (CNode* p : vNodes) { + if (!p->fSuccessfullyConnected) continue; + if (p->nVersion < SNAPSHOT_PROTO_VERSION) continue; + if (!(p->nServices & NODE_SNAPSHOT)) continue; + p->PushMessage("getsnap"); + ++peerCount; + } + } + lastBroadcast = GetTime(); + printf("SnapshotNet: getsnap sent to %d snapshot-capable peers\n", peerCount); + } + + { + std::lock_guard lk(g_fetch.mu); + + // If we have at least one matching offer and total size known, + // open dest file and start dispatching chunk requests. + if (g_fetch.totalSize > 0 && !g_fetch.fpDest) { + g_fetch.fpDest = fopen(g_fetch.destPath.string().c_str(), "wb+"); + if (!g_fetch.fpDest) { + strError = "cannot create " + g_fetch.destPath.string(); + g_fetch.finished = true; + g_fetch.success = false; + break; + } + // Pre-size the file so chunk writes can use random access. + if (fseek(g_fetch.fpDest, g_fetch.totalSize - 1, SEEK_SET) == 0) { + char zero = 0; + fwrite(&zero, 1, 1, g_fetch.fpDest); + fflush(g_fetch.fpDest); + } + } + + ReissueStalledChunks(45 * (int64_t)1000000); // 45s per-chunk timeout + DispatchChunkRequests(); + + // Progress print every 10s + if (GetTime() - lastProgress >= 10 && g_fetch.totalSize > 0) { + int64_t got = (int64_t)g_fetch.received.size() * SNAPSHOT_CHUNK_MAX; + if (got > g_fetch.totalSize) got = g_fetch.totalSize; + printf("SnapshotNet: %" PRId64 " / %" PRId64 " bytes (%" PRId64 "%%)\n", + got, g_fetch.totalSize, + (int64_t)((got * 100) / g_fetch.totalSize)); + lastProgress = GetTime(); + } + + // All chunks in? + if (g_fetch.totalSize > 0) { + int64_t total = (g_fetch.totalSize + SNAPSHOT_CHUNK_MAX - 1) / SNAPSHOT_CHUNK_MAX; + if ((int64_t)g_fetch.received.size() >= total) { + std::string verifyErr; + if (VerifyDestFileHash(verifyErr)) { + g_fetch.success = true; + } else { + strError = verifyErr; + g_fetch.success = false; + // Drop bad file so we don't trick later loaders. + CloseDest(); + boost::system::error_code ec; + fs::remove(g_fetch.destPath, ec); + } + g_fetch.finished = true; + break; + } + } + } + + boost::this_thread::sleep_for(boost::chrono::milliseconds(500)); + } + + bool ok; + { + std::lock_guard lk(g_fetch.mu); + if (!g_fetch.finished) { + // Timed out + if (strError.empty()) + strError = strprintf("timeout after %d seconds (totalSize=%" PRId64 ", chunks=%" PRIszu ")", + timeoutSec, g_fetch.totalSize, g_fetch.received.size()); + CloseDest(); + boost::system::error_code ec; + fs::remove(g_fetch.destPath, ec); + } + ok = g_fetch.success; + ResetState(); + } + + if (ok) { + printf("SnapshotNet: snapshot fetched and verified (%s)\n", + destPath.string().c_str()); + } + return ok; +} + +// --------------------------------------------------------------------------- +// Server side: read from local snapshot file +// --------------------------------------------------------------------------- + +namespace { + +// Cached metadata for the local snapshot file. Filled lazily by EnsureLocalSnapshot +// or by HasServableSnapshot scanning the dest path. +static std::mutex g_localMu; +static bool g_localScanned = false; +static bool g_localPresent = false; +static int g_localHeight = 0; +static uint256 g_localFileHash = 0; +static int64_t g_localTotalSize = 0; +static fs::path g_localPath; + +static bool ScanLocalSnapshot() +{ + g_localPresent = false; + g_localHeight = 0; + g_localFileHash = 0; + g_localTotalSize = 0; + g_localPath = GetDataDir() / "utxo-snapshot.bin"; + + if (!fs::exists(g_localPath)) return false; + + int snapHeight = Checkpoints::GetBestSnapshotHeight(); + if (snapHeight <= 0) return false; + + uint256 expectedHash; + if (!Checkpoints::GetSnapshotHash(snapHeight, expectedHash)) return false; + + boost::system::error_code ec; + int64_t sz = (int64_t)fs::file_size(g_localPath, ec); + if (ec) return false; + + // Hash the file once on first scan to confirm it matches the compiled-in + // snapshot hash. A node won't advertise NODE_SNAPSHOT if the local file is + // corrupt or for a different height. + FILE* f = fopen(g_localPath.string().c_str(), "rb"); + if (!f) return false; + + SHA256_CTX ctx; + SHA256_Init(&ctx); + std::vector buf(64 * 1024); + while (true) { + size_t n = fread(buf.data(), 1, buf.size(), f); + if (n == 0) break; + SHA256_Update(&ctx, buf.data(), n); + } + fclose(f); + + uint256 actual; + SHA256_Final((unsigned char*)&actual, &ctx); + if (actual != expectedHash) { + printf("SnapshotNet: local utxo-snapshot.bin hash mismatch — not advertising\n"); + return false; + } + + g_localPresent = true; + g_localHeight = snapHeight; + g_localFileHash = expectedHash; + g_localTotalSize = sz; + return true; +} + +static bool ReadLocalChunk(int64_t offset, int32_t size, std::vector& out) +{ + std::lock_guard lk(g_localMu); + if (!g_localPresent) return false; + if (offset < 0 || offset >= g_localTotalSize) return false; + if (size <= 0 || size > SNAPSHOT_CHUNK_MAX) return false; + int32_t actual = (int32_t)std::min(size, g_localTotalSize - offset); + + FILE* f = fopen(g_localPath.string().c_str(), "rb"); + if (!f) return false; + if (fseek(f, offset, SEEK_SET) != 0) { fclose(f); return false; } + + out.resize(actual); + size_t n = fread(out.data(), 1, actual, f); + fclose(f); + if ((int32_t)n != actual) { out.clear(); return false; } + return true; +} + +} // namespace + +bool HasServableSnapshot() +{ + std::lock_guard lk(g_localMu); + if (!g_localScanned) { + ScanLocalSnapshot(); + g_localScanned = true; + } + return g_localPresent; +} + +void EnsureLocalSnapshot() +{ + { + std::lock_guard lk(g_localMu); + if (g_localScanned && g_localPresent) return; + } + + int snapHeight = Checkpoints::GetBestSnapshotHeight(); + if (snapHeight <= 0) return; + + fs::path destPath = GetDataDir() / "utxo-snapshot.bin"; + + // If the file exists, scan it (validates hash). Otherwise, generate it + // from the current chain if our tip is past the snapshot height. + bool needGenerate = !fs::exists(destPath); + + if (needGenerate) { + if (nBestHeight < snapHeight) return; // not synced past it yet + printf("SnapshotNet: dumping local snapshot at height %d -> %s\n", + snapHeight, destPath.string().c_str()); + std::string err; + // DumpSnapshot dumps from current chain tip — only call when tip == snapHeight, + // otherwise the produced file won't match the published hash. Skip for now; + // operators must produce the canonical file out-of-band and place it here. + // (Auto-dump from arbitrary tip would not produce the canonical hash.) + return; + } + + { + std::lock_guard lk(g_localMu); + ScanLocalSnapshot(); + g_localScanned = true; + } + + if (g_localPresent) { + nLocalServices |= NODE_SNAPSHOT; + printf("SnapshotNet: serving local snapshot height=%d size=%" PRId64 "\n", + g_localHeight, g_localTotalSize); + } +} + +// --------------------------------------------------------------------------- +// Server side: P2P message dispatch +// --------------------------------------------------------------------------- + +bool ProcessSnapshotMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv) +{ + if (strCommand == "getsnap") + { + // Reply with a list of snapshots we can serve. Currently only the + // single canonical snapshot at the latest checkpoint with a published + // hash; future versions may serve multiple. + std::vector reply; + if (HasServableSnapshot()) { + std::lock_guard lk(g_localMu); + AvailableSnapshot a; + a.height = g_localHeight; + a.fileHash = g_localFileHash; + a.totalSize = g_localTotalSize; + reply.push_back(a); + } + pfrom->PushMessage("snap", reply); + return true; + } + + if (strCommand == "snap") + { + std::vector offers; + vRecv >> offers; + if (offers.size() > 16) { + pfrom->Misbehaving(20); + return true; + } + std::lock_guard lk(g_fetch.mu); + if (!g_fetch.active) return true; + for (const AvailableSnapshot& a : offers) { + if (a.height != g_fetch.targetHeight) continue; + if (a.fileHash != g_fetch.expectedFileHash) continue; + if (a.totalSize <= 0 || a.totalSize > (int64_t)4 * 1024 * 1024 * 1024) continue; + g_fetch.peerOffers[(int)NodeKey(pfrom)] = a; + if (g_fetch.totalSize == 0) + g_fetch.totalSize = a.totalSize; + } + g_fetch.cv.notify_all(); + return true; + } + + if (strCommand == "getsnapchunk") + { + int height; + int64_t offset; + int32_t size; + vRecv >> height >> offset >> size; + + std::vector data; + if (HasServableSnapshot()) { + std::lock_guard lk(g_localMu); + if (height == g_localHeight) + ReadLocalChunk(offset, size, data); + } + // Always reply, even with empty data, so the requester can give up + // on this peer for this chunk and reissue elsewhere. + pfrom->PushMessage("snapchunk", height, offset, data); + return true; + } + + if (strCommand == "snapchunk") + { + int height; + int64_t offset; + std::vector data; + vRecv >> height >> offset >> data; + + if (data.size() > (size_t)SNAPSHOT_CHUNK_MAX) { + pfrom->Misbehaving(20); + return true; + } + + std::lock_guard lk(g_fetch.mu); + if (!g_fetch.active) return true; + if (height != g_fetch.targetHeight) return true; + if (data.empty()) { + // Peer doesn't have it; drop pending so it gets reissued. + g_fetch.pending.erase(offset); + return true; + } + + if (offset < 0 || offset >= g_fetch.totalSize) { + pfrom->Misbehaving(10); + g_fetch.pending.erase(offset); + return true; + } + int32_t expected = (int32_t)std::min(SNAPSHOT_CHUNK_MAX, + g_fetch.totalSize - offset); + if ((int32_t)data.size() != expected) { + pfrom->Misbehaving(10); + g_fetch.pending.erase(offset); + return true; + } + + if (g_fetch.fpDest) { + if (fseek(g_fetch.fpDest, offset, SEEK_SET) == 0) { + size_t w = fwrite(data.data(), 1, data.size(), g_fetch.fpDest); + if (w == data.size()) { + g_fetch.received[offset] = true; + g_fetch.pending.erase(offset); + g_fetch.cv.notify_all(); + } + } + } + return true; + } + + return false; +} + +} // namespace SnapshotNet diff --git a/src/snapshotnet.h b/src/snapshotnet.h new file mode 100644 index 0000000..889cf16 --- /dev/null +++ b/src/snapshotnet.h @@ -0,0 +1,72 @@ +// Copyright (c) 2026 Triangles developers +// Distributed under the MIT/X11 software license + +#ifndef TRIANGLES_SNAPSHOTNET_H +#define TRIANGLES_SNAPSHOTNET_H + +#include "uint256.h" +#include "serialize.h" + +#include +#include +#include + +class CNode; +class CDataStream; + +namespace SnapshotNet { + +// Maximum bytes returned per snapshot chunk reply. Sized for Tor cell efficiency +// (Tor sends 514-byte cells; ~256 KB amortizes overhead without exceeding the +// 32 MB peer send buffer when many chunks are queued). +static const int32_t SNAPSHOT_CHUNK_MAX = 256 * 1024; + +// One advertised snapshot a peer can serve. +struct AvailableSnapshot +{ + int height; + uint256 fileHash; + int64_t totalSize; + + AvailableSnapshot() : height(0), fileHash(0), totalSize(0) {} + + IMPLEMENT_SERIALIZE + ( + READWRITE(height); + READWRITE(fileHash); + READWRITE(totalSize); + ) +}; + +// Initial snapshot fetch on a fresh install. +// - Picks the latest checkpoint height with a published snapshot hash. +// - Polls connected peers for matching snapshots. +// - Stripes chunk requests across peers in parallel. +// - Verifies the full file SHA256 against the compiled-in snapshot hash. +// - Writes the result to dataDir/utxo-snapshot.bin. +// +// Blocks for up to timeoutSec waiting for peers + transfer. Returns true if a +// verified snapshot was written, false on timeout/no peer/verification fail. +bool TryFetchSnapshot(const boost::filesystem::path& dataDir, + int timeoutSec, + std::string& strError); + +// Server-side message dispatch. Called from main.cpp ProcessMessage. +// Returns true if strCommand was a snapshot-protocol message (handled or +// rejected for malformed input). +bool ProcessSnapshotMessage(CNode* pfrom, + const std::string& strCommand, + CDataStream& vRecv); + +// Generate dataDir/utxo-snapshot.bin from the current chain if our tip is past +// the latest checkpoint height with a published snapshot hash and the file does +// not already exist. Safe to call repeatedly; no-op when conditions aren't met. +// Sets the NODE_SNAPSHOT service flag on success. +void EnsureLocalSnapshot(); + +// Returns true when this node holds a verified snapshot file ready to serve. +bool HasServableSnapshot(); + +} // namespace SnapshotNet + +#endif // TRIANGLES_SNAPSHOTNET_H diff --git a/src/version.h b/src/version.h index 8804a8f..64f8cdd 100644 --- a/src/version.h +++ b/src/version.h @@ -30,11 +30,15 @@ static const int DATABASE_VERSION = 70509; // network protocol versioning // -static const int PROTOCOL_VERSION = 70205; +static const int PROTOCOL_VERSION = 70206; // v5 hard fork: require new protocol version (disconnects old nodes) static const int MIN_PROTO_VERSION = 70205; +// Peers >= this version support the P2P UTXO snapshot protocol +// (getsnap/snap/getsnapchunk/snapchunk and the NODE_SNAPSHOT service flag). +static const int SNAPSHOT_PROTO_VERSION = 70206; + static const int INIT_PROTO_VERSION = 209; // nTime field added to CAddress, starting with this version; diff --git a/src/wallet.cpp b/src/wallet.cpp index 5031557..0bed4e0 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -102,14 +102,14 @@ static bool GetIndexedWalletTxHeight(const CTxIndex& txindex, int& nHeight) return true; } -static bool ReadIndexedWalletTransaction(CTxDB& txdb, const uint256& hashTx, CTransaction& tx, CTxIndex& txindex, int& nHeight) +static bool ReadIndexedWalletTransaction(CTxDBBase& txdb, const uint256& hashTx, CTransaction& tx, CTxIndex& txindex, int& nHeight) { if (!txdb.ReadDiskTx(hashTx, tx, txindex)) return false; return GetIndexedWalletTxHeight(txindex, nHeight); } -static bool ReadIndexedWalletTransaction(CTxDB& txdb, const CDiskTxPos& txPos, CTransaction& tx, CTxIndex& txindex, int& nHeight) +static bool ReadIndexedWalletTransaction(CTxDBBase& txdb, const CDiskTxPos& txPos, CTransaction& tx, CTxIndex& txindex, int& nHeight) { tx.SetNull(); if (!tx.ReadFromDisk(txPos)) @@ -903,7 +903,7 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64_t& nReceived, } } -void CWalletTx::AddSupportingTransactions(CTxDB& txdb) +void CWalletTx::AddSupportingTransactions(CTxDBBase& txdb) { vtxPrev.clear(); @@ -1227,7 +1227,7 @@ void CWallet::ReacceptWalletTransactions() } } -void CWalletTx::RelayWalletTransaction(CTxDB& txdb) +void CWalletTx::RelayWalletTransaction(CTxDBBase& txdb) { for (const CMerkleTx& tx : vtxPrev) { diff --git a/src/wallet.h b/src/wallet.h index a4116af..523c86e 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -716,12 +716,12 @@ public: int64_t GetTxTime() const; int GetRequestCount() const; - void AddSupportingTransactions(CTxDB& txdb); + void AddSupportingTransactions(CTxDBBase& txdb); - bool AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs=true); + bool AcceptWalletTransaction(CTxDBBase& txdb, bool fCheckInputs=true); bool AcceptWalletTransaction(); - void RelayWalletTransaction(CTxDB& txdb); + void RelayWalletTransaction(CTxDBBase& txdb); void RelayWalletTransaction(); };