aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci/test/04_install.sh12
-rw-r--r--doc/developer-notes.md2
-rw-r--r--src/consensus/tx_verify.cpp9
-rw-r--r--src/httpserver.cpp26
-rw-r--r--src/init.cpp10
-rw-r--r--src/net_processing.cpp4
-rw-r--r--src/node/blockstorage.cpp9
-rw-r--r--src/qt/bitcoingui.cpp6
-rw-r--r--src/qt/coincontroldialog.cpp4
-rw-r--r--src/qt/guiutil.cpp3
-rw-r--r--src/qt/overviewpage.cpp4
-rw-r--r--src/qt/rpcconsole.cpp6
-rw-r--r--src/qt/sendcoinsentry.cpp4
-rw-r--r--src/qt/signverifymessagedialog.cpp4
-rw-r--r--src/qt/test/wallettests.cpp2
-rw-r--r--src/qt/transactionview.cpp4
-rw-r--r--src/rpc/blockchain.cpp8
-rw-r--r--src/script/descriptor.cpp183
-rw-r--r--src/script/descriptor.h23
-rw-r--r--src/script/interpreter.cpp6
-rw-r--r--src/test/descriptor_tests.cpp8
-rw-r--r--src/test/fuzz/load_external_block_file.cpp2
-rw-r--r--src/test/interfaces_tests.cpp2
-rw-r--r--src/test/util/setup_common.cpp4
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp9
-rw-r--r--src/validation.cpp206
-rw-r--r--src/validation.h42
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.h4
-rw-r--r--src/wallet/interfaces.cpp6
-rw-r--r--src/wallet/rpcdump.cpp17
-rw-r--r--src/wallet/rpcwallet.cpp2
-rw-r--r--src/wallet/scriptpubkeyman.cpp126
-rw-r--r--src/wallet/scriptpubkeyman.h25
-rw-r--r--src/wallet/wallet.cpp121
-rw-r--r--src/wallet/wallet.h24
-rw-r--r--src/wallet/walletdb.cpp55
-rw-r--r--src/wallet/walletdb.h3
-rw-r--r--src/wallet/walletutil.h3
-rwxr-xr-xtest/functional/wallet_importdescriptors.py159
-rwxr-xr-xtest/functional/wallet_listdescriptors.py4
40 files changed, 749 insertions, 402 deletions
diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh
index 01dbfe221b..2079d2ed2b 100755
--- a/ci/test/04_install.sh
+++ b/ci/test/04_install.sh
@@ -11,6 +11,7 @@ if [[ $QEMU_USER_CMD == qemu-s390* ]]; then
fi
if [ "$CI_OS_NAME" == "macos" ]; then
+ sudo -H pip3 install --upgrade pip
IN_GETOPT_BIN="/usr/local/opt/gnu-getopt/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES
fi
@@ -83,11 +84,14 @@ fi
DOCKER_EXEC echo "Free disk space:"
DOCKER_EXEC df -h
-if [ ! -d ${DIR_QA_ASSETS} ]; then
- DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
+if [ "$RUN_FUZZ_TESTS" = "true" ] || [ "$RUN_UNIT_TESTS" = "true" ] || [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then
+ if [ ! -d ${DIR_QA_ASSETS} ]; then
+ DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
+ fi
+
+ export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/
+ export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
fi
-export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/
-export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
DOCKER_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/"
diff --git a/doc/developer-notes.md b/doc/developer-notes.md
index c3a63b3523..583c50a763 100644
--- a/doc/developer-notes.md
+++ b/doc/developer-notes.md
@@ -88,7 +88,7 @@ code.
separate words (snake_case).
- Class member variables have a `m_` prefix.
- Global variables have a `g_` prefix.
- - Compile-time constant names are all uppercase, and use `_` to separate words.
+ - Constant names are all uppercase, and use `_` to separate words.
- Class names, function names, and method names are UpperCamelCase
(PascalCase). Do not prefix class names with `C`.
- Test suite naming convention: The Boost test suite in file
diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp
index f595f16eab..88d8da6ed5 100644
--- a/src/consensus/tx_verify.cpp
+++ b/src/consensus/tx_verify.cpp
@@ -20,6 +20,15 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
return true;
if ((int64_t)tx.nLockTime < ((int64_t)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
return true;
+
+ // Even if tx.nLockTime isn't satisfied by nBlockHeight/nBlockTime, a
+ // transaction is still considered final if all inputs' nSequence ==
+ // SEQUENCE_FINAL (0xffffffff), in which case nLockTime is ignored.
+ //
+ // Because of this behavior OP_CHECKLOCKTIMEVERIFY/CheckLockTime() will
+ // also check that the spending input's nSequence != SEQUENCE_FINAL,
+ // ensuring that an unsatisfied nLockTime value will actually cause
+ // IsFinalTx() to return false here:
for (const auto& txin : tx.vin) {
if (!(txin.nSequence == CTxIn::SEQUENCE_FINAL))
return false;
diff --git a/src/httpserver.cpp b/src/httpserver.cpp
index b3984a43bb..8741ad9b86 100644
--- a/src/httpserver.cpp
+++ b/src/httpserver.cpp
@@ -136,7 +136,7 @@ static struct evhttp* eventHTTP = nullptr;
//! List of subnets to allow RPC connections from
static std::vector<CSubNet> rpc_allow_subnets;
//! Work queue for handling longer requests off the event loop thread
-static WorkQueue<HTTPClosure>* workQueue = nullptr;
+static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr};
//! Handlers for (sub)paths
static std::vector<HTTPPathHandler> pathHandlers;
//! Bound listening sockets
@@ -256,10 +256,10 @@ static void http_request_cb(struct evhttp_request* req, void* arg)
// Dispatch to worker thread
if (i != iend) {
std::unique_ptr<HTTPWorkItem> item(new HTTPWorkItem(std::move(hreq), path, i->handler));
- assert(workQueue);
- if (workQueue->Enqueue(item.get()))
+ assert(g_work_queue);
+ if (g_work_queue->Enqueue(item.get())) {
item.release(); /* if true, queue took ownership */
- else {
+ } else {
LogPrintf("WARNING: request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting\n");
item->req->WriteReply(HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded");
}
@@ -392,7 +392,7 @@ bool InitHTTPServer()
int workQueueDepth = std::max((long)gArgs.GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L);
LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth);
- workQueue = new WorkQueue<HTTPClosure>(workQueueDepth);
+ g_work_queue = std::make_unique<WorkQueue<HTTPClosure>>(workQueueDepth);
// transfer ownership to eventBase/HTTP via .release()
eventBase = base_ctr.release();
eventHTTP = http_ctr.release();
@@ -424,7 +424,7 @@ void StartHTTPServer()
g_thread_http = std::thread(ThreadHTTP, eventBase);
for (int i = 0; i < rpcThreads; i++) {
- g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue, i);
+ g_thread_http_workers.emplace_back(HTTPWorkQueueRun, g_work_queue.get(), i);
}
}
@@ -435,16 +435,17 @@ void InterruptHTTPServer()
// Reject requests on current connections
evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr);
}
- if (workQueue)
- workQueue->Interrupt();
+ if (g_work_queue) {
+ g_work_queue->Interrupt();
+ }
}
void StopHTTPServer()
{
LogPrint(BCLog::HTTP, "Stopping HTTP server\n");
- if (workQueue) {
+ if (g_work_queue) {
LogPrint(BCLog::HTTP, "Waiting for HTTP worker threads to exit\n");
- for (auto& thread: g_thread_http_workers) {
+ for (auto& thread : g_thread_http_workers) {
thread.join();
}
g_thread_http_workers.clear();
@@ -467,10 +468,7 @@ void StopHTTPServer()
event_base_free(eventBase);
eventBase = nullptr;
}
- if (workQueue) {
- delete workQueue;
- workQueue = nullptr;
- }
+ g_work_queue.reset();
LogPrint(BCLog::HTTP, "Stopped HTTP server\n");
}
diff --git a/src/init.cpp b/src/init.cpp
index da0447ca79..a88adbaa21 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1372,7 +1372,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// block file from disk.
// Note that it also sets fReindex based on the disk flag!
// From here on out fReindex and fReset mean something different!
- if (!chainman.LoadBlockIndex(chainparams)) {
+ if (!chainman.LoadBlockIndex()) {
if (ShutdownRequested()) break;
strLoadError = _("Error loading block database");
break;
@@ -1396,7 +1396,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// If we're not mid-reindex (based on disk + args), add a genesis block on disk
// (otherwise we use the one already on disk).
// This is called again in ThreadImport after the reindex completes.
- if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock(chainparams)) {
+ if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) {
strLoadError = _("Error initializing block database");
break;
}
@@ -1427,7 +1427,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
- if (!chainstate->ReplayBlocks(chainparams)) {
+ if (!chainstate->ReplayBlocks()) {
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.");
failed_chainstate_init = true;
break;
@@ -1439,7 +1439,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
- if (!chainstate->LoadChainTip(chainparams)) {
+ if (!chainstate->LoadChainTip()) {
strLoadError = _("Error initializing block database");
failed_chainstate_init = true;
break; // out of the per-chainstate loop
@@ -1461,7 +1461,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
LOCK(cs_main);
auto chainstates{chainman.GetAll()};
if (std::any_of(chainstates.begin(), chainstates.end(),
- [&chainparams](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(chainparams); })) {
+ [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainparams.GetConsensus().SegwitHeight);
break;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 2175b76d16..ab9c41ca2b 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1709,7 +1709,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv&
} // release cs_main before calling ActivateBestChain
if (need_activate_chain) {
BlockValidationState state;
- if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) {
+ if (!m_chainman.ActiveChainstate().ActivateBestChain(state, a_recent_block)) {
LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString());
}
}
@@ -2935,7 +2935,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
a_recent_block = most_recent_block;
}
BlockValidationState state;
- if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) {
+ if (!m_chainman.ActiveChainstate().ActivateBestChain(state, a_recent_block)) {
LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString());
}
}
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index 013d61282b..0083b74b33 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -493,7 +493,6 @@ struct CImportingNow {
void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args)
{
- const CChainParams& chainparams = Params();
ScheduleBatchPriority();
{
@@ -512,7 +511,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
break; // This error is logged in OpenBlockFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
- chainman.ActiveChainstate().LoadExternalBlockFile(chainparams, file, &pos);
+ chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
@@ -523,7 +522,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
fReindex = false;
LogPrintf("Reindexing finished\n");
// To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
- chainman.ActiveChainstate().LoadGenesisBlock(chainparams);
+ chainman.ActiveChainstate().LoadGenesisBlock();
}
// -loadblock=
@@ -531,7 +530,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
FILE* file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", path.string());
- chainman.ActiveChainstate().LoadExternalBlockFile(chainparams, file);
+ chainman.ActiveChainstate().LoadExternalBlockFile(file);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
return;
@@ -548,7 +547,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
// the relevant pointers before the ABC call.
for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
- if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
+ if (!chainstate->ActivateBestChain(state, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
StartShutdown();
return;
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 3d632ec702..f8aeb01659 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -1173,14 +1173,12 @@ void BitcoinGUI::message(const QString& title, QString message, unsigned int sty
void BitcoinGUI::changeEvent(QEvent *e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
overviewAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/overview")));
sendCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/send")));
receiveCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/receiving_addresses")));
historyAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/history")));
}
-#endif
QMainWindow::changeEvent(e);
@@ -1511,14 +1509,14 @@ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event)
void UnitDisplayStatusBarControl::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
QString style = QString("QLabel { color : %1 }").arg(m_platform_style->SingleColor().name());
if (style != styleSheet()) {
setStyleSheet(style);
}
}
-#endif
+
+ QLabel::changeEvent(e);
}
/** Creates context menu, its actions, and wires up all the relevant signals for mouse events. */
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index 2360fa9b37..d2a9365890 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -564,11 +564,11 @@ void CoinControlDialog::updateLabels(CCoinControl& m_coin_control, WalletModel *
void CoinControlDialog::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
updateView();
}
-#endif
+
+ QDialog::changeEvent(e);
}
void CoinControlDialog::updateView()
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 393dca8ccd..ecdfce2f5a 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -809,11 +809,10 @@ void ThemedLabel::setThemedPixmap(const QString& image_filename, int width, int
void ThemedLabel::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
updateThemedPixmap();
}
-#endif
+
QLabel::changeEvent(e);
}
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp
index 26e3dd0d60..1fd1ff3142 100644
--- a/src/qt/overviewpage.cpp
+++ b/src/qt/overviewpage.cpp
@@ -297,13 +297,13 @@ void OverviewPage::setWalletModel(WalletModel *model)
void OverviewPage::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning"));
ui->labelTransactionsStatus->setIcon(icon);
ui->labelWalletStatus->setIcon(icon);
}
-#endif
+
+ QWidget::changeEvent(e);
}
void OverviewPage::updateDisplayUnit()
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index ff4bfb16f6..c973fdbe78 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -529,6 +529,8 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
//: Secondary shortcut to decrease the RPC console font size.
GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_"));
+ ui->promptIcon->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/prompticon")));
+
// Install event filter for up and down arrow
ui->lineEdit->installEventFilter(this);
ui->lineEdit->setMaxLength(16 * 1024 * 1024);
@@ -882,7 +884,6 @@ void RPCConsole::keyPressEvent(QKeyEvent *event)
void RPCConsole::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
ui->clearButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/fontbigger")));
@@ -896,7 +897,8 @@ void RPCConsole::changeEvent(QEvent* e)
platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}
-#endif
+
+ QWidget::changeEvent(e);
}
void RPCConsole::message(int category, const QString &message, bool html)
diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp
index f701bb9615..683c0441fa 100644
--- a/src/qt/sendcoinsentry.cpp
+++ b/src/qt/sendcoinsentry.cpp
@@ -238,7 +238,6 @@ void SendCoinsEntry::updateDisplayUnit()
void SendCoinsEntry::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book")));
ui->pasteButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste")));
@@ -246,7 +245,8 @@ void SendCoinsEntry::changeEvent(QEvent* e)
ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
}
-#endif
+
+ QStackedWidget::changeEvent(e);
}
bool SendCoinsEntry::updateLabel(const QString &address)
diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp
index b982cc577d..33589f09bf 100644
--- a/src/qt/signverifymessagedialog.cpp
+++ b/src/qt/signverifymessagedialog.cpp
@@ -286,7 +286,6 @@ bool SignVerifyMessageDialog::eventFilter(QObject *object, QEvent *event)
void SignVerifyMessageDialog::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
ui->addressBookButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book")));
ui->pasteButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste")));
@@ -297,5 +296,6 @@ void SignVerifyMessageDialog::changeEvent(QEvent* e)
ui->verifyMessageButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/transaction_0")));
ui->clearButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
}
-#endif
+
+ QDialog::changeEvent(e);
}
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index 3e1a0e0fa9..e883337fb5 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -69,7 +69,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe
->findChild<QCheckBox*>("optInRBF")
->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
uint256 txid;
- boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
+ boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const uint256& hash, ChangeType status) {
if (status == CT_NEW) txid = hash;
}));
ConfirmSend();
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 4b1a546c7c..83d17a32c0 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -245,7 +245,6 @@ void TransactionView::setModel(WalletModel *_model)
void TransactionView::changeEvent(QEvent* e)
{
-#ifdef Q_OS_MACOS
if (e->type() == QEvent::PaletteChange) {
watchOnlyWidget->setItemIcon(
TransactionFilterProxy::WatchOnlyFilter_Yes,
@@ -254,7 +253,8 @@ void TransactionView::changeEvent(QEvent* e)
TransactionFilterProxy::WatchOnlyFilter_No,
m_platform_style->SingleColorIcon(QStringLiteral(":/icons/eye_minus")));
}
-#endif
+
+ QWidget::changeEvent(e);
}
void TransactionView::chooseDate(int idx)
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f996aa9c76..02f8cec54c 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1706,7 +1706,7 @@ static RPCHelpMan preciousblock()
}
BlockValidationState state;
- chainman.ActiveChainstate().PreciousBlock(state, Params(), pblockindex);
+ chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
@@ -1743,10 +1743,10 @@ static RPCHelpMan invalidateblock()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
- chainman.ActiveChainstate().InvalidateBlock(state, Params(), pblockindex);
+ chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
if (state.IsValid()) {
- chainman.ActiveChainstate().ActivateBestChain(state, Params());
+ chainman.ActiveChainstate().ActivateBestChain(state);
}
if (!state.IsValid()) {
@@ -1787,7 +1787,7 @@ static RPCHelpMan reconsiderblock()
}
BlockValidationState state;
- chainman.ActiveChainstate().ActivateBestChain(state, Params());
+ chainman.ActiveChainstate().ActivateBestChain(state);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index fdbd2d7fc7..682b55742a 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -166,7 +166,7 @@ public:
* write_cache is the cache to write keys to (if not nullptr)
* Caches are not exclusive but this is not tested. Currently we use them exclusively
*/
- virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) = 0;
+ virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const = 0;
/** Whether this represent multiple public keys at different positions. */
virtual bool IsRange() const = 0;
@@ -181,7 +181,7 @@ public:
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
/** Get the descriptor string form with the xpub at the last hardened derivation */
- virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const = 0;
+ virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache = nullptr) const = 0;
/** Derive a private key, if private data is available in arg. */
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
@@ -199,7 +199,7 @@ class OriginPubkeyProvider final : public PubkeyProvider
public:
OriginPubkeyProvider(uint32_t exp_index, KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider) : PubkeyProvider(exp_index), m_origin(std::move(info)), m_provider(std::move(provider)) {}
- bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override
+ bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{
if (!m_provider->GetPubKey(pos, arg, key, info, read_cache, write_cache)) return false;
std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint);
@@ -216,10 +216,10 @@ public:
ret = "[" + OriginString() + "]" + std::move(sub);
return true;
}
- bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
+ bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
{
std::string sub;
- if (!m_provider->ToNormalizedString(arg, sub, priv)) return false;
+ if (!m_provider->ToNormalizedString(arg, sub, cache)) return false;
// If m_provider is a BIP32PubkeyProvider, we may get a string formatted like a OriginPubkeyProvider
// In that case, we need to strip out the leading square bracket and fingerprint from the substring,
// and append that to our own origin string.
@@ -245,7 +245,7 @@ class ConstPubkeyProvider final : public PubkeyProvider
public:
ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {}
- bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override
+ bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{
key = m_pubkey;
info.path.clear();
@@ -263,9 +263,8 @@ public:
ret = EncodeSecret(key);
return true;
}
- bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
+ bool ToNormalizedString(const SigningProvider& arg, std::string& ret, const DescriptorCache* cache) const override
{
- if (priv) return ToPrivateString(arg, ret);
ret = ToString();
return true;
}
@@ -288,9 +287,6 @@ class BIP32PubkeyProvider final : public PubkeyProvider
CExtPubKey m_root_extkey;
KeyPath m_path;
DeriveType m_derive;
- // Cache of the parent of the final derived pubkeys.
- // Primarily useful for situations when no read_cache is provided
- CExtPubKey m_cached_xpub;
bool GetExtKey(const SigningProvider& arg, CExtKey& ret) const
{
@@ -305,11 +301,14 @@ class BIP32PubkeyProvider final : public PubkeyProvider
}
// Derives the last xprv
- bool GetDerivedExtKey(const SigningProvider& arg, CExtKey& xprv) const
+ bool GetDerivedExtKey(const SigningProvider& arg, CExtKey& xprv, CExtKey& last_hardened) const
{
if (!GetExtKey(arg, xprv)) return false;
for (auto entry : m_path) {
xprv.Derive(xprv, entry);
+ if (entry >> 31) {
+ last_hardened = xprv;
+ }
}
return true;
}
@@ -327,7 +326,7 @@ public:
BIP32PubkeyProvider(uint32_t exp_index, const CExtPubKey& extkey, KeyPath path, DeriveType derive) : PubkeyProvider(exp_index), m_root_extkey(extkey), m_path(std::move(path)), m_derive(derive) {}
bool IsRange() const override { return m_derive != DeriveType::NO; }
size_t GetSize() const override { return 33; }
- bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key_out, KeyOriginInfo& final_info_out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override
+ bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key_out, KeyOriginInfo& final_info_out, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) const override
{
// Info of parent of the to be derived pubkey
KeyOriginInfo parent_info;
@@ -343,6 +342,7 @@ public:
// Derive keys or fetch them from cache
CExtPubKey final_extkey = m_root_extkey;
CExtPubKey parent_extkey = m_root_extkey;
+ CExtPubKey last_hardened_extkey;
bool der = true;
if (read_cache) {
if (!read_cache->GetCachedDerivedExtPubKey(m_expr_index, pos, final_extkey)) {
@@ -352,16 +352,17 @@ public:
final_extkey = parent_extkey;
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
}
- } else if (m_cached_xpub.pubkey.IsValid() && m_derive != DeriveType::HARDENED) {
- parent_extkey = final_extkey = m_cached_xpub;
- if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
} else if (IsHardened()) {
CExtKey xprv;
- if (!GetDerivedExtKey(arg, xprv)) return false;
+ CExtKey lh_xprv;
+ if (!GetDerivedExtKey(arg, xprv, lh_xprv)) return false;
parent_extkey = xprv.Neuter();
if (m_derive == DeriveType::UNHARDENED) der = xprv.Derive(xprv, pos);
if (m_derive == DeriveType::HARDENED) der = xprv.Derive(xprv, pos | 0x80000000UL);
final_extkey = xprv.Neuter();
+ if (lh_xprv.key.IsValid()) {
+ last_hardened_extkey = lh_xprv.Neuter();
+ }
} else {
for (auto entry : m_path) {
der = parent_extkey.Derive(parent_extkey, entry);
@@ -376,15 +377,14 @@ public:
final_info_out = final_info_out_tmp;
key_out = final_extkey.pubkey;
- // We rely on the consumer to check that m_derive isn't HARDENED as above
- // But we can't have already cached something in case we read something from the cache
- // and parent_extkey isn't actually the parent.
- if (!m_cached_xpub.pubkey.IsValid()) m_cached_xpub = parent_extkey;
-
if (write_cache) {
// Only cache parent if there is any unhardened derivation
if (m_derive != DeriveType::HARDENED) {
write_cache->CacheParentExtPubKey(m_expr_index, parent_extkey);
+ // Cache last hardened xpub if we have it
+ if (last_hardened_extkey.pubkey.IsValid()) {
+ write_cache->CacheLastHardenedExtPubKey(m_expr_index, last_hardened_extkey);
+ }
} else if (final_info_out.path.size() > 0) {
write_cache->CacheDerivedExtPubKey(m_expr_index, pos, final_extkey);
}
@@ -412,11 +412,10 @@ public:
}
return true;
}
- bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override
+ bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override
{
// For hardened derivation type, just return the typical string, nothing to normalize
if (m_derive == DeriveType::HARDENED) {
- if (priv) return ToPrivateString(arg, out);
out = ToString();
return true;
}
@@ -429,33 +428,42 @@ public:
}
// Either no derivation or all unhardened derivation
if (i == -1) {
- if (priv) return ToPrivateString(arg, out);
out = ToString();
return true;
}
- // Derive the xpub at the last hardened step
- CExtKey xprv;
- if (!GetExtKey(arg, xprv)) return false;
+ // Get the path to the last hardened stup
KeyOriginInfo origin;
int k = 0;
for (; k <= i; ++k) {
- // Derive
- xprv.Derive(xprv, m_path.at(k));
// Add to the path
origin.path.push_back(m_path.at(k));
- // First derivation element, get the fingerprint for origin
- if (k == 0) {
- std::copy(xprv.vchFingerprint, xprv.vchFingerprint + 4, origin.fingerprint);
- }
}
// Build the remaining path
KeyPath end_path;
for (; k < (int)m_path.size(); ++k) {
end_path.push_back(m_path.at(k));
}
+ // Get the fingerprint
+ CKeyID id = m_root_extkey.pubkey.GetID();
+ std::copy(id.begin(), id.begin() + 4, origin.fingerprint);
+
+ CExtPubKey xpub;
+ CExtKey lh_xprv;
+ // If we have the cache, just get the parent xpub
+ if (cache != nullptr) {
+ cache->GetCachedLastHardenedExtPubKey(m_expr_index, xpub);
+ }
+ if (!xpub.pubkey.IsValid()) {
+ // Cache miss, or nor cache, or need privkey
+ CExtKey xprv;
+ if (!GetDerivedExtKey(arg, xprv, lh_xprv)) return false;
+ xpub = lh_xprv.Neuter();
+ }
+ assert(xpub.pubkey.IsValid());
+
// Build the string
std::string origin_str = HexStr(origin.fingerprint) + FormatHDKeypath(origin.path);
- out = "[" + origin_str + "]" + (priv ? EncodeExtKey(xprv) : EncodeExtPubKey(xprv.Neuter())) + FormatHDKeypath(end_path);
+ out = "[" + origin_str + "]" + EncodeExtPubKey(xpub) + FormatHDKeypath(end_path);
if (IsRange()) {
out += "/*";
assert(m_derive == DeriveType::UNHARDENED);
@@ -465,7 +473,8 @@ public:
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
CExtKey extkey;
- if (!GetDerivedExtKey(arg, extkey)) return false;
+ CExtKey dummy;
+ if (!GetDerivedExtKey(arg, extkey, dummy)) return false;
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
key = extkey.key;
@@ -508,6 +517,13 @@ public:
DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(Vector(std::move(script))) {}
DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::vector<std::unique_ptr<DescriptorImpl>> scripts, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(std::move(scripts)) {}
+ enum class StringType
+ {
+ PUBLIC,
+ PRIVATE,
+ NORMALIZED,
+ };
+
bool IsSolvable() const override
{
for (const auto& arg : m_subdescriptor_args) {
@@ -527,19 +543,19 @@ public:
return false;
}
- virtual bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const
+ virtual bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const
{
size_t pos = 0;
for (const auto& scriptarg : m_subdescriptor_args) {
if (pos++) ret += ",";
std::string tmp;
- if (!scriptarg->ToStringHelper(arg, tmp, priv, normalized)) return false;
+ if (!scriptarg->ToStringHelper(arg, tmp, type, cache)) return false;
ret += std::move(tmp);
}
return true;
}
- bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv, bool normalized) const
+ bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const
{
std::string extra = ToStringExtra();
size_t pos = extra.size() > 0 ? 1 : 0;
@@ -547,17 +563,21 @@ public:
for (const auto& pubkey : m_pubkey_args) {
if (pos++) ret += ",";
std::string tmp;
- if (normalized) {
- if (!pubkey->ToNormalizedString(*arg, tmp, priv)) return false;
- } else if (priv) {
- if (!pubkey->ToPrivateString(*arg, tmp)) return false;
- } else {
- tmp = pubkey->ToString();
+ switch (type) {
+ case StringType::NORMALIZED:
+ if (!pubkey->ToNormalizedString(*arg, tmp, cache)) return false;
+ break;
+ case StringType::PRIVATE:
+ if (!pubkey->ToPrivateString(*arg, tmp)) return false;
+ break;
+ case StringType::PUBLIC:
+ tmp = pubkey->ToString();
+ break;
}
ret += std::move(tmp);
}
std::string subscript;
- if (!ToStringSubScriptHelper(arg, subscript, priv, normalized)) return false;
+ if (!ToStringSubScriptHelper(arg, subscript, type, cache)) return false;
if (pos && subscript.size()) ret += ',';
out = std::move(ret) + std::move(subscript) + ")";
return true;
@@ -566,20 +586,20 @@ public:
std::string ToString() const final
{
std::string ret;
- ToStringHelper(nullptr, ret, false, false);
+ ToStringHelper(nullptr, ret, StringType::PUBLIC);
return AddChecksum(ret);
}
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final
{
- bool ret = ToStringHelper(&arg, out, true, false);
+ bool ret = ToStringHelper(&arg, out, StringType::PRIVATE);
out = AddChecksum(out);
return ret;
}
- bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override final
+ bool ToNormalizedString(const SigningProvider& arg, std::string& out, const DescriptorCache* cache) const override final
{
- bool ret = ToStringHelper(&arg, out, priv, true);
+ bool ret = ToStringHelper(&arg, out, StringType::NORMALIZED, cache);
out = AddChecksum(out);
return ret;
}
@@ -833,7 +853,7 @@ protected:
out.tr_spenddata[output].Merge(builder.GetSpendData());
return Vector(GetScriptForDestination(output));
}
- bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override
+ bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, const StringType type, const DescriptorCache* cache = nullptr) const override
{
if (m_depths.empty()) return true;
std::vector<bool> path;
@@ -844,7 +864,7 @@ protected:
path.push_back(false);
}
std::string tmp;
- if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, priv, normalized)) return false;
+ if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, type, cache)) return false;
ret += std::move(tmp);
while (!path.empty() && path.back()) {
if (path.size() > 1) ret += '}';
@@ -1411,6 +1431,11 @@ void DescriptorCache::CacheDerivedExtPubKey(uint32_t key_exp_pos, uint32_t der_i
xpubs[der_index] = xpub;
}
+void DescriptorCache::CacheLastHardenedExtPubKey(uint32_t key_exp_pos, const CExtPubKey& xpub)
+{
+ m_last_hardened_xpubs[key_exp_pos] = xpub;
+}
+
bool DescriptorCache::GetCachedParentExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const
{
const auto& it = m_parent_xpubs.find(key_exp_pos);
@@ -1429,6 +1454,55 @@ bool DescriptorCache::GetCachedDerivedExtPubKey(uint32_t key_exp_pos, uint32_t d
return true;
}
+bool DescriptorCache::GetCachedLastHardenedExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const
+{
+ const auto& it = m_last_hardened_xpubs.find(key_exp_pos);
+ if (it == m_last_hardened_xpubs.end()) return false;
+ xpub = it->second;
+ return true;
+}
+
+DescriptorCache DescriptorCache::MergeAndDiff(const DescriptorCache& other)
+{
+ DescriptorCache diff;
+ for (const auto& parent_xpub_pair : other.GetCachedParentExtPubKeys()) {
+ CExtPubKey xpub;
+ if (GetCachedParentExtPubKey(parent_xpub_pair.first, xpub)) {
+ if (xpub != parent_xpub_pair.second) {
+ throw std::runtime_error(std::string(__func__) + ": New cached parent xpub does not match already cached parent xpub");
+ }
+ continue;
+ }
+ CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second);
+ diff.CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second);
+ }
+ for (const auto& derived_xpub_map_pair : other.GetCachedDerivedExtPubKeys()) {
+ for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) {
+ CExtPubKey xpub;
+ if (GetCachedDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, xpub)) {
+ if (xpub != derived_xpub_pair.second) {
+ throw std::runtime_error(std::string(__func__) + ": New cached derived xpub does not match already cached derived xpub");
+ }
+ continue;
+ }
+ CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second);
+ diff.CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second);
+ }
+ }
+ for (const auto& lh_xpub_pair : other.GetCachedLastHardenedExtPubKeys()) {
+ CExtPubKey xpub;
+ if (GetCachedLastHardenedExtPubKey(lh_xpub_pair.first, xpub)) {
+ if (xpub != lh_xpub_pair.second) {
+ throw std::runtime_error(std::string(__func__) + ": New cached last hardened xpub does not match already cached last hardened xpub");
+ }
+ continue;
+ }
+ CacheLastHardenedExtPubKey(lh_xpub_pair.first, lh_xpub_pair.second);
+ diff.CacheLastHardenedExtPubKey(lh_xpub_pair.first, lh_xpub_pair.second);
+ }
+ return diff;
+}
+
const ExtPubKeyMap DescriptorCache::GetCachedParentExtPubKeys() const
{
return m_parent_xpubs;
@@ -1438,3 +1512,8 @@ const std::unordered_map<uint32_t, ExtPubKeyMap> DescriptorCache::GetCachedDeriv
{
return m_derived_xpubs;
}
+
+const ExtPubKeyMap DescriptorCache::GetCachedLastHardenedExtPubKeys() const
+{
+ return m_last_hardened_xpubs;
+}
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index 332ae2f230..ecd7c4eea5 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -22,6 +22,8 @@ private:
std::unordered_map<uint32_t, ExtPubKeyMap> m_derived_xpubs;
/** Map key expression index -> parent xpub */
ExtPubKeyMap m_parent_xpubs;
+ /** Map key expression index -> last hardened xpub */
+ ExtPubKeyMap m_last_hardened_xpubs;
public:
/** Cache a parent xpub
@@ -50,11 +52,30 @@ public:
* @param[in] xpub The CExtPubKey to get from cache
*/
bool GetCachedDerivedExtPubKey(uint32_t key_exp_pos, uint32_t der_index, CExtPubKey& xpub) const;
+ /** Cache a last hardened xpub
+ *
+ * @param[in] key_exp_pos Position of the key expression within the descriptor
+ * @param[in] xpub The CExtPubKey to cache
+ */
+ void CacheLastHardenedExtPubKey(uint32_t key_exp_pos, const CExtPubKey& xpub);
+ /** Retrieve a cached last hardened xpub
+ *
+ * @param[in] key_exp_pos Position of the key expression within the descriptor
+ * @param[in] xpub The CExtPubKey to get from cache
+ */
+ bool GetCachedLastHardenedExtPubKey(uint32_t key_exp_pos, CExtPubKey& xpub) const;
/** Retrieve all cached parent xpubs */
const ExtPubKeyMap GetCachedParentExtPubKeys() const;
/** Retrieve all cached derived xpubs */
const std::unordered_map<uint32_t, ExtPubKeyMap> GetCachedDerivedExtPubKeys() const;
+ /** Retrieve all cached last hardened xpubs */
+ const ExtPubKeyMap GetCachedLastHardenedExtPubKeys() const;
+
+ /** Combine another DescriptorCache into this one.
+ * Returns a cache containing the items from the other cache unknown to current cache
+ */
+ DescriptorCache MergeAndDiff(const DescriptorCache& other);
};
/** \brief Interface for parsed descriptor objects.
@@ -94,7 +115,7 @@ struct Descriptor {
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
/** Convert the descriptor to a normalized string. Normalized descriptors have the xpub at the last hardened step. This fails if the provided provider does not have the private keys to derive that xpub. */
- virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, bool priv) const = 0;
+ virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const = 0;
/** Expand a descriptor at a specified position.
*
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 8cebbc5d10..ef48f89965 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1740,9 +1740,9 @@ bool GenericTransactionSignatureChecker<T>::CheckLockTime(const CScriptNum& nLoc
if (nLockTime > (int64_t)txTo->nLockTime)
return false;
- // Finally the nLockTime feature can be disabled and thus
- // CHECKLOCKTIMEVERIFY bypassed if every txin has been
- // finalized by setting nSequence to maxint. The
+ // Finally the nLockTime feature can be disabled in IsFinalTx()
+ // and thus CHECKLOCKTIMEVERIFY bypassed if every txin has
+ // been finalized by setting nSequence to maxint. The
// transaction would be allowed into the blockchain, making
// the opcode ineffective.
//
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 36e2dac3ff..8553f80a17 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -124,14 +124,10 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
// Check that private can produce the normalized descriptors
std::string norm1;
- BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1, false));
+ BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1));
BOOST_CHECK(EqualDescriptor(norm1, norm_pub));
- BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1, false));
+ BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1));
BOOST_CHECK(EqualDescriptor(norm1, norm_pub));
- BOOST_CHECK(parse_priv->ToNormalizedString(keys_priv, norm1, true));
- BOOST_CHECK(EqualDescriptor(norm1, norm_prv));
- BOOST_CHECK(parse_pub->ToNormalizedString(keys_priv, norm1, true));
- BOOST_CHECK(EqualDescriptor(norm1, norm_prv));
// Check whether IsRange on both returns the expected result
BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0);
diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp
index a7770c90e8..bfa977520b 100644
--- a/src/test/fuzz/load_external_block_file.cpp
+++ b/src/test/fuzz/load_external_block_file.cpp
@@ -32,5 +32,5 @@ FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file)
return;
}
FlatFilePos flat_file_pos;
- g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(Params(), fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr);
+ g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr);
}
diff --git a/src/test/interfaces_tests.cpp b/src/test/interfaces_tests.cpp
index 42a7c7798c..44779f7d7c 100644
--- a/src/test/interfaces_tests.cpp
+++ b/src/test/interfaces_tests.cpp
@@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(findCommonAncestor)
auto* orig_tip = active.Tip();
for (int i = 0; i < 10; ++i) {
BlockValidationState state;
- m_node.chainman->ActiveChainstate().InvalidateBlock(state, Params(), active.Tip());
+ m_node.chainman->ActiveChainstate().InvalidateBlock(state, active.Tip());
}
BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10);
coinbaseKey.MakeNewKey(true);
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index f71d9148b6..748272bb1d 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -186,12 +186,12 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
assert(!m_node.chainman->ActiveChainstate().CanFlushToDisk());
m_node.chainman->ActiveChainstate().InitCoinsCache(1 << 23);
assert(m_node.chainman->ActiveChainstate().CanFlushToDisk());
- if (!m_node.chainman->ActiveChainstate().LoadGenesisBlock(chainparams)) {
+ if (!m_node.chainman->ActiveChainstate().LoadGenesisBlock()) {
throw std::runtime_error("LoadGenesisBlock failed.");
}
BlockValidationState state;
- if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state, chainparams)) {
+ if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) {
throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 0b912acb08..7c1db9d4b9 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -31,7 +31,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
CTxMemPool& mempool = *m_node.mempool;
std::vector<CChainState*> chainstates;
- const CChainParams& chainparams = Params();
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
@@ -76,9 +75,9 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23));
// Unlike c1, which doesn't have any blocks. Gets us different tip, height.
- c2.LoadGenesisBlock(chainparams);
+ c2.LoadGenesisBlock();
BlockValidationState _;
- BOOST_CHECK(c2.ActivateBestChain(_, chainparams, nullptr));
+ BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(!manager.IsSnapshotValidated());
@@ -138,7 +137,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
{
LOCK(::cs_main);
c1.InitCoinsCache(1 << 23);
- BOOST_REQUIRE(c1.LoadGenesisBlock(Params()));
+ BOOST_REQUIRE(c1.LoadGenesisBlock());
c1.CoinsTip().SetBestBlock(InsecureRand256());
manager.MaybeRebalanceCaches();
}
@@ -156,7 +155,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
{
LOCK(::cs_main);
c2.InitCoinsCache(1 << 23);
- BOOST_REQUIRE(c2.LoadGenesisBlock(Params()));
+ BOOST_REQUIRE(c2.LoadGenesisBlock());
c2.CoinsTip().SetBestBlock(InsecureRand256());
manager.MaybeRebalanceCaches();
}
diff --git a/src/validation.cpp b/src/validation.cpp
index fe4de0d218..df0ec3bd4f 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1124,7 +1124,7 @@ static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainp
}
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
BlockValidationState state_dummy;
- active_chainstate.FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC);
+ active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
return result;
}
@@ -1210,6 +1210,7 @@ void CoinsViews::InitCache()
CChainState::CChainState(CTxMemPool& mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash)
: m_mempool(mempool),
+ m_params(::Params()),
m_blockman(blockman),
m_from_snapshot_blockhash(from_snapshot_blockhash) {}
@@ -1724,7 +1725,7 @@ static int64_t nBlocksTotal = 0;
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
* can fail if those validity checks fail (among other reasons). */
bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
- CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck)
+ CCoinsViewCache& view, bool fJustCheck)
{
AssertLockHeld(cs_main);
assert(pindex);
@@ -1744,7 +1745,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// is enforced in ContextualCheckBlockHeader(); we wouldn't want to
// re-enforce that rule here (at least until we make it impossible for
// GetAdjustedTime() to go backward).
- if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) {
+ if (!CheckBlock(block, state, m_params.GetConsensus(), !fJustCheck, !fJustCheck)) {
if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) {
// We don't write down blocks to disk if they may have been
// corrupted, so this should be impossible unless we're having hardware
@@ -1762,7 +1763,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// Special case for the genesis block, skipping connection of its transactions
// (its coinbase is unspendable)
- if (block.GetHash() == chainparams.GetConsensus().hashGenesisBlock) {
+ if (block.GetHash() == m_params.GetConsensus().hashGenesisBlock) {
if (!fJustCheck)
view.SetBestBlock(pindex->GetBlockHash());
return true;
@@ -1794,7 +1795,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// artificially set the default assumed verified block further back.
// The test against nMinimumChainWork prevents the skipping when denied access to any chain at
// least as good as the expected chain.
- fScriptChecks = (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, chainparams.GetConsensus()) <= 60 * 60 * 24 * 7 * 2);
+ fScriptChecks = (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2);
}
}
}
@@ -1874,9 +1875,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// be reset before it reaches block 1,983,702 and starts doing unnecessary
// BIP30 checking again.
assert(pindex->pprev);
- CBlockIndex *pindexBIP34height = pindex->pprev->GetAncestor(chainparams.GetConsensus().BIP34Height);
+ CBlockIndex* pindexBIP34height = pindex->pprev->GetAncestor(m_params.GetConsensus().BIP34Height);
//Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond.
- fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == chainparams.GetConsensus().BIP34Hash));
+ fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == m_params.GetConsensus().BIP34Hash));
// TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a
// consensus change that ensures coinbases at those heights can not
@@ -1894,12 +1895,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
// Start enforcing BIP68 (sequence locks)
int nLockTimeFlags = 0;
- if (pindex->nHeight >= chainparams.GetConsensus().CSVHeight) {
+ if (pindex->nHeight >= m_params.GetConsensus().CSVHeight) {
nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;
}
// Get the script flags for this block
- unsigned int flags = GetBlockScriptFlags(pindex, chainparams.GetConsensus());
+ unsigned int flags = GetBlockScriptFlags(pindex, m_params.GetConsensus());
int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1;
LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal);
@@ -1989,7 +1990,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2;
LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal);
- CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus());
+ CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, m_params.GetConsensus());
if (block.vtx[0]->GetValueOut() > blockReward) {
LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[0]->GetValueOut(), blockReward);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount");
@@ -2005,8 +2006,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
if (fJustCheck)
return true;
- if (!WriteUndoDataForBlock(blockundo, state, pindex, chainparams))
+ if (!WriteUndoDataForBlock(blockundo, state, pindex, m_params)) {
return false;
+ }
if (!pindex->IsValid(BLOCK_VALID_SCRIPTS)) {
pindex->RaiseValidity(BLOCK_VALID_SCRIPTS);
@@ -2059,7 +2061,6 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(
}
bool CChainState::FlushStateToDisk(
- const CChainParams& chainparams,
BlockValidationState &state,
FlushStateMode mode,
int nManualPruneHeight)
@@ -2096,7 +2097,7 @@ bool CChainState::FlushStateToDisk(
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
- m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload());
+ m_blockman.FindFilesToPrune(setFilesToPrune, m_params.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload());
fCheckForPruning = false;
}
if (!setFilesToPrune.empty()) {
@@ -2196,20 +2197,19 @@ bool CChainState::FlushStateToDisk(
return true;
}
-void CChainState::ForceFlushStateToDisk() {
+void CChainState::ForceFlushStateToDisk()
+{
BlockValidationState state;
- const CChainParams& chainparams = Params();
- if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS)) {
+ if (!this->FlushStateToDisk(state, FlushStateMode::ALWAYS)) {
LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString());
}
}
-void CChainState::PruneAndFlush() {
+void CChainState::PruneAndFlush()
+{
BlockValidationState state;
fCheckForPruning = true;
- const CChainParams& chainparams = Params();
-
- if (!this->FlushStateToDisk(chainparams, state, FlushStateMode::NONE)) {
+ if (!this->FlushStateToDisk(state, FlushStateMode::NONE)) {
LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString());
}
}
@@ -2278,7 +2278,7 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C
* disconnectpool (note that the caller is responsible for mempool consistency
* in any case).
*/
-bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool)
+bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_mempool.cs);
@@ -2288,8 +2288,9 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams&
// Read block from disk.
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock& block = *pblock;
- if (!ReadBlockFromDisk(block, pindexDelete, chainparams.GetConsensus()))
+ if (!ReadBlockFromDisk(block, pindexDelete, m_params.GetConsensus())) {
return error("DisconnectTip(): Failed to read block");
+ }
// Apply the block atomically to the chain state.
int64_t nStart = GetTimeMicros();
{
@@ -2302,8 +2303,9 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams&
}
LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI);
// Write the chain state to disk, if necessary.
- if (!FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED))
+ if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
+ }
if (disconnectpool) {
// Save transactions to re-add to mempool at end of reorg
@@ -2320,7 +2322,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams&
m_chain.SetTip(pindexDelete->pprev);
- UpdateTip(m_mempool, pindexDelete->pprev, chainparams, *this);
+ UpdateTip(m_mempool, pindexDelete->pprev, m_params, *this);
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
GetMainSignals().BlockDisconnected(pblock, pindexDelete);
@@ -2379,7 +2381,7 @@ public:
*
* The block is added to connectTrace if connection succeeds.
*/
-bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool)
+bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_mempool.cs);
@@ -2390,8 +2392,9 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
std::shared_ptr<const CBlock> pthisBlock;
if (!pblock) {
std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
- if (!ReadBlockFromDisk(*pblockNew, pindexNew, chainparams.GetConsensus()))
+ if (!ReadBlockFromDisk(*pblockNew, pindexNew, m_params.GetConsensus())) {
return AbortNode(state, "Failed to read block");
+ }
pthisBlock = pblockNew;
} else {
pthisBlock = pblock;
@@ -2403,7 +2406,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO);
{
CCoinsViewCache view(&CoinsTip());
- bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, chainparams);
+ bool rv = ConnectBlock(blockConnecting, state, pindexNew, view);
GetMainSignals().BlockChecked(blockConnecting, state);
if (!rv) {
if (state.IsInvalid())
@@ -2419,8 +2422,9 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3;
LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO, nTimeFlush * MILLI / nBlocksTotal);
// Write the chain state to disk, if necessary.
- if (!FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED))
+ if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
+ }
int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal);
// Remove conflicting transactions from the mempool.;
@@ -2428,7 +2432,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
disconnectpool.removeForBlock(blockConnecting.vtx);
// Update m_chain & related variables.
m_chain.SetTip(pindexNew);
- UpdateTip(m_mempool, pindexNew, chainparams, *this);
+ UpdateTip(m_mempool, pindexNew, m_params, *this);
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, nTimePostConnect * MILLI / nBlocksTotal);
@@ -2515,7 +2519,7 @@ void CChainState::PruneBlockIndexCandidates() {
*
* @returns true unless a system error occurred
*/
-bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace)
+bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_mempool.cs);
@@ -2527,7 +2531,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
bool fBlocksDisconnected = false;
DisconnectedBlockTransactions disconnectpool;
while (m_chain.Tip() && m_chain.Tip() != pindexFork) {
- if (!DisconnectTip(state, chainparams, &disconnectpool)) {
+ if (!DisconnectTip(state, &disconnectpool)) {
// This is likely a fatal error, but keep the mempool consistent,
// just in case. Only remove from the mempool in this case.
UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false);
@@ -2560,7 +2564,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
// Connect new blocks.
for (CBlockIndex* pindexConnect : reverse_iterate(vpindexToConnect)) {
- if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) {
+ if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) {
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
@@ -2637,7 +2641,8 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
}
}
-bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) {
+bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock)
+{
// Note that while we're often called here from ProcessNewBlock, this is
// far from a guarantee. Things in the P2P/RPC will often end up calling
// us in the middle of ProcessNewBlock - do not assume pblock is set
@@ -2683,7 +2688,7 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar
bool fInvalidFound = false;
std::shared_ptr<const CBlock> nullBlockPtr;
- if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) {
+ if (!ActivateBestChainStep(state, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) {
// A system error occurred
return false;
}
@@ -2725,17 +2730,17 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar
// that the best block hash is non-null.
if (ShutdownRequested()) break;
} while (pindexNewTip != pindexMostWork);
- CheckBlockIndex(chainparams.GetConsensus());
+ CheckBlockIndex();
// Write changes periodically to disk, after relay.
- if (!FlushStateToDisk(chainparams, state, FlushStateMode::PERIODIC)) {
+ if (!FlushStateToDisk(state, FlushStateMode::PERIODIC)) {
return false;
}
return true;
}
-bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex)
+bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
{
{
LOCK(cs_main);
@@ -2761,10 +2766,10 @@ bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams&
}
}
- return ActivateBestChain(state, params, std::shared_ptr<const CBlock>());
+ return ActivateBestChain(state, std::shared_ptr<const CBlock>());
}
-bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex)
+bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
{
// Genesis block can't be invalidated
assert(pindex);
@@ -2822,7 +2827,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam
// ActivateBestChain considers blocks already in m_chain
// unconditionally valid already, so force disconnect away from it.
DisconnectedBlockTransactions disconnectpool;
- bool ret = DisconnectTip(state, chainparams, &disconnectpool);
+ bool ret = DisconnectTip(state, &disconnectpool);
// DisconnectTip will add transactions to disconnectpool.
// Adjust the mempool to be consistent with the new tip, adding
// transactions back to the mempool if disconnecting was successful,
@@ -2864,7 +2869,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam
to_mark_failed = invalid_walk_tip;
}
- CheckBlockIndex(chainparams.GetConsensus());
+ CheckBlockIndex();
{
LOCK(cs_main);
@@ -2975,7 +2980,7 @@ CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block)
}
/** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */
-void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams)
+void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos)
{
pindexNew->nTx = block.vtx.size();
pindexNew->nChainTx = 0;
@@ -2983,7 +2988,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
pindexNew->nStatus |= BLOCK_HAVE_DATA;
- if (IsWitnessEnabled(pindexNew->pprev, consensusParams)) {
+ if (IsWitnessEnabled(pindexNew->pprev, m_params.GetConsensus())) {
pindexNew->nStatus |= BLOCK_OPT_WITNESS;
}
pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS);
@@ -3400,7 +3405,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>&
CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast
bool accepted = m_blockman.AcceptBlockHeader(
header, state, chainparams, &pindex);
- ActiveChainstate().CheckBlockIndex(chainparams.GetConsensus());
+ ActiveChainstate().CheckBlockIndex();
if (!accepted) {
return false;
@@ -3419,7 +3424,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>&
}
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
-bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock)
+bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock)
{
const CBlock& block = *pblock;
@@ -3429,8 +3434,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
CBlockIndex *pindexDummy = nullptr;
CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy;
- bool accepted_header = m_blockman.AcceptBlockHeader(block, state, chainparams, &pindex);
- CheckBlockIndex(chainparams.GetConsensus());
+ bool accepted_header = m_blockman.AcceptBlockHeader(block, state, m_params, &pindex);
+ CheckBlockIndex();
if (!accepted_header)
return false;
@@ -3467,8 +3472,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
if (pindex->nChainWork < nMinimumChainWork) return true;
}
- if (!CheckBlock(block, state, chainparams.GetConsensus()) ||
- !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) {
+ if (!CheckBlock(block, state, m_params.GetConsensus()) ||
+ !ContextualCheckBlock(block, state, m_params.GetConsensus(), pindex->pprev)) {
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID;
setDirtyBlockIndex.insert(pindex);
@@ -3484,19 +3489,19 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
// Write block to history file
if (fNewBlock) *fNewBlock = true;
try {
- FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, chainparams, dbp);
+ FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, m_params, dbp);
if (blockPos.IsNull()) {
state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__));
return false;
}
- ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus());
+ ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error& e) {
return AbortNode(state, std::string("System error: ") + e.what());
}
- FlushStateToDisk(chainparams, state, FlushStateMode::NONE);
+ FlushStateToDisk(state, FlushStateMode::NONE);
- CheckBlockIndex(chainparams.GetConsensus());
+ CheckBlockIndex();
return true;
}
@@ -3522,7 +3527,7 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s
bool ret = CheckBlock(*block, state, chainparams.GetConsensus());
if (ret) {
// Store to disk
- ret = ActiveChainstate().AcceptBlock(block, state, chainparams, &pindex, force_processing, nullptr, new_block);
+ ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block);
}
if (!ret) {
GetMainSignals().BlockChecked(*block, state);
@@ -3533,8 +3538,9 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s
NotifyHeaderTip(ActiveChainstate());
BlockValidationState state; // Only used to report errors, not invalidity - ignore it
- if (!ActiveChainstate().ActivateBestChain(state, chainparams, block))
+ if (!ActiveChainstate().ActivateBestChain(state, block)) {
return error("%s: ActivateBestChain failed (%s)", __func__, state.ToString());
+ }
return true;
}
@@ -3563,8 +3569,9 @@ bool TestBlockValidity(BlockValidationState& state,
return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString());
if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev))
return error("%s: Consensus::ContextualCheckBlock: %s", __func__, state.ToString());
- if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, chainparams, true))
+ if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) {
return false;
+ }
assert(state.IsValid());
return true;
@@ -3635,9 +3642,8 @@ void BlockManager::FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nM
void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight)
{
BlockValidationState state;
- const CChainParams& chainparams = Params();
if (!active_chainstate.FlushStateToDisk(
- chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) {
+ state, FlushStateMode::NONE, nManualPruneHeight)) {
LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString());
}
}
@@ -3787,10 +3793,10 @@ void BlockManager::Unload() {
m_block_index.clear();
}
-bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams)
+bool CChainState::LoadBlockIndexDB()
{
if (!m_blockman.LoadBlockIndex(
- chainparams.GetConsensus(), *pblocktree,
+ m_params.GetConsensus(), *pblocktree,
setBlockIndexCandidates)) {
return false;
}
@@ -3850,7 +3856,7 @@ void CChainState::LoadMempool(const ArgsManager& args)
m_mempool.SetIsLoaded(!ShutdownRequested());
}
-bool CChainState::LoadChainTip(const CChainParams& chainparams)
+bool CChainState::LoadChainTip()
{
AssertLockHeld(cs_main);
const CCoinsViewCache& coins_cache = CoinsTip();
@@ -3871,10 +3877,10 @@ bool CChainState::LoadChainTip(const CChainParams& chainparams)
tip = m_chain.Tip();
LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n",
- tip->GetBlockHash().ToString(),
- m_chain.Height(),
- FormatISO8601DateTime(tip->GetBlockTime()),
- GuessVerificationProgress(chainparams.TxData(), tip));
+ tip->GetBlockHash().ToString(),
+ m_chain.Height(),
+ FormatISO8601DateTime(tip->GetBlockTime()),
+ GuessVerificationProgress(m_params.TxData(), tip));
return true;
}
@@ -3985,8 +3991,9 @@ bool CVerifyDB::VerifyDB(
CBlock block;
if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus()))
return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
- if (!chainstate.ConnectBlock(block, state, pindex, coins, chainparams))
+ if (!chainstate.ConnectBlock(block, state, pindex, coins)) {
return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
+ }
if (ShutdownRequested()) return true;
}
}
@@ -3998,11 +4005,11 @@ bool CVerifyDB::VerifyDB(
}
/** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */
-bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params)
+bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs)
{
// TODO: merge with ConnectBlock
CBlock block;
- if (!ReadBlockFromDisk(block, pindex, params.GetConsensus())) {
+ if (!ReadBlockFromDisk(block, pindex, m_params.GetConsensus())) {
return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
}
@@ -4018,7 +4025,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i
return true;
}
-bool CChainState::ReplayBlocks(const CChainParams& params)
+bool CChainState::ReplayBlocks()
{
LOCK(cs_main);
@@ -4054,7 +4061,7 @@ bool CChainState::ReplayBlocks(const CChainParams& params)
while (pindexOld != pindexFork) {
if (pindexOld->nHeight > 0) { // Never disconnect the genesis block.
CBlock block;
- if (!ReadBlockFromDisk(block, pindexOld, params.GetConsensus())) {
+ if (!ReadBlockFromDisk(block, pindexOld, m_params.GetConsensus())) {
return error("RollbackBlock(): ReadBlockFromDisk() failed at %d, hash=%s", pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
}
LogPrintf("Rolling back %s (%i)\n", pindexOld->GetBlockHash().ToString(), pindexOld->nHeight);
@@ -4076,7 +4083,7 @@ bool CChainState::ReplayBlocks(const CChainParams& params)
const CBlockIndex* pindex = pindexNew->GetAncestor(nHeight);
LogPrintf("Rolling forward %s (%i)\n", pindex->GetBlockHash().ToString(), nHeight);
uiInterface.ShowProgress(_("Replaying blocks…").translated, (int) ((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)) , false);
- if (!RollforwardBlock(pindex, cache, params)) return false;
+ if (!RollforwardBlock(pindex, cache)) return false;
}
cache.SetBestBlock(pindexNew->GetBlockHash());
@@ -4085,13 +4092,13 @@ bool CChainState::ReplayBlocks(const CChainParams& params)
return true;
}
-bool CChainState::NeedsRedownload(const CChainParams& params) const
+bool CChainState::NeedsRedownload() const
{
AssertLockHeld(cs_main);
- // At and above params.SegwitHeight, segwit consensus rules must be validated
+ // At and above m_params.SegwitHeight, segwit consensus rules must be validated
CBlockIndex* block{m_chain.Tip()};
- const int segwit_height{params.GetConsensus().SegwitHeight};
+ const int segwit_height{m_params.GetConsensus().SegwitHeight};
while (block != nullptr && block->nHeight >= segwit_height) {
if (!(block->nStatus & BLOCK_OPT_WITNESS)) {
@@ -4130,13 +4137,13 @@ void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman)
fHavePruned = false;
}
-bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams)
+bool ChainstateManager::LoadBlockIndex()
{
AssertLockHeld(cs_main);
// Load block index from databases
bool needs_init = fReindex;
if (!fReindex) {
- bool ret = ActiveChainstate().LoadBlockIndexDB(chainparams);
+ bool ret = ActiveChainstate().LoadBlockIndexDB();
if (!ret) return false;
needs_init = m_blockman.m_block_index.empty();
}
@@ -4153,7 +4160,7 @@ bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams)
return true;
}
-bool CChainState::LoadGenesisBlock(const CChainParams& chainparams)
+bool CChainState::LoadGenesisBlock()
{
LOCK(cs_main);
@@ -4161,16 +4168,16 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams)
// m_blockman.m_block_index. Note that we can't use m_chain here, since it is
// set based on the coins db, not the block index db, which is the only
// thing loaded at this point.
- if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash()))
+ if (m_blockman.m_block_index.count(m_params.GenesisBlock().GetHash()))
return true;
try {
- const CBlock& block = chainparams.GenesisBlock();
- FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, chainparams, nullptr);
+ const CBlock& block = m_params.GenesisBlock();
+ FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, m_params, nullptr);
if (blockPos.IsNull())
return error("%s: writing genesis block to disk failed", __func__);
CBlockIndex *pindex = m_blockman.AddToBlockIndex(block);
- ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus());
+ ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error& e) {
return error("%s: failed to write genesis block: %s", __func__, e.what());
}
@@ -4178,7 +4185,7 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams)
return true;
}
-void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp)
+void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp)
{
// Map of disk positions for blocks with unknown parent (only used for reindex)
static std::multimap<uint256, FlatFilePos> mapBlocksUnknownParent;
@@ -4199,11 +4206,12 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f
try {
// locate a header
unsigned char buf[CMessageHeader::MESSAGE_START_SIZE];
- blkdat.FindByte(chainparams.MessageStart()[0]);
+ blkdat.FindByte(m_params.MessageStart()[0]);
nRewind = blkdat.GetPos()+1;
blkdat >> buf;
- if (memcmp(buf, chainparams.MessageStart(), CMessageHeader::MESSAGE_START_SIZE))
+ if (memcmp(buf, m_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE)) {
continue;
+ }
// read size
blkdat >> nSize;
if (nSize < 80 || nSize > MAX_BLOCK_SERIALIZED_SIZE)
@@ -4227,7 +4235,7 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f
{
LOCK(cs_main);
// detect out of order blocks, and store them for later
- if (hash != chainparams.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) {
+ if (hash != m_params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) {
LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(),
block.hashPrevBlock.ToString());
if (dbp)
@@ -4239,21 +4247,21 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f
CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash);
if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) {
BlockValidationState state;
- if (AcceptBlock(pblock, state, chainparams, nullptr, true, dbp, nullptr)) {
+ if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) {
nLoaded++;
}
if (state.IsError()) {
break;
}
- } else if (hash != chainparams.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) {
- LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight);
+ } else if (hash != m_params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) {
+ LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight);
}
}
// Activate the genesis block so normal node progress can continue
- if (hash == chainparams.GetConsensus().hashGenesisBlock) {
+ if (hash == m_params.GetConsensus().hashGenesisBlock) {
BlockValidationState state;
- if (!ActivateBestChain(state, chainparams, nullptr)) {
+ if (!ActivateBestChain(state, nullptr)) {
break;
}
}
@@ -4270,14 +4278,12 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f
while (range.first != range.second) {
std::multimap<uint256, FlatFilePos>::iterator it = range.first;
std::shared_ptr<CBlock> pblockrecursive = std::make_shared<CBlock>();
- if (ReadBlockFromDisk(*pblockrecursive, it->second, chainparams.GetConsensus()))
- {
+ if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())) {
LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(),
head.ToString());
LOCK(cs_main);
BlockValidationState dummy;
- if (AcceptBlock(pblockrecursive, dummy, chainparams, nullptr, true, &it->second, nullptr))
- {
+ if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr)) {
nLoaded++;
queue.push_back(pblockrecursive->GetHash());
}
@@ -4297,7 +4303,7 @@ void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* f
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart);
}
-void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams)
+void CChainState::CheckBlockIndex()
{
if (!fCheckBlockIndex) {
return;
@@ -4351,7 +4357,7 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams)
// Begin: actual consistency checks.
if (pindex->pprev == nullptr) {
// Genesis block checks.
- assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock); // Genesis block's hash must match.
+ assert(pindex->GetBlockHash() == m_params.GetConsensus().hashGenesisBlock); // Genesis block's hash must match.
assert(pindex == m_chain.Genesis()); // The current active chain's genesis block must be this block.
}
if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock)
@@ -4507,16 +4513,14 @@ bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
this->ToString(), coinstip_size * (1.0 / 1024 / 1024));
BlockValidationState state;
- const CChainParams& chainparams = Params();
-
bool ret;
if (coinstip_size > old_coinstip_size) {
// Likely no need to flush if cache sizes have grown.
- ret = FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED);
+ ret = FlushStateToDisk(state, FlushStateMode::IF_NEEDED);
} else {
// Otherwise, flush state to disk and deallocate the in-memory coins map.
- ret = FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS);
+ ret = FlushStateToDisk(state, FlushStateMode::ALWAYS);
CoinsTip().ReallocateCache();
}
return ret;
@@ -4816,7 +4820,7 @@ bool ChainstateManager::ActivateSnapshot(
LOCK(::cs_main);
assert(!m_snapshot_chainstate);
m_snapshot_chainstate.swap(snapshot_chainstate);
- const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip(::Params());
+ const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip();
assert(chaintip_loaded);
m_active_chainstate = m_snapshot_chainstate.get();
diff --git a/src/validation.h b/src/validation.h
index 5720ba8071..fc702b7183 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -595,6 +595,8 @@ protected:
//! mempool that is kept in sync with the chain
CTxMemPool& m_mempool;
+ const CChainParams& m_params;
+
//! Manages the UTXO set, which is a reflection of the contents of `m_chain`.
std::unique_ptr<CoinsViews> m_coins_views;
@@ -680,7 +682,7 @@ public:
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Import blocks from an external file */
- void LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp = nullptr);
+ void LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp = nullptr);
/**
* Update the on-disk chain state.
@@ -694,8 +696,7 @@ public:
* @returns true unless a system error occurred
*/
bool FlushStateToDisk(
- const CChainParams& chainparams,
- BlockValidationState &state,
+ BlockValidationState& state,
FlushStateMode mode,
int nManualPruneHeight = 0);
@@ -723,37 +724,36 @@ public:
*/
bool ActivateBestChain(
BlockValidationState& state,
- const CChainParams& chainparams,
std::shared_ptr<const CBlock> pblock = nullptr) LOCKS_EXCLUDED(cs_main);
- bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Block (dis)connection on a given view:
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view);
bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
- CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Apply the effects of a block disconnection on the UTXO set.
- bool DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
+ bool DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
// Manual block validity manipulation:
/** Mark a block as precious and reorganize.
*
* May not be called in a validationinterface callback.
*/
- bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
+ bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
/** Mark a block as invalid. */
- bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
+ bool InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
/** Remove invalidity status from a block and its descendants. */
void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Replay blocks that aren't fully applied to the database. */
- bool ReplayBlocks(const CChainParams& params);
+ bool ReplayBlocks();
/** Whether the chain state needs to be redownloaded due to lack of witness data */
- [[nodiscard]] bool NeedsRedownload(const CChainParams& params) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ [[nodiscard]] bool NeedsRedownload() const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Ensures we have a genesis block in the block tree, possibly writing one to disk. */
- bool LoadGenesisBlock(const CChainParams& chainparams);
+ bool LoadGenesisBlock();
void PruneBlockIndexCandidates();
@@ -767,13 +767,13 @@ public:
*
* By default this only executes fully when using the Regtest chain; see: fCheckBlockIndex.
*/
- void CheckBlockIndex(const Consensus::Params& consensusParams);
+ void CheckBlockIndex();
/** Load the persisted mempool from disk */
void LoadMempool(const ArgsManager& args);
/** Update the chain tip based on database information, i.e. CoinsTip()'s best block. */
- bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool LoadChainTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Dictates whether we need to flush the cache to disk or not.
//!
@@ -789,19 +789,19 @@ public:
std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
private:
- bool ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
- bool ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
+ bool ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
+ bool ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs);
- void InvalidBlockFound(CBlockIndex *pindex, const BlockValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ void InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
- void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
- bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
- bool LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
friend ChainstateManager;
};
@@ -1002,7 +1002,7 @@ public:
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main);
//! Load the block tree and coins database from disk, initializing state if we're running with -reindex
- bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Unload block index and chain data before shutdown.
void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h
index 8eed947b7b..61df3d0015 100644
--- a/src/wallet/external_signer_scriptpubkeyman.h
+++ b/src/wallet/external_signer_scriptpubkeyman.h
@@ -15,8 +15,8 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
ExternalSignerScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor)
: DescriptorScriptPubKeyMan(storage, descriptor)
{}
- ExternalSignerScriptPubKeyMan(WalletStorage& storage, bool internal)
- : DescriptorScriptPubKeyMan(storage, internal)
+ ExternalSignerScriptPubKeyMan(WalletStorage& storage)
+ : DescriptorScriptPubKeyMan(storage)
{}
/** Provide a descriptor at setup time
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 5a832d020b..e33adf94c9 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -475,13 +475,13 @@ public:
std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override
{
return MakeHandler(m_wallet->NotifyAddressBookChanged.connect(
- [fn](CWallet*, const CTxDestination& address, const std::string& label, bool is_mine,
- const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
+ [fn](const CTxDestination& address, const std::string& label, bool is_mine,
+ const std::string& purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); }));
}
std::unique_ptr<Handler> handleTransactionChanged(TransactionChangedFn fn) override
{
return MakeHandler(m_wallet->NotifyTransactionChanged.connect(
- [fn](CWallet*, const uint256& txid, ChangeType status) { fn(txid, status); }));
+ [fn](const uint256& txid, ChangeType status) { fn(txid, status); }));
}
std::unique_ptr<Handler> handleWatchOnlyChanged(WatchOnlyChangedFn fn) override
{
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 35649ab02c..ea97b339cf 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1566,9 +1566,8 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
// Check if the wallet already contains the descriptor
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
if (existing_spk_manager) {
- LOCK(existing_spk_manager->cs_desc_man);
- if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) {
- throw JSONRPCError(RPC_INVALID_PARAMS, strprintf("range_start can only decrease; current range = [%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end));
+ if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}
}
@@ -1585,16 +1584,16 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
} else {
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
}
+ } else {
+ if (w_desc.descriptor->GetOutputType()) {
+ wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
+ }
}
result.pushKV("success", UniValue(true));
} catch (const UniValue& e) {
result.pushKV("success", UniValue(false));
result.pushKV("error", e);
- } catch (...) {
- result.pushKV("success", UniValue(false));
-
- result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
}
if (warnings.size()) result.pushKV("warnings", warnings);
return result;
@@ -1787,8 +1786,6 @@ RPCHelpMan listdescriptors()
throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
}
- EnsureWalletIsUnlocked(*wallet);
-
LOCK(wallet->cs_wallet);
UniValue descriptors(UniValue::VARR);
@@ -1802,7 +1799,7 @@ RPCHelpMan listdescriptors()
LOCK(desc_spk_man->cs_desc_man);
const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
std::string descriptor;
- if (!desc_spk_man->GetDescriptorString(descriptor, false)) {
+ if (!desc_spk_man->GetDescriptorString(descriptor)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't get normalized descriptor string.");
}
spk.pushKV("desc", descriptor);
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index bc5d771b6e..f1d5117415 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -3872,7 +3872,7 @@ RPCHelpMan getaddressinfo()
DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(pwallet->GetScriptPubKeyMan(scriptPubKey));
if (desc_spk_man) {
std::string desc_str;
- if (desc_spk_man->GetDescriptorString(desc_str, false)) {
+ if (desc_spk_man->GetDescriptorString(desc_str)) {
ret.pushKV("parent_desc", desc_str);
}
}
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 44c3912544..73433214f1 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -1613,12 +1613,10 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
return set_address;
}
-void LegacyScriptPubKeyMan::SetInternal(bool internal) {}
-
bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
- if (!CanGetAddresses(m_internal)) {
+ if (!CanGetAddresses()) {
error = "No addresses available";
return false;
}
@@ -1805,34 +1803,10 @@ bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
}
m_map_pubkeys[pubkey] = i;
}
- // Write the cache
- for (const auto& parent_xpub_pair : temp_cache.GetCachedParentExtPubKeys()) {
- CExtPubKey xpub;
- if (m_wallet_descriptor.cache.GetCachedParentExtPubKey(parent_xpub_pair.first, xpub)) {
- if (xpub != parent_xpub_pair.second) {
- throw std::runtime_error(std::string(__func__) + ": New cached parent xpub does not match already cached parent xpub");
- }
- continue;
- }
- if (!batch.WriteDescriptorParentCache(parent_xpub_pair.second, id, parent_xpub_pair.first)) {
- throw std::runtime_error(std::string(__func__) + ": writing cache item failed");
- }
- m_wallet_descriptor.cache.CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second);
- }
- for (const auto& derived_xpub_map_pair : temp_cache.GetCachedDerivedExtPubKeys()) {
- for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) {
- CExtPubKey xpub;
- if (m_wallet_descriptor.cache.GetCachedDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, xpub)) {
- if (xpub != derived_xpub_pair.second) {
- throw std::runtime_error(std::string(__func__) + ": New cached derived xpub does not match already cached derived xpub");
- }
- continue;
- }
- if (!batch.WriteDescriptorDerivedCache(derived_xpub_pair.second, id, derived_xpub_map_pair.first, derived_xpub_pair.first)) {
- throw std::runtime_error(std::string(__func__) + ": writing cache item failed");
- }
- m_wallet_descriptor.cache.CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second);
- }
+ // Merge and write the cache
+ DescriptorCache new_items = m_wallet_descriptor.cache.MergeAndDiff(temp_cache);
+ if (!batch.WriteDescriptorCacheItems(id, new_items)) {
+ throw std::runtime_error(std::string(__func__) + ": writing cache items failed");
}
m_max_cached_index++;
}
@@ -1875,6 +1849,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
AssertLockHeld(cs_desc_man);
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ // Check if provided key already exists
+ if (m_map_keys.find(pubkey.GetID()) != m_map_keys.end() ||
+ m_map_crypted_keys.find(pubkey.GetID()) != m_map_crypted_keys.end()) {
+ return true;
+ }
+
if (m_storage.HasEncryptionKeys()) {
if (m_storage.IsLocked()) {
return false;
@@ -1894,7 +1874,7 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
}
}
-bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type)
+bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal)
{
if (addr_type == OutputType::BECH32M) {
// Don't allow setting up taproot descriptors yet
@@ -1942,7 +1922,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
desc_prefix += "/0'";
}
- std::string internal_path = m_internal ? "/1" : "/0";
+ std::string internal_path = internal ? "/1" : "/0";
std::string desc_str = desc_prefix + "/0'" + internal_path + desc_suffix;
// Make the descriptor
@@ -1997,13 +1977,6 @@ int64_t DescriptorScriptPubKeyMan::GetOldestKeyPoolTime() const
return 0;
}
-size_t DescriptorScriptPubKeyMan::KeypoolCountExternalKeys() const
-{
- if (m_internal) {
- return 0;
- }
- return GetKeyPoolSize();
-}
unsigned int DescriptorScriptPubKeyMan::GetKeyPoolSize() const
{
@@ -2205,11 +2178,6 @@ uint256 DescriptorScriptPubKeyMan::GetID() const
return id;
}
-void DescriptorScriptPubKeyMan::SetInternal(bool internal)
-{
- this->m_internal = internal;
-}
-
void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
{
LOCK(cs_desc_man);
@@ -2290,15 +2258,75 @@ const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
return script_pub_keys;
}
-bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, bool priv) const
+bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out) const
{
LOCK(cs_desc_man);
- if (m_storage.IsLocked()) {
- return false;
+
+ FlatSigningProvider provider;
+ provider.keys = GetKeys();
+
+ return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache);
+}
+
+void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
+{
+ LOCK(cs_desc_man);
+ if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
+ return;
}
+ // Skip if we have the last hardened xpub cache
+ if (m_wallet_descriptor.cache.GetCachedLastHardenedExtPubKeys().size() > 0) {
+ return;
+ }
+
+ // Expand the descriptor
FlatSigningProvider provider;
provider.keys = GetKeys();
+ FlatSigningProvider out_keys;
+ std::vector<CScript> scripts_temp;
+ DescriptorCache temp_cache;
+ if (!m_wallet_descriptor.descriptor->Expand(0, provider, scripts_temp, out_keys, &temp_cache)){
+ throw std::runtime_error("Unable to expand descriptor");
+ }
+
+ // Cache the last hardened xpubs
+ DescriptorCache diff = m_wallet_descriptor.cache.MergeAndDiff(temp_cache);
+ if (!WalletBatch(m_storage.GetDatabase()).WriteDescriptorCacheItems(GetID(), diff)) {
+ throw std::runtime_error(std::string(__func__) + ": writing cache items failed");
+ }
+}
+
+void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descriptor)
+{
+ LOCK(cs_desc_man);
+ std::string error;
+ if (!CanUpdateToWalletDescriptor(descriptor, error)) {
+ throw std::runtime_error(std::string(__func__) + ": " + error);
+ }
+
+ m_map_pubkeys.clear();
+ m_map_script_pub_keys.clear();
+ m_max_cached_index = -1;
+ m_wallet_descriptor = descriptor;
+}
- return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, priv);
+bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error)
+{
+ LOCK(cs_desc_man);
+ if (!HasWalletDescriptor(descriptor)) {
+ error = "can only update matching descriptor";
+ return false;
+ }
+
+ if (descriptor.range_start > m_wallet_descriptor.range_start ||
+ descriptor.range_end < m_wallet_descriptor.range_end) {
+ // Use inclusive range for error
+ error = strprintf("new range must include current range = [%d,%d]",
+ m_wallet_descriptor.range_start,
+ m_wallet_descriptor.range_end - 1);
+ return false;
+ }
+
+ return true;
}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index b2ca354b0a..572a695662 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -216,7 +216,6 @@ public:
virtual int64_t GetOldestKeyPoolTime() const { return GetTime(); }
- virtual size_t KeypoolCountExternalKeys() const { return 0; }
virtual unsigned int GetKeyPoolSize() const { return 0; }
virtual int64_t GetTimeFirstKey() const { return 0; }
@@ -239,8 +238,6 @@ public:
virtual uint256 GetID() const { return uint256(); }
- virtual void SetInternal(bool internal) {}
-
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
template<typename... Params>
void WalletLogPrintf(std::string fmt, Params... parameters) const {
@@ -386,7 +383,7 @@ public:
void RewriteDB() override;
int64_t GetOldestKeyPoolTime() const override;
- size_t KeypoolCountExternalKeys() const override;
+ size_t KeypoolCountExternalKeys() const;
unsigned int GetKeyPoolSize() const override;
int64_t GetTimeFirstKey() const override;
@@ -405,8 +402,6 @@ public:
uint256 GetID() const override;
- void SetInternal(bool internal) override;
-
// Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
@@ -533,8 +528,6 @@ private:
PubKeyMap m_map_pubkeys GUARDED_BY(cs_desc_man);
int32_t m_max_cached_index = -1;
- bool m_internal = false;
-
KeyMap m_map_keys GUARDED_BY(cs_desc_man);
CryptedKeyMap m_map_crypted_keys GUARDED_BY(cs_desc_man);
@@ -560,9 +553,8 @@ public:
: ScriptPubKeyMan(storage),
m_wallet_descriptor(descriptor)
{}
- DescriptorScriptPubKeyMan(WalletStorage& storage, bool internal)
- : ScriptPubKeyMan(storage),
- m_internal(internal)
+ DescriptorScriptPubKeyMan(WalletStorage& storage)
+ : ScriptPubKeyMan(storage)
{}
mutable RecursiveMutex cs_desc_man;
@@ -587,7 +579,7 @@ public:
bool IsHDEnabled() const override;
//! Setup descriptors based on the given CExtkey
- bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type);
+ bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type, bool internal);
/** Provide a descriptor at setup time
* Returns false if already setup or setup fails, true if setup is successful
@@ -597,7 +589,6 @@ public:
bool HavePrivateKeys() const override;
int64_t GetOldestKeyPoolTime() const override;
- size_t KeypoolCountExternalKeys() const override;
unsigned int GetKeyPoolSize() const override;
int64_t GetTimeFirstKey() const override;
@@ -616,21 +607,23 @@ public:
uint256 GetID() const override;
- void SetInternal(bool internal) override;
-
void SetCache(const DescriptorCache& cache);
bool AddKey(const CKeyID& key_id, const CKey& key);
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);
bool HasWalletDescriptor(const WalletDescriptor& desc) const;
+ void UpdateWalletDescriptor(WalletDescriptor& descriptor);
+ bool CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error);
void AddDescriptorKey(const CKey& key, const CPubKey &pubkey);
void WriteDescriptor();
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
const std::vector<CScript> GetScriptPubKeys() const;
- bool GetDescriptorString(std::string& out, bool priv) const;
+ bool GetDescriptorString(std::string& out) const;
+
+ void UpgradeDescriptorCache();
};
#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 42be172b81..27565aefc9 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -374,6 +374,19 @@ void CWallet::UpgradeKeyMetadata()
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
+void CWallet::UpgradeDescriptorCache()
+{
+ if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) || IsLocked() || IsWalletFlagSet(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED)) {
+ return;
+ }
+
+ for (ScriptPubKeyMan* spkm : GetAllScriptPubKeyMans()) {
+ DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(spkm);
+ desc_spkm->UpgradeDescriptorCache();
+ }
+ SetWalletFlag(WALLET_FLAG_LAST_HARDENED_XPUB_CACHED);
+}
+
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys)
{
CCrypter crypter;
@@ -390,6 +403,8 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key
if (Unlock(_vMasterKey, accept_no_keys)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
+ // Now that we've unlocked, upgrade the descriptor cache
+ UpgradeDescriptorCache();
return true;
}
}
@@ -801,7 +816,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
success = false;
}
- NotifyTransactionChanged(this, originalHash, CT_UPDATED);
+ NotifyTransactionChanged(originalHash, CT_UPDATED);
return success;
}
@@ -930,7 +945,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
wtx.MarkDirty();
// Notify UI of new or updated transaction
- NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
+ NotifyTransactionChanged(hash, fInsertedNew ? CT_NEW : CT_UPDATED);
#if HAVE_SYSTEM
// notify an external script when a wallet transaction comes in or is updated
@@ -1104,7 +1119,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
wtx.setAbandoned();
wtx.MarkDirty();
batch.WriteTx(wtx);
- NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED);
+ NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
@@ -1944,7 +1959,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
for (const CTxIn& txin : tx->vin) {
CWalletTx &coin = mapWallet.at(txin.prevout.hash);
coin.MarkDirty();
- NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
+ NotifyTransactionChanged(coin.GetHash(), CT_UPDATED);
}
// Get the inserted-CWalletTx from mapWallet so that the
@@ -1999,7 +2014,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
for (const auto& txin : it->second.tx->vin)
mapTxSpends.erase(txin.prevout);
mapWallet.erase(it);
- NotifyTransactionChanged(this, hash, CT_DELETED);
+ NotifyTransactionChanged(hash, CT_DELETED);
}
if (nZapSelectTxRet == DBErrors::NEED_REWRITE)
@@ -2033,8 +2048,8 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add
m_address_book[address].purpose = strPurpose;
is_mine = IsMine(address) != ISMINE_NO;
}
- NotifyAddressBookChanged(this, address, strName, is_mine,
- strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
+ NotifyAddressBookChanged(address, strName, is_mine,
+ strPurpose, (fUpdated ? CT_UPDATED : CT_NEW));
if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose))
return false;
return batch.WriteName(EncodeDestination(address), strName);
@@ -2069,7 +2084,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
is_mine = IsMine(address) != ISMINE_NO;
}
- NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED);
+ NotifyAddressBookChanged(address, "", is_mine, "", CT_DELETED);
batch.ErasePurpose(EncodeDestination(address));
return batch.EraseName(EncodeDestination(address));
@@ -2079,9 +2094,14 @@ size_t CWallet::KeypoolCountExternalKeys() const
{
AssertLockHeld(cs_wallet);
+ auto legacy_spk_man = GetLegacyScriptPubKeyMan();
+ if (legacy_spk_man) {
+ return legacy_spk_man->KeypoolCountExternalKeys();
+ }
+
unsigned int count = 0;
- for (auto spk_man : GetActiveScriptPubKeyMans()) {
- count += spk_man->KeypoolCountExternalKeys();
+ for (auto spk_man : m_external_spk_managers) {
+ count += spk_man.second->GetKeyPoolSize();
}
return count;
@@ -3096,7 +3116,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
// TODO: Setup taproot (bech32m) descriptors by default
continue;
}
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this));
if (IsCrypted()) {
if (IsLocked()) {
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
@@ -3105,7 +3125,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
}
}
- spk_manager->SetupDescriptorGeneration(master_key, t);
+ spk_manager->SetupDescriptorGeneration(master_key, t, internal);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddActiveScriptPubKeyMan(id, t, internal);
@@ -3131,7 +3151,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
continue;
}
OutputType t = *desc->GetOutputType();
- auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, internal));
+ auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this));
spk_manager->SetupDescriptor(std::move(desc));
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
@@ -3152,12 +3172,37 @@ void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool interna
void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal)
{
+ // Activating ScriptPubKeyManager for a given output and change type is incompatible with legacy wallets.
+ // Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types.
+ Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
- spk_man->SetInternal(internal);
spk_mans[type] = spk_man;
+ if (spk_mans_other[type] == spk_man) {
+ spk_mans_other.erase(type);
+ }
+
+ NotifyCanGetAddressesChanged();
+}
+
+void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal)
+{
+ auto spk_man = GetScriptPubKeyMan(type, internal);
+ if (spk_man != nullptr && spk_man->GetID() == id) {
+ WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
+ WalletBatch batch(GetDatabase());
+ if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) {
+ throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed");
+ }
+
+ auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ spk_mans.erase(type);
+ }
+
NotifyCanGetAddressesChanged();
}
@@ -3191,44 +3236,26 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
LOCK(cs_wallet);
- auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
-
- // If we already have this descriptor, remove it from the maps but add the existing cache to desc
- auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
- if (old_spk_man) {
+ auto spk_man = GetDescriptorScriptPubKeyMan(desc);
+ if (spk_man) {
WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
+ spk_man->UpdateWalletDescriptor(desc);
+ } else {
+ auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
+ spk_man = new_spk_man.get();
- {
- LOCK(old_spk_man->cs_desc_man);
- new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
- }
-
- // Remove from maps of active spkMans
- auto old_spk_man_id = old_spk_man->GetID();
- for (bool internal : {false, true}) {
- for (OutputType t : OUTPUT_TYPES) {
- auto active_spk_man = GetScriptPubKeyMan(t, internal);
- if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
- if (internal) {
- m_internal_spk_managers.erase(t);
- } else {
- m_external_spk_managers.erase(t);
- }
- break;
- }
- }
- }
- m_spk_managers.erase(old_spk_man_id);
+ // Save the descriptor to memory
+ m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
}
// Add the private keys to the descriptor
for (const auto& entry : signing_provider.keys) {
const CKey& key = entry.second;
- new_spk_man->AddDescriptorKey(key, key.GetPubKey());
+ spk_man->AddDescriptorKey(key, key.GetPubKey());
}
// Top up key pool, the manager will generate new scriptPubKeys internally
- if (!new_spk_man->TopUp()) {
+ if (!spk_man->TopUp()) {
WalletLogPrintf("Could not top up scriptPubKeys\n");
return nullptr;
}
@@ -3236,7 +3263,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
// Apply the label if necessary
// Note: we disable labels for ranged descriptors
if (!desc.descriptor->IsRange()) {
- auto script_pub_keys = new_spk_man->GetScriptPubKeys();
+ auto script_pub_keys = spk_man->GetScriptPubKeys();
if (script_pub_keys.empty()) {
WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
return nullptr;
@@ -3248,12 +3275,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}
}
- // Save the descriptor to memory
- auto ret = new_spk_man.get();
- m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
-
// Save the descriptor to DB
- ret->WriteDescriptor();
+ spk_man->WriteDescriptor();
- return ret;
+ return spk_man;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index b63938c5f1..3997751f52 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -117,6 +117,7 @@ static constexpr uint64_t KNOWN_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE
| WALLET_FLAG_BLANK_WALLET
| WALLET_FLAG_KEY_ORIGIN_METADATA
+ | WALLET_FLAG_LAST_HARDENED_XPUB_CACHED
| WALLET_FLAG_DISABLE_PRIVATE_KEYS
| WALLET_FLAG_DESCRIPTORS
| WALLET_FLAG_EXTERNAL_SIGNER;
@@ -128,6 +129,7 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
{"avoid_reuse", WALLET_FLAG_AVOID_REUSE},
{"blank", WALLET_FLAG_BLANK_WALLET},
{"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA},
+ {"last_hardened_xpub_cached", WALLET_FLAG_LAST_HARDENED_XPUB_CACHED},
{"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS},
{"descriptor_wallet", WALLET_FLAG_DESCRIPTORS},
{"external_signer", WALLET_FLAG_EXTERNAL_SIGNER}
@@ -476,6 +478,9 @@ public:
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ //! Upgrade DescriptorCaches
+ void UpgradeDescriptorCache() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; }
//! Adds a destination data tuple to the store, without saving it to disk
@@ -724,19 +729,18 @@ public:
/**
* Address book entry changed.
- * @note called with lock cs_wallet held.
+ * @note called without lock cs_wallet held.
*/
- boost::signals2::signal<void (CWallet *wallet, const CTxDestination
- &address, const std::string &label, bool isMine,
- const std::string &purpose,
- ChangeType status)> NotifyAddressBookChanged;
+ boost::signals2::signal<void(const CTxDestination& address,
+ const std::string& label, bool isMine,
+ const std::string& purpose, ChangeType status)>
+ NotifyAddressBookChanged;
/**
* Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held.
*/
- boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx,
- ChangeType status)> NotifyTransactionChanged;
+ boost::signals2::signal<void(const uint256& hashTx, ChangeType status)> NotifyTransactionChanged;
/** Show progress e.g. for rescan */
boost::signals2::signal<void (const std::string &title, int nProgress)> ShowProgress;
@@ -895,6 +899,12 @@ public:
//! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
+ //! Remove specified ScriptPubKeyMan from set of active SPK managers. Writes the change to the wallet file.
+ //! @param[in] id The unique id for the ScriptPubKeyMan
+ //! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
+ //! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
+ void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal);
+
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 24d5351945..1e5d8dfa3a 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -52,6 +52,7 @@ const std::string TX{"tx"};
const std::string VERSION{"version"};
const std::string WALLETDESCRIPTOR{"walletdescriptor"};
const std::string WALLETDESCRIPTORCACHE{"walletdescriptorcache"};
+const std::string WALLETDESCRIPTORLHCACHE{"walletdescriptorlhcache"};
const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"};
const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"};
const std::string WATCHMETA{"watchmeta"};
@@ -209,6 +210,12 @@ bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bo
return WriteIC(make_pair(key, type), id);
}
+bool WalletBatch::EraseActiveScriptPubKeyMan(uint8_t type, bool internal)
+{
+ const std::string key{internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK};
+ return EraseIC(make_pair(key, type));
+}
+
bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey)
{
// hash pubkey/privkey to accelerate wallet load
@@ -248,6 +255,35 @@ bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey& xpub, const uint2
return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), key_exp_index), ser_xpub);
}
+bool WalletBatch::WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index)
+{
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ xpub.Encode(ser_xpub.data());
+ return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORLHCACHE, desc_id), key_exp_index), ser_xpub);
+}
+
+bool WalletBatch::WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache)
+{
+ for (const auto& parent_xpub_pair : cache.GetCachedParentExtPubKeys()) {
+ if (!WriteDescriptorParentCache(parent_xpub_pair.second, desc_id, parent_xpub_pair.first)) {
+ return false;
+ }
+ }
+ for (const auto& derived_xpub_map_pair : cache.GetCachedDerivedExtPubKeys()) {
+ for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) {
+ if (!WriteDescriptorDerivedCache(derived_xpub_pair.second, desc_id, derived_xpub_map_pair.first, derived_xpub_pair.first)) {
+ return false;
+ }
+ }
+ }
+ for (const auto& lh_xpub_pair : cache.GetCachedLastHardenedExtPubKeys()) {
+ if (!WriteDescriptorLastHardenedCache(lh_xpub_pair.second, desc_id, lh_xpub_pair.first)) {
+ return false;
+ }
+ }
+ return true;
+}
+
class CWalletScanState {
public:
unsigned int nKeys{0};
@@ -602,6 +638,17 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} else {
wss.m_descriptor_caches[desc_id].CacheDerivedExtPubKey(key_exp_index, der_index, xpub);
}
+ } else if (strType == DBKeys::WALLETDESCRIPTORLHCACHE) {
+ uint256 desc_id;
+ uint32_t key_exp_index;
+ ssKey >> desc_id;
+ ssKey >> key_exp_index;
+
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ ssValue >> ser_xpub;
+ CExtPubKey xpub;
+ xpub.Decode(ser_xpub.data());
+ wss.m_descriptor_caches[desc_id].CacheLastHardenedExtPubKey(key_exp_index, xpub);
} else if (strType == DBKeys::WALLETDESCRIPTORKEY) {
uint256 desc_id;
CPubKey pubkey;
@@ -843,6 +890,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
result = DBErrors::CORRUPT;
}
+ // Upgrade all of the descriptor caches to cache the last hardened xpub
+ // This operation is not atomic, but if it fails, only new entries are added so it is backwards compatible
+ try {
+ pwallet->UpgradeDescriptorCache();
+ } catch (...) {
+ result = DBErrors::CORRUPT;
+ }
+
// Set the inactive chain
if (wss.m_hd_chains.size() > 0) {
LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index e7b2d7d780..9b775eb481 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -246,6 +246,8 @@ public:
bool WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor);
bool WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index);
bool WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
+ bool WriteDescriptorLastHardenedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
+ bool WriteDescriptorCacheItems(const uint256& desc_id, const DescriptorCache& cache);
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
@@ -253,6 +255,7 @@ public:
bool EraseDestData(const std::string &address, const std::string &key);
bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal);
+ bool EraseActiveScriptPubKeyMan(uint8_t type, bool internal);
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx);
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 0713f768c1..c75e1759bc 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -43,6 +43,9 @@ enum WalletFlags : uint64_t {
// Indicates that the metadata has already been upgraded to contain key origins
WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
+ // Indicates that the descriptor cache has been upgraded to cache last hardened xpubs
+ WALLET_FLAG_LAST_HARDENED_XPUB_CACHED = (1ULL << 2),
+
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
index a2da16e5a3..262175c789 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -79,7 +79,6 @@ class ImportDescriptorsTest(BitcoinTestFramework):
# RPC importdescriptors -----------------------------------------------
# # Test import fails if no descriptor present
- key = get_generate_key()
self.log.info("Import should fail if a descriptor is not provided")
self.test_importdesc({"timestamp": "now"},
success=False,
@@ -89,10 +88,10 @@ class ImportDescriptorsTest(BitcoinTestFramework):
# # Test importing of a P2PKH descriptor
key = get_generate_key()
self.log.info("Should import a p2pkh descriptor")
- self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
- "timestamp": "now",
- "label": "Descriptor import test"},
- success=True)
+ import_request = {"desc": descsum_create("pkh(" + key.pubkey + ")"),
+ "timestamp": "now",
+ "label": "Descriptor import test"}
+ self.test_importdesc(import_request, success=True)
test_address(w1,
key.p2pkh_addr,
solvable=True,
@@ -100,11 +99,15 @@ class ImportDescriptorsTest(BitcoinTestFramework):
labels=["Descriptor import test"])
assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
+ self.log.info("Test can import same descriptor with public key twice")
+ self.test_importdesc(import_request, success=True)
+
+ self.log.info("Test can update descriptor label")
+ self.test_importdesc({**import_request, "label": "Updated label"}, success=True)
+ test_address(w1, key.p2pkh_addr, solvable=True, ismine=True, labels=["Updated label"])
+
self.log.info("Internal addresses cannot have labels")
- self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
- "timestamp": "now",
- "internal": True,
- "label": "Descriptor import test"},
+ self.test_importdesc({**import_request, "internal": True},
success=False,
error_code=-8,
error_message="Internal addresses should not have a label")
@@ -252,6 +255,39 @@ class ImportDescriptorsTest(BitcoinTestFramework):
self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
success=False, error_code=-8, error_message='Range is too large')
+ self.log.info("Verify we can only extend descriptor's range")
+ range_request = {"desc": descsum_create(desc), "timestamp": "now", "range": [5, 10], 'active': True}
+ self.test_importdesc(range_request, wallet=wpriv, success=True)
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 6)
+ self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=True)
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 11)
+ self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True)
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
+ # Can keep range the same
+ self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True)
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
+
+ self.test_importdesc({**range_request, "range": [5, 10]}, wallet=wpriv, success=False,
+ error_code=-8, error_message='new range must include current range = [0,20]')
+ self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=False,
+ error_code=-8, error_message='new range must include current range = [0,20]')
+ self.test_importdesc({**range_request, "range": [5, 20]}, wallet=wpriv, success=False,
+ error_code=-8, error_message='new range must include current range = [0,20]')
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
+
+ self.log.info("Check we can change descriptor internal flag")
+ self.test_importdesc({**range_request, "range": [0, 20], "internal": True}, wallet=wpriv, success=True)
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
+ assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getnewaddress, '', 'p2sh-segwit')
+ assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 21)
+ wpriv.getrawchangeaddress('p2sh-segwit')
+
+ self.test_importdesc({**range_request, "range": [0, 20], "internal": False}, wallet=wpriv, success=True)
+ assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
+ wpriv.getnewaddress('', 'p2sh-segwit')
+ assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 0)
+ assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getrawchangeaddress, 'p2sh-segwit')
+
# Make sure ranged imports import keys in order
w1 = self.nodes[1].get_wallet_rpc('w1')
self.log.info('Key ranges should be imported in order')
@@ -303,9 +339,21 @@ class ImportDescriptorsTest(BitcoinTestFramework):
w1.keypoolrefill()
assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
+ self.log.info("Check we can change next_index")
+ # go back and forth with next_index
+ for i in [4, 0, 2, 1, 3]:
+ self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
+ 'active': True,
+ 'range': [0, 9],
+ 'next_index': i,
+ 'timestamp': 'now'
+ },
+ success=True)
+ assert_equal(w1.getnewaddress('', 'bech32'), addresses[i])
+
# Check active=False default
self.log.info('Check imported descriptors are not active by default')
- self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
+ self.test_importdesc({'desc': descsum_create('pkh([12345678/1h]' + xpub + '/*)'),
'range' : [0, 2],
'timestamp': 'now',
'internal': True
@@ -313,6 +361,32 @@ class ImportDescriptorsTest(BitcoinTestFramework):
success=True)
assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
+ self.log.info('Check can activate inactive descriptor')
+ self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'),
+ 'range': [0, 5],
+ 'active': True,
+ 'timestamp': 'now',
+ 'internal': True
+ },
+ success=True)
+ address = w1.getrawchangeaddress('legacy')
+ assert_equal(address, "mpA2Wh9dvZT7yfELq1UnrUmAoc5qCkMetg")
+
+ self.log.info('Check can deactivate active descriptor')
+ self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'),
+ 'range': [0, 5],
+ 'active': False,
+ 'timestamp': 'now',
+ 'internal': True
+ },
+ success=True)
+ assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
+
+ self.log.info('Verify activation state is persistent')
+ w1.unloadwallet()
+ self.nodes[1].loadwallet('w1')
+ assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
+
# # Test importing a descriptor containing a WIF private key
wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg"
@@ -322,6 +396,10 @@ class ImportDescriptorsTest(BitcoinTestFramework):
"timestamp": "now"},
success=True,
wallet=wpriv)
+
+ self.log.info('Test can import same descriptor with private key twice')
+ self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now"}, success=True, wallet=wpriv)
+
test_address(wpriv,
address,
solvable=True,
@@ -339,14 +417,25 @@ class ImportDescriptorsTest(BitcoinTestFramework):
wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv")
assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0)
- self.test_importdesc({"desc":"wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#m2sr93jn",
+ xprv1 = 'tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52'
+ acc_xpub1 = 'tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8' # /84'/0'/0'
+ chg_xpub1 = 'tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf' # /84'/1'/0'
+ xprv2 = 'tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq'
+ acc_xprv2 = 'tprv8gVCsmRAxVSxyUpsL13Y7ZEWBFPWbgS5E2MmFVNGuANrknvmmn2vWnmHvU8AwEFYzR2ji6EeZLSCLVacsYkvor3Pcb5JY5FGcevqTwYvdYx'
+ acc_xpub2 = 'tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH'
+ chg_xpub2 = 'tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh'
+ xprv3 = 'tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1'
+ acc_xpub3 = 'tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E'
+ chg_xpub3 = 'tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb'
+
+ self.test_importdesc({"desc":"wsh(multi(2," + xprv1 + "/84h/0h/0h/*," + xprv2 + "/84h/0h/0h/*," + xprv3 + "/84h/0h/0h/*))#m2sr93jn",
"active": True,
"range": 1000,
"next_index": 0,
"timestamp": "now"},
success=True,
wallet=wmulti_priv)
- self.test_importdesc({"desc":"wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#q3sztvx5",
+ self.test_importdesc({"desc":"wsh(multi(2," + xprv1 + "/84h/1h/0h/*," + xprv2 + "/84h/1h/0h/*," + xprv3 + "/84h/1h/0h/*))#q3sztvx5",
"active": True,
"internal" : True,
"range": 1000,
@@ -374,14 +463,14 @@ class ImportDescriptorsTest(BitcoinTestFramework):
wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub")
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0)
- self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#tsry0s5e",
+ self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/0h/0h]" + acc_xpub1 + "/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 +"/*))#tsry0s5e",
"active": True,
"range": 1000,
"next_index": 0,
"timestamp": "now"},
success=True,
wallet=wmulti_pub)
- self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#c08a2rzv",
+ self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/1h/0h]" + chg_xpub1 + "/*,[59b09cd6/84h/1h/0h]" + chg_xpub2 + "/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))#c08a2rzv",
"active": True,
"internal" : True,
"range": 1000,
@@ -396,8 +485,15 @@ class ImportDescriptorsTest(BitcoinTestFramework):
change_addr = wmulti_pub.getrawchangeaddress('bech32')
assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e')
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
+
+ # generate some utxos for next tests
txid = w0.sendtoaddress(addr, 10)
vout = find_vout_for_address(self.nodes[0], txid, addr)
+
+ addr2 = wmulti_pub.getnewaddress('', 'bech32')
+ txid2 = w0.sendtoaddress(addr2, 10)
+ vout2 = find_vout_for_address(self.nodes[0], txid2, addr2)
+
self.nodes[0].generate(6)
self.sync_all()
assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
@@ -411,14 +507,14 @@ class ImportDescriptorsTest(BitcoinTestFramework):
wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
res = wmulti_priv1.importdescriptors([
{
- "desc": descsum_create("wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"),
+ "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
"active": True,
"range": 1000,
"next_index": 0,
"timestamp": "now"
},
{
- "desc": descsum_create("wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"),
+ "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/1h/0h/*,[59b09cd6/84h/1h/0h]" + chg_xpub2 + "/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))"),
"active": True,
"internal" : True,
"range": 1000,
@@ -434,14 +530,14 @@ class ImportDescriptorsTest(BitcoinTestFramework):
wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2')
res = wmulti_priv2.importdescriptors([
{
- "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"),
+ "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/0h/0h]" + acc_xpub1 + "/*," + xprv2 + "/84h/0h/0h/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
"active": True,
"range": 1000,
"next_index": 0,
"timestamp": "now"
},
{
- "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"),
+ "desc": descsum_create("wsh(multi(2,[7b2d0242/84h/1h/0h]" + chg_xpub1 + "/*," + xprv2 + "/84h/1h/0h/*,[e81a0532/84h/1h/0h]" + chg_xpub3 + "/*))"),
"active": True,
"internal" : True,
"range": 1000,
@@ -531,6 +627,33 @@ class ImportDescriptorsTest(BitcoinTestFramework):
)
+ self.log.info("Amending multisig with new private keys")
+ self.nodes[1].createwallet(wallet_name="wmulti_priv3", descriptors=True)
+ wmulti_priv3 = self.nodes[1].get_wallet_rpc("wmulti_priv3")
+ res = wmulti_priv3.importdescriptors([
+ {
+ "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xpub2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
+ "active": True,
+ "range": 1000,
+ "next_index": 0,
+ "timestamp": "now"
+ }])
+ assert_equal(res[0]['success'], True)
+ res = wmulti_priv3.importdescriptors([
+ {
+ "desc": descsum_create("wsh(multi(2," + xprv1 + "/84h/0h/0h/*,[59b09cd6/84h/0h/0h]" + acc_xprv2 + "/*,[e81a0532/84h/0h/0h]" + acc_xpub3 + "/*))"),
+ "active": True,
+ "range": 1000,
+ "next_index": 0,
+ "timestamp": "now"
+ }])
+ assert_equal(res[0]['success'], True)
+
+ rawtx = self.nodes[1].createrawtransaction([{'txid': txid2, 'vout': vout2}], {w0.getnewaddress(): 9.999})
+ tx = wmulti_priv3.signrawtransactionwithwallet(rawtx)
+ assert_equal(tx['complete'], True)
+ self.nodes[1].sendrawtransaction(tx['hex'])
+
self.log.info("Combo descriptors cannot be active")
self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
"active": True,
diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py
index c1444164ce..bf53c99855 100755
--- a/test/functional/wallet_listdescriptors.py
+++ b/test/functional/wallet_listdescriptors.py
@@ -72,6 +72,10 @@ class ListDescriptorsTest(BitcoinTestFramework):
}
assert_equal(expected, wallet.listdescriptors())
+ self.log.info("Test listdescriptors with encrypted wallet")
+ wallet.encryptwallet("pass")
+ assert_equal(expected, wallet.listdescriptors())
+
self.log.info('Test non-active non-range combo descriptor')
node.createwallet(wallet_name='w4', blank=True, descriptors=True)
wallet = node.get_wallet_rpc('w4')