diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a37f88a168729..7e4d7a9f9daf3 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -182,12 +182,13 @@ static MapCheckpoints mapCheckpoints = { {3014000, uint256S("78ad99b7225f73c42238bd7ca841ff700542b92bba75a0ef2ed351caa560f87f")}, //!< PIVX v5.3.0 enforced {3024000, uint256S("be4bc75afcfb9136924810f7483b2695089a366cc4ee27fd6dc3ecd5396e1f0f")}, //!< Superblock {3715200, uint256S("a676b9a598c393c82b949c37dd35013aeda55f5d18ab062349db6a8235972aaa")}, //!< Superblock for 5.5.0 mainnet rewards changeover + {3780000, uint256S("2667fa1d552999aca930ced7fd3902ae5721e5c256a607049e3c47a3137a18ee")}, //!< Fri, 10 Mar 2023 rewindblockindex testing }; static const CCheckpointData data = { &mapCheckpoints, - 1591401645, // * UNIX timestamp of last checkpoint block - 5607713, // * total number of transactions between genesis and last checkpoint + 1678401150, // * UNIX timestamp of last checkpoint block + 8716106, // * total number of transactions between genesis and last checkpoint // (the tx=... number in the UpdateTip debug.log lines) 3000 // * estimated number of transactions per day after checkpoint }; diff --git a/src/init.cpp b/src/init.cpp index cbe3a90a403e0..d5636a3242915 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -421,6 +421,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks"); strUsage += HelpMessageOpt("-reindex", "Rebuild block chain index from current blk000??.dat files on startup"); strUsage += HelpMessageOpt("-resync", "Delete blockchain folders and resync from scratch on startup"); + strUsage += HelpMessageOpt("-rewindblockindex", "Rewind blockchain to the last checkpoint"); #if !defined(WIN32) strUsage += HelpMessageOpt("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)"); #endif @@ -1424,6 +1425,7 @@ bool AppInitMain() fReindex = gArgs.GetBoolArg("-reindex", false); bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false); + bool clearWitnessCaches = false; // cache size calculations int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20); @@ -1572,8 +1574,17 @@ bool AppInitMain() } } + uiInterface.InitMessage(_("Verifying blocks...")); + if (!is_coinsview_empty) { - uiInterface.InitMessage(_("Verifying blocks...")); + if (!fReindex && chainActive.Tip() != NULL) { + uiInterface.InitMessage(_("Rewinding blocks to last checkpoint if needed...")); + if (!RewindBlockIndexToLastCheckpoint(chainparams, clearWitnessCaches, gArgs.GetBoolArg("-rewindblockindex", false))) { + strLoadError = _("Unable to rewind the blockchain to last checkpoint. You will need to redownload the blockchain"); + break; + } + } + CBlockIndex *tip = chainActive.Tip(); RPCNotifyBlockChange(true, tip); if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { @@ -1635,7 +1646,7 @@ bool AppInitMain() // ********************************************************* Step 8: Backup and Load wallet #ifdef ENABLE_WALLET - if (!InitLoadWallet()) + if (!InitLoadWallet(clearWitnessCaches)) return false; #else LogPrintf("No wallet compiled in!\n"); diff --git a/src/qt/pivx/qtutils.h b/src/qt/pivx/qtutils.h index fc5355ab7e42b..ecedb5073fe2e 100644 --- a/src/qt/pivx/qtutils.h +++ b/src/qt/pivx/qtutils.h @@ -29,6 +29,7 @@ const QString ZAPTXES2("-zapwallettxes=2"); const QString UPGRADEWALLET("-upgradewallet"); const QString REINDEX("-reindex"); const QString RESYNC("-resync"); +const QString REWIND("-rewindblockindex"); extern Qt::Modifier SHORT_KEY; diff --git a/src/qt/pivx/settings/forms/settingswalletrepairwidget.ui b/src/qt/pivx/settings/forms/settingswalletrepairwidget.ui index 3143627d58ef4..430f98d663118 100644 --- a/src/qt/pivx/settings/forms/settingswalletrepairwidget.ui +++ b/src/qt/pivx/settings/forms/settingswalletrepairwidget.ui @@ -65,9 +65,9 @@ 0 - 0 - 398 - 620 + -57 + 383 + 677 @@ -584,11 +584,88 @@ + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + 20 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + Qt::NoFocus + + + Rewind blockchain + + + + + + + + + Rewind blockchain to last checkpoint + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + Qt::Vertical + + QSizePolicy::Expanding + 20 diff --git a/src/qt/pivx/settings/settingswalletrepairwidget.cpp b/src/qt/pivx/settings/settingswalletrepairwidget.cpp index 0ca0db2442575..9fe2e23af99c1 100644 --- a/src/qt/pivx/settings/settingswalletrepairwidget.cpp +++ b/src/qt/pivx/settings/settingswalletrepairwidget.cpp @@ -26,12 +26,12 @@ SettingsWalletRepairWidget::SettingsWalletRepairWidget(PIVXGUI* _window, QWidget // Labels setCssProperty({ui->labelMessageSalvage, ui->labelMessageRescan, ui->labelMessageRecover1, ui->labelMessageRecover2, ui->labelMessageUpgrade, ui->labelMessageRebuild, - ui->labelMessageDelete}, "text-main-settings"); + ui->labelMessageDelete, ui->labelMessageRewind}, "text-main-settings"); // Buttons setCssProperty({ui->pushButtonSalvage, ui->pushButtonRescan, ui->pushButtonRecover1, ui->pushButtonRecover2, ui->pushButtonUpgrade, ui->pushButtonRebuild, - ui->pushButtonDelete}, "btn-primary"); + ui->pushButtonDelete, ui->pushButtonRewind}, "btn-primary"); // Wallet Repair Buttons connect(ui->pushButtonSalvage, &QPushButton::clicked, this, &SettingsWalletRepairWidget::walletSalvage); @@ -41,6 +41,7 @@ SettingsWalletRepairWidget::SettingsWalletRepairWidget(PIVXGUI* _window, QWidget connect(ui->pushButtonUpgrade, &QPushButton::clicked, this, &SettingsWalletRepairWidget::walletUpgrade); connect(ui->pushButtonRebuild, &QPushButton::clicked, this, &SettingsWalletRepairWidget::walletReindex); connect(ui->pushButtonDelete, &QPushButton::clicked, this, &SettingsWalletRepairWidget::walletResync); + connect(ui->pushButtonRewind, &QPushButton::clicked, this, &SettingsWalletRepairWidget::walletRewind); } /** Restart wallet with "-salvagewallet" */ @@ -100,6 +101,25 @@ void SettingsWalletRepairWidget::walletResync() buildParameterlist(RESYNC); } +/** Restart wallet with "-rewindblockindex" */ +void SettingsWalletRepairWidget::walletRewind() +{ + QString rewindWarning = tr("This will rewind your blocks to the most recent checkpoint.

"); + rewindWarning += tr("Do you want to continue?.
"); + QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm resync Blockchain"), + rewindWarning, + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + + if (retval != QMessageBox::Yes) { + // Rewind canceled + return; + } + + // Restart and rewind + buildParameterlist(REWIND); +} + /** Build command-line parameter list for restart */ void SettingsWalletRepairWidget::buildParameterlist(QString arg) { diff --git a/src/qt/pivx/settings/settingswalletrepairwidget.h b/src/qt/pivx/settings/settingswalletrepairwidget.h index f28bf6e5917ab..8c29bdddfbad9 100644 --- a/src/qt/pivx/settings/settingswalletrepairwidget.h +++ b/src/qt/pivx/settings/settingswalletrepairwidget.h @@ -35,6 +35,7 @@ public Q_SLOTS: void walletUpgrade(); void walletReindex(); void walletResync(); + void walletRewind(); private: Ui::SettingsWalletRepairWidget *ui; diff --git a/src/txdb.cpp b/src/txdb.cpp index f971f1389edce..58d5341c51bf9 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -247,6 +247,14 @@ bool CBlockTreeDB::WriteBatchSync(const std::vector& blockinfo) { + CDBBatch batch; + for (std::vector::const_iterator it=blockinfo.begin(); it != blockinfo.end(); it++) { + batch.Erase(std::make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash())); + } + return WriteBatch(batch, true); +} + bool CBlockTreeDB::ReadTxIndex(const uint256& txid, CDiskTxPos& pos) { return Read(std::make_pair(DB_TXINDEX, txid), pos); diff --git a/src/txdb.h b/src/txdb.h index 3dce71b61a274..22d278f95243a 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -134,6 +134,7 @@ class CBlockTreeDB : public CDBWrapper bool WriteBlockIndex(const CDiskBlockIndex& blockindex); bool WriteBatchSync(const std::vector >& fileInfo, int nLastFile, const std::vector& blockinfo); + bool EraseBatchSync(const std::vector& blockinfo); bool ReadBlockFileInfo(int nFile, CBlockFileInfo& info); bool ReadLastBlockFile(int& nFile); bool WriteReindexing(bool fReindexing); diff --git a/src/validation.cpp b/src/validation.cpp index 87d8a03379388..79beaebd10f9c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3741,6 +3741,102 @@ bool CVerifyDB::VerifyDB(CCoinsView* coinsview, int nCheckLevel, int nCheckDepth return true; } +bool RewindBlockIndexToLastCheckpoint(const CChainParams& chainparams, bool& clearWitnessCaches, bool fJustCheck) +{ + LOCK(cs_main); + + // Get current Height and current Checkpoints + int nHeight = chainActive.Height(); + const CBlockIndex* prevCheckPoint = GetLastCheckpoint(); + const int checkPointHeight = prevCheckPoint ? prevCheckPoint->nHeight : 0; + + // First load options + if(!fJustCheck) + return true; + + clearWitnessCaches = true; + CValidationState state; + const int blocksToRollBack = nHeight - checkPointHeight; + // Iterate to start removing blocks + while (nHeight > checkPointHeight) { + if (!DisconnectTip(state, chainparams, nullptr)) { + return error("%s: unable to disconnect block at height %i", __func__, nHeight); + } + // Occasionally flush state to disk. + if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) + return false; + + nHeight = chainActive.Height(); + } + + // Collect blocks to be removed (blocks in mapBlockIndex must be at least BLOCK_VALID_TREE). + // We do this after actual disconnecting, otherwise we'll end up writing the lack of data + // to disk before writing the chainstate, resulting in a failure to continue if interrupted. + std::vector vBlocks; + for (BlockMap::iterator it = mapBlockIndex.begin(); it != mapBlockIndex.end(); it++) { + CBlockIndex* pindexIter = it->second; + if (!chainActive.Contains(pindexIter)) { + // Add to the list of blocks to remove + vBlocks.emplace_back(pindexIter); + if (pindexIter == pindexBestInvalid) { + // Reset invalid block marker if it was pointing to this block + pindexBestInvalid = nullptr; + } + // Reduce validity + pindexIter->nStatus = std::min(pindexIter->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) | (pindexIter->nStatus & ~BLOCK_VALID_MASK); + // Remove have-data flags. + pindexIter->nStatus &= ~(BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO); + // Remove storage location. + pindexIter->nFile = 0; + pindexIter->nDataPos = 0; + pindexIter->nUndoPos = 0; + // Remove various other things + pindexIter->nTx = 0; + pindexIter->nChainTx = 0; + pindexIter->nSequenceId = 0; + // Update indices + setBlockIndexCandidates.erase(pindexIter); + auto ret = mapBlocksUnlinked.equal_range(pindexIter->pprev); + while (ret.first != ret.second) { + if (ret.first->second == pindexIter) { + ret.first = mapBlocksUnlinked.erase(ret.first); + } else { + ++ret.first; + } + } + } else if (pindexIter->IsValid(BLOCK_VALID_TRANSACTIONS) && pindexIter->nChainTx) { + setBlockIndexCandidates.insert(pindexIter); + } + } + + // Set pindexBestHeader to the current chain tip + // (since we are about to delete the block it is pointing to) + pindexBestHeader = chainActive.Tip(); + + // Erase block indices on-disk + if (!pblocktree->EraseBatchSync(vBlocks)) { + return AbortNode(state, "Failed to erase from block index database"); + } + + // Erase block indices in-memory + for (auto pindex : vBlocks) { + auto ret = mapBlockIndex.find(*pindex->phashBlock); + if (ret != mapBlockIndex.end()) { + mapBlockIndex.erase(ret); + delete pindex; + } + } + + CheckBlockIndex(); + + if (!FlushStateToDisk(state, FLUSH_STATE_ALWAYS)) { + return false; + } + + return true; +} + + /** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */ static bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { diff --git a/src/validation.h b/src/validation.h index 871e9f3586ac9..29f5246c3c4ca 100644 --- a/src/validation.h +++ b/src/validation.h @@ -338,6 +338,9 @@ class CVerifyDB bool VerifyDB(CCoinsView* coinsview, int nCheckLevel, int nCheckDepth); }; +/** Rewind chain up to the last checkpoint */ +bool RewindBlockIndexToLastCheckpoint(const CChainParams& chainparams, bool& clearWitnessCaches, bool fJustCheck); + /** Replay blocks that aren't fully applied to the database. */ bool ReplayBlocks(const CChainParams& params, CCoinsView* view); diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index c47966d5b9b65..59aff7cd08ee2 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -205,7 +205,7 @@ bool WalletVerify() return true; } -bool InitLoadWallet() +bool InitLoadWallet(bool clearWitnessCaches) { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); @@ -214,7 +214,7 @@ bool InitLoadWallet() for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { // create/load wallet - CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir())); + CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir()), clearWitnessCaches); if (!pwallet) { return false; } diff --git a/src/wallet/init.h b/src/wallet/init.h index 3d0ba7c6b9f33..3a5c473f33305 100644 --- a/src/wallet/init.h +++ b/src/wallet/init.h @@ -21,6 +21,6 @@ bool WalletParameterInteraction(); bool WalletVerify(); //! Load wallet databases. -bool InitLoadWallet(); +bool InitLoadWallet(bool clearWitnessCaches); #endif // PIVX_WALLET_INIT_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c13f8ba19f426..fb9663434b157 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4157,14 +4157,14 @@ void CWallet::LockIfMyCollateral(const CTransactionRef& ptx) } } -CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path) +CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, bool clearWitnessCaches) { const std::string& walletFile = name; // needed to restore wallet transaction meta data after -zapwallettxes std::vector vWtx; - if (gArgs.GetBoolArg("-zapwallettxes", false)) { + if (clearWitnessCaches || gArgs.GetBoolArg("-zapwallettxes", false)) { uiInterface.InitMessage(_("Zapping all transactions from wallet...")); std::unique_ptr tempWallet = std::make_unique(name, WalletDatabase::Create(path)); @@ -4278,7 +4278,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& LOCK(cs_main); CBlockIndex* pindexRescan = chainActive.Genesis(); - if (gArgs.GetBoolArg("-rescan", false)) { + if (clearWitnessCaches || gArgs.GetBoolArg("-rescan", false)) { // clear note witness cache before a full rescan walletInstance->ClearNoteWitnessCache(); } else { @@ -4326,7 +4326,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 - if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") { + if ((clearWitnessCaches || gArgs.GetBoolArg("-zapwallettxes", false)) && gArgs.GetArg("-zapwallettxes", "1") != "2") { WalletBatch batch(*walletInstance->database); for (const CWalletTx& wtxOld : vWtx) { uint256 hash = wtxOld.GetHash(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c2f7bfb3c2ffa..873b3ac61ab19 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1190,7 +1190,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool AbandonTransaction(const uint256& hashTx); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static CWallet* CreateWalletFromFile(const std::string& name, const fs::path& path); + static CWallet* CreateWalletFromFile(const std::string& name, const fs::path& path, bool clearWitnessCaches); /** * Wallet post-init setup diff --git a/test/functional/feature_rewind.py b/test/functional/feature_rewind.py new file mode 100755 index 0000000000000..8426ba7d6fee6 --- /dev/null +++ b/test/functional/feature_rewind.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Copyright (c) 2023 The PIVX Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. + +"""Test running pivxd with -rewindblockindex option. + +- Start 2 Nodes +- Sync both nodes to different chains +- Stop and then start both nodes with -rewindblockindex + +""" + +from test_framework.test_framework import PivxTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, +) + +class RewindBlockIndexCheckpointTest(PivxTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [ + ['-nuparams=PoS:201', '-nuparams=PoS_v2:201', "-whitelist=127.0.0.1", '-debug=0'], #right chain + ['-nuparams=PoS:301', '-nuparams=PoS_v2:301', "-whitelist=127.0.0.1", '-debug=0'] #wrong chain + ] + + def run_test(self): + self.log.info("Starting nodes") + + res0 = self.nodes[0].getblockchaininfo() + res1 = self.nodes[1].getblockchaininfo() + + self.nodes[0].generate(1) + self.nodes[1].generate(1) + + res0 = self.nodes[0].getblockchaininfo() + res1 = self.nodes[1].getblockchaininfo() + + rightChain = res0["bestblockhash"] + wrongChain = res1["bestblockhash"] + + assert not rightChain==wrongChain + + self.log.info("Restart Node1 (after upgrade window) with forced rewindblockindex") + # restart node1 with right nuparams and rewindblockindex + # on regtest only forced rewinds possible because there are no checkpoints + self.restart_node(1, extra_args=['-nuparams=PoS:201', '-nuparams=PoS_v2:201', '-rewindblockindex', '-debug=0']) + + res0 = self.nodes[0].getblockchaininfo() + res1 = self.nodes[1].getblockchaininfo() + + connect_nodes(self.nodes[0], 1) + self.sync_blocks([self.nodes[i] for i in [0, 1]], timeout=120) + + res0 = self.nodes[0].getblockchaininfo() + res1 = self.nodes[1].getblockchaininfo() + + assert_equal(res1["bestblockhash"], rightChain) + + self.log.info("Node1 on right chain") + +if __name__ == '__main__': + RewindBlockIndexCheckpointTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 99f6a0479af35..d2635566148d9 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -193,6 +193,7 @@ # Longest test should go first, to favor running tests in parallel # vv Tests less than 20m vv 'feature_dbcrash.py', + 'feature_rewind.py', # ~ 353 sec 'sapling_fillblock.py', # ~ 780 sec 'feature_fee_estimation.py', # ~ 360 sec # vv Tests less than 5m vv