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