aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.bench.include31
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/bench/logging.cpp48
-rw-r--r--src/bench/rpc_mempool.cpp2
-rw-r--r--src/chainparams.cpp2
-rw-r--r--src/node/miner.cpp17
-rw-r--r--src/node/miner.h1
-rw-r--r--src/qt/bitcoin.cpp2
-rw-r--r--src/qt/paymentserver.cpp23
-rw-r--r--src/qt/sendcoinsdialog.cpp205
-rw-r--r--src/qt/sendcoinsdialog.h13
-rw-r--r--src/rest.cpp1
-rw-r--r--src/rpc/blockchain.cpp461
-rw-r--r--src/rpc/blockchain.h7
-rw-r--r--src/rpc/mempool.cpp476
-rw-r--r--src/rpc/mempool.h17
-rw-r--r--src/rpc/register.h3
-rw-r--r--src/script/standard.cpp8
-rw-r--r--src/test/fs_tests.cpp6
-rw-r--r--src/test/miner_tests.cpp32
-rw-r--r--src/test/script_segwit_tests.cpp164
-rw-r--r--src/test/util_tests.cpp49
-rw-r--r--src/util/readwritefile.cpp4
-rw-r--r--src/validation.cpp66
-rw-r--r--src/validation.h15
-rw-r--r--src/wallet/coinselection.cpp2
-rw-r--r--src/wallet/rpc/spend.cpp6
-rw-r--r--src/wallet/rpc/wallet.cpp4
-rw-r--r--src/wallet/spend.cpp38
-rw-r--r--src/wallet/sqlite.cpp67
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp1
-rw-r--r--src/wallet/wallet.cpp5
33 files changed, 1036 insertions, 743 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8f4cbee62f..e940736b71 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -209,6 +209,7 @@ BITCOIN_CORE_H = \
reverse_iterator.h \
rpc/blockchain.h \
rpc/client.h \
+ rpc/mempool.h \
rpc/mining.h \
rpc/protocol.h \
rpc/rawtransaction_util.h \
@@ -370,6 +371,7 @@ libbitcoin_node_a_SOURCES = \
pow.cpp \
rest.cpp \
rpc/blockchain.cpp \
+ rpc/mempool.cpp \
rpc/mining.cpp \
rpc/misc.cpp \
rpc/net.cpp \
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
index 0bcce6ebe1..5dae4374e3 100644
--- a/src/Makefile.bench.include
+++ b/src/Makefile.bench.include
@@ -13,38 +13,39 @@ GENERATED_BENCH_FILES = $(RAW_BENCH_FILES:.raw=.raw.h)
bench_bench_bitcoin_SOURCES = \
$(RAW_BENCH_FILES) \
bench/addrman.cpp \
- bench/bench_bitcoin.cpp \
+ bench/base58.cpp \
+ bench/bech32.cpp \
bench/bench.cpp \
bench/bench.h \
+ bench/bench_bitcoin.cpp \
bench/block_assemble.cpp \
+ bench/ccoins_caching.cpp \
+ bench/chacha20.cpp \
+ bench/chacha_poly_aead.cpp \
bench/checkblock.cpp \
bench/checkqueue.cpp \
- bench/data.h \
+ bench/crypto_hash.cpp \
bench/data.cpp \
+ bench/data.h \
bench/duplicate_inputs.cpp \
bench/examples.cpp \
- bench/rollingbloom.cpp \
- bench/chacha20.cpp \
- bench/chacha_poly_aead.cpp \
- bench/crypto_hash.cpp \
- bench/ccoins_caching.cpp \
bench/gcs_filter.cpp \
bench/hashpadding.cpp \
- bench/merkle_root.cpp \
+ bench/lockedpool.cpp \
+ bench/logging.cpp \
bench/mempool_eviction.cpp \
bench/mempool_stress.cpp \
- bench/nanobench.h \
+ bench/merkle_root.cpp \
bench/nanobench.cpp \
+ bench/nanobench.h \
bench/peer_eviction.cpp \
+ bench/poly1305.cpp \
+ bench/prevector.cpp \
+ bench/rollingbloom.cpp \
bench/rpc_blockchain.cpp \
bench/rpc_mempool.cpp \
bench/util_time.cpp \
- bench/verify_script.cpp \
- bench/base58.cpp \
- bench/bech32.cpp \
- bench/lockedpool.cpp \
- bench/poly1305.cpp \
- bench/prevector.cpp
+ bench/verify_script.cpp
nodist_bench_bench_bitcoin_SOURCES = $(GENERATED_BENCH_FILES)
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index e2e08468a7..a7505b9bcf 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -121,6 +121,7 @@ BITCOIN_TESTS =\
test/scheduler_tests.cpp \
test/script_p2sh_tests.cpp \
test/script_parse_tests.cpp \
+ test/script_segwit_tests.cpp \
test/script_standard_tests.cpp \
test/script_tests.cpp \
test/scriptnum10.h \
diff --git a/src/bench/logging.cpp b/src/bench/logging.cpp
new file mode 100644
index 0000000000..d28777df9e
--- /dev/null
+++ b/src/bench/logging.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <bench/bench.h>
+#include <logging.h>
+#include <test/util/setup_common.h>
+
+
+static void Logging(benchmark::Bench& bench, const std::vector<const char*>& extra_args, const std::function<void()>& log)
+{
+ TestingSetup test_setup{
+ CBaseChainParams::REGTEST,
+ extra_args,
+ };
+
+ bench.run([&] { log(); });
+}
+
+static void LoggingYoThreadNames(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=1"}, [] { LogPrintf("%s\n", "test"); });
+}
+static void LoggingNoThreadNames(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=0"}, [] { LogPrintf("%s\n", "test"); });
+}
+static void LoggingYoCategory(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=0", "-debug=net"}, [] { LogPrint(BCLog::NET, "%s\n", "test"); });
+}
+static void LoggingNoCategory(benchmark::Bench& bench)
+{
+ Logging(bench, {"-logthreadnames=0", "-debug=0"}, [] { LogPrint(BCLog::NET, "%s\n", "test"); });
+}
+static void LoggingNoFile(benchmark::Bench& bench)
+{
+ Logging(bench, {"-nodebuglogfile", "-debug=1"}, [] {
+ LogPrintf("%s\n", "test");
+ LogPrint(BCLog::NET, "%s\n", "test");
+ });
+}
+
+BENCHMARK(LoggingYoThreadNames);
+BENCHMARK(LoggingNoThreadNames);
+BENCHMARK(LoggingYoCategory);
+BENCHMARK(LoggingNoCategory);
+BENCHMARK(LoggingNoFile);
diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp
index 12dcff5844..64e4c46899 100644
--- a/src/bench/rpc_mempool.cpp
+++ b/src/bench/rpc_mempool.cpp
@@ -3,7 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bench/bench.h>
-#include <rpc/blockchain.h>
+#include <rpc/mempool.h>
#include <txmempool.h>
#include <univalue.h>
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index d3ae6f4cb2..93510e925f 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -397,7 +397,7 @@ public:
consensus.BIP65Height = 1; // Always active unless overridden
consensus.BIP66Height = 1; // Always active unless overridden
consensus.CSVHeight = 1; // Always active unless overridden
- consensus.SegwitHeight = 1; // Always active unless overridden
+ consensus.SegwitHeight = 0; // Always active unless overridden
consensus.MinBIP9WarningHeight = 0;
consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index fbfa88c170..917df91933 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -97,7 +97,6 @@ void BlockAssembler::resetBlock()
// Reserve space for coinbase tx
nBlockWeight = 4000;
nBlockSigOpsCost = 400;
- fIncludeWitness = false;
// These counters do not include coinbase tx
nBlockTx = 0;
@@ -137,17 +136,6 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
pblock->nTime = GetAdjustedTime();
m_lock_time_cutoff = pindexPrev->GetMedianTimePast();
- // Decide whether to include witness transactions
- // This is only needed in case the witness softfork activation is reverted
- // (which would require a very deep reorganization).
- // Note that the mempool would accept transactions with witness data before
- // the deployment is active, but we would only ever mine blocks after activation
- // unless there is a massive block reorganization with the witness softfork
- // not activated.
- // TODO: replace this with a call to main to assess validity of a mempool
- // transaction (which in most cases can be a no-op).
- fIncludeWitness = DeploymentActiveAfter(pindexPrev, chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT);
-
int nPackagesSelected = 0;
int nDescendantsUpdated = 0;
addPackageTxs(nPackagesSelected, nDescendantsUpdated);
@@ -215,17 +203,12 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOpsCost
// Perform transaction-level checks before adding to block:
// - transaction finality (locktime)
-// - premature witness (in case segwit transactions are added to mempool before
-// segwit activation)
bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) const
{
for (CTxMemPool::txiter it : package) {
if (!IsFinalTx(it->GetTx(), nHeight, m_lock_time_cutoff)) {
return false;
}
- if (!fIncludeWitness && it->GetTx().HasWitness()) {
- return false;
- }
}
return true;
}
diff --git a/src/node/miner.h b/src/node/miner.h
index c96da874a7..97c55f2864 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -132,7 +132,6 @@ private:
std::unique_ptr<CBlockTemplate> pblocktemplate;
// Configuration parameters for the block size
- bool fIncludeWitness;
unsigned int nBlockMaxWeight;
CFeeRate blockMinFeeRate;
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index d6f80e1558..c6b884e40a 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -587,7 +587,7 @@ int GuiMain(int argc, char* argv[])
return EXIT_FAILURE;
}
#ifdef ENABLE_WALLET
- // Parse URIs on command line -- this can affect Params()
+ // Parse URIs on command line
PaymentServer::ipcParseCommandLine(argc, argv);
#endif
diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index cb09035b45..c82f0683fc 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -78,32 +78,11 @@ void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
for (int i = 1; i < argc; i++)
{
QString arg(argv[i]);
- if (arg.startsWith("-"))
- continue;
+ if (arg.startsWith("-")) continue;
- // If the bitcoin: URI contains a payment request, we are not able to detect the
- // network as that would require fetching and parsing the payment request.
- // That means clicking such an URI which contains a testnet payment request
- // will start a mainnet instance and throw a "wrong network" error.
if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
{
- if (savedPaymentRequests.contains(arg)) continue;
savedPaymentRequests.insert(arg);
-
- SendCoinsRecipient r;
- if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
- {
- auto tempChainParams = CreateChainParams(gArgs, CBaseChainParams::MAIN);
-
- if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
- SelectParams(CBaseChainParams::MAIN);
- } else {
- tempChainParams = CreateChainParams(gArgs, CBaseChainParams::TESTNET);
- if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
- SelectParams(CBaseChainParams::TESTNET);
- }
- }
- }
}
}
}
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 579ef0c3fd..c924789796 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -401,6 +401,80 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
return true;
}
+void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
+{
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
+ QMessageBox msgBox;
+ msgBox.setText("Unsigned Transaction");
+ msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
+ msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
+ msgBox.setDefaultButton(QMessageBox::Discard);
+ switch (msgBox.exec()) {
+ case QMessageBox::Save: {
+ QString selectedFilter;
+ QString fileNameSuggestion = "";
+ bool first = true;
+ for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
+ if (!first) {
+ fileNameSuggestion.append(" - ");
+ }
+ QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
+ QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
+ fileNameSuggestion.append(labelOrAddress + "-" + amount);
+ first = false;
+ }
+ fileNameSuggestion.append(".psbt");
+ QString filename = GUIUtil::getSaveFileName(this,
+ tr("Save Transaction Data"), fileNameSuggestion,
+ //: Expanded name of the binary PSBT file format. See: BIP 174.
+ tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
+ if (filename.isEmpty()) {
+ return;
+ }
+ std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
+ out << ssTx.str();
+ out.close();
+ Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
+ break;
+ }
+ case QMessageBox::Discard:
+ break;
+ default:
+ assert(false);
+ } // msgBox.exec()
+}
+
+bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
+ TransactionError err;
+ try {
+ err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
+ } catch (const std::runtime_error& e) {
+ QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
+ return false;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
+ //: "External signer" means using devices such as hardware wallets.
+ QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
+ return false;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
+ //: "External signer" means using devices such as hardware wallets.
+ QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
+ return false;
+ }
+ if (err != TransactionError::OK) {
+ tfm::format(std::cerr, "Failed to sign PSBT");
+ processSendCoinsReturn(WalletModel::TransactionCreationFailed);
+ return false;
+ }
+ // fillPSBT does not always properly finalize
+ complete = FinalizeAndExtractPSBT(psbtx, mtx);
+ return true;
+}
+
void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
{
if(!model || !model->getOptionsModel())
@@ -411,7 +485,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
assert(m_current_transaction);
const QString confirmation = tr("Confirm send coins");
- auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet().privateKeysDisabled(), model->getOptionsModel()->getEnablePSBTControls(), this);
+ const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
+ const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
+ auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
@@ -424,49 +500,50 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool send_failure = false;
if (retval == QMessageBox::Save) {
+ // "Create Unsigned" clicked
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- // Always fill without signing first. This prevents an external signer
- // from being called prematurely and is not expensive.
- TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
+ // Fill without signing
+ TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
assert(!complete);
assert(err == TransactionError::OK);
+
+ // Copy PSBT to clipboard and offer to save
+ presentPSBT(psbtx);
+ } else {
+ // "Send" clicked
+ assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner());
+ bool broadcast = true;
if (model->wallet().hasExternalSigner()) {
- try {
- err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
- } catch (const std::runtime_error& e) {
- QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
- send_failure = true;
- return;
- }
- if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
- //: "External signer" means using devices such as hardware wallets.
- QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
- send_failure = true;
- return;
- }
- if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
- //: "External signer" means using devices such as hardware wallets.
- QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
- send_failure = true;
- return;
- }
- if (err != TransactionError::OK) {
- tfm::format(std::cerr, "Failed to sign PSBT");
- processSendCoinsReturn(WalletModel::TransactionCreationFailed);
- send_failure = true;
- return;
+ CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
+ PartiallySignedTransaction psbtx(mtx);
+ bool complete = false;
+ // Always fill without signing first. This prevents an external signer
+ // from being called prematurely and is not expensive.
+ TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
+ assert(!complete);
+ assert(err == TransactionError::OK);
+ send_failure = !signWithExternalSigner(psbtx, mtx, complete);
+ // Don't broadcast when user rejects it on the device or there's a failure:
+ broadcast = complete && !send_failure;
+ if (!send_failure) {
+ // A transaction signed with an external signer is not always complete,
+ // e.g. in a multisig wallet.
+ if (complete) {
+ // Prepare transaction for broadcast transaction if complete
+ const CTransactionRef tx = MakeTransactionRef(mtx);
+ m_current_transaction->setWtx(tx);
+ } else {
+ presentPSBT(psbtx);
+ }
}
- // fillPSBT does not always properly finalize
- complete = FinalizeAndExtractPSBT(psbtx, mtx);
}
- // Broadcast transaction if complete (even with an external signer this
- // is not always the case, e.g. in a multisig wallet).
- if (complete) {
- const CTransactionRef tx = MakeTransactionRef(mtx);
- m_current_transaction->setWtx(tx);
+ // Broadcast the transaction, unless an external signer was used and it
+ // failed, or more signatures are needed.
+ if (broadcast) {
+ // now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
// process sendStatus and on error generate message shown to user
processSendCoinsReturn(sendStatus);
@@ -476,64 +553,6 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
} else {
send_failure = true;
}
- return;
- }
-
- // Copy PSBT to clipboard and offer to save
- assert(!complete);
- // Serialize the PSBT
- CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- ssTx << psbtx;
- GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
- QMessageBox msgBox;
- msgBox.setText("Unsigned Transaction");
- msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
- msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
- msgBox.setDefaultButton(QMessageBox::Discard);
- switch (msgBox.exec()) {
- case QMessageBox::Save: {
- QString selectedFilter;
- QString fileNameSuggestion = "";
- bool first = true;
- for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
- if (!first) {
- fileNameSuggestion.append(" - ");
- }
- QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
- QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
- fileNameSuggestion.append(labelOrAddress + "-" + amount);
- first = false;
- }
- fileNameSuggestion.append(".psbt");
- QString filename = GUIUtil::getSaveFileName(this,
- tr("Save Transaction Data"), fileNameSuggestion,
- //: Expanded name of the binary PSBT file format. See: BIP 174.
- tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
- if (filename.isEmpty()) {
- return;
- }
- std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
- out << ssTx.str();
- out.close();
- Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
- break;
- }
- case QMessageBox::Discard:
- break;
- default:
- assert(false);
- } // msgBox.exec()
- } else {
- assert(!model->wallet().privateKeysDisabled());
- // now send the prepared transaction
- WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
- // process sendStatus and on error generate message shown to user
- processSendCoinsReturn(sendStatus);
-
- if (sendStatus.status == WalletModel::OK) {
- Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
- } else {
- send_failure = true;
}
}
if (!send_failure) {
diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h
index 4a16702756..400503d0c0 100644
--- a/src/qt/sendcoinsdialog.h
+++ b/src/qt/sendcoinsdialog.h
@@ -70,6 +70,8 @@ private:
bool fFeeMinimized;
const PlatformStyle *platformStyle;
+ // Copy PSBT to clipboard and offer to save it.
+ void presentPSBT(PartiallySignedTransaction& psbt);
// Process WalletModel::SendCoinsReturn and generate a pair consisting
// of a message and message flags for use in Q_EMIT message().
// Additional parameter msgArg can be used via .arg(msgArg).
@@ -77,6 +79,15 @@ private:
void minimizeFeeSection(bool fMinimize);
// Format confirmation message
bool PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text);
+ /* Sign PSBT using external signer.
+ *
+ * @param[in,out] psbtx the PSBT to sign
+ * @param[in,out] mtx needed to attempt to finalize
+ * @param[in,out] complete whether the PSBT is complete (a successfully signed multisig transaction may not be complete)
+ *
+ * @returns false if any failure occurred, which may include the user rejection of a transaction on the device.
+ */
+ bool signWithExternalSigner(PartiallySignedTransaction& psbt, CMutableTransaction& mtx, bool& complete);
void updateFeeMinimizedLabel();
void updateCoinControlState();
@@ -117,6 +128,8 @@ class SendConfirmationDialog : public QMessageBox
public:
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr);
+ /* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is
+ clicked and QMessageBox::Save when "Create Unsigned" is clicked. */
int exec() override;
private Q_SLOTS:
diff --git a/src/rest.cpp b/src/rest.cpp
index 29a16fadb5..4b6bb7ecaf 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -15,6 +15,7 @@
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/blockchain.h>
+#include <rpc/mempool.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 641c8dca45..35b5b979eb 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -26,10 +26,6 @@
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
-#include <policy/feerate.h>
-#include <policy/fees.h>
-#include <policy/policy.h>
-#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
@@ -40,8 +36,8 @@
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
+#include <univalue.h>
#include <util/strencodings.h>
-#include <util/string.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
@@ -50,8 +46,6 @@
#include <stdint.h>
-#include <univalue.h>
-
#include <condition_variable>
#include <memory>
#include <mutex>
@@ -427,366 +421,6 @@ static RPCHelpMan getdifficulty()
};
}
-static std::vector<RPCResult> MempoolEntryDescription() { return {
- RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
- RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
- RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
- "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true,
- "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT +
- " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
- RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
- RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
- RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true,
- "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " +
- CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
- RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
- RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true,
- "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " +
- CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
- RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
- RPCResult{RPCResult::Type::OBJ, "fees", "",
- {
- RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
- RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
- }},
- RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction",
- {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
- RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
- {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
- RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
- RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
-};}
-
-static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
-{
- AssertLockHeld(pool.cs);
-
- info.pushKV("vsize", (int)e.GetTxSize());
- info.pushKV("weight", (int)e.GetTxWeight());
- // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24
- const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")};
- if (deprecated_fee_fields_enabled) {
- info.pushKV("fee", ValueFromAmount(e.GetFee()));
- info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
- }
- info.pushKV("time", count_seconds(e.GetTime()));
- info.pushKV("height", (int)e.GetHeight());
- info.pushKV("descendantcount", e.GetCountWithDescendants());
- info.pushKV("descendantsize", e.GetSizeWithDescendants());
- if (deprecated_fee_fields_enabled) {
- info.pushKV("descendantfees", e.GetModFeesWithDescendants());
- }
- info.pushKV("ancestorcount", e.GetCountWithAncestors());
- info.pushKV("ancestorsize", e.GetSizeWithAncestors());
- if (deprecated_fee_fields_enabled) {
- info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
- }
- info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString());
-
- UniValue fees(UniValue::VOBJ);
- fees.pushKV("base", ValueFromAmount(e.GetFee()));
- fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
- fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors()));
- fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants()));
- info.pushKV("fees", fees);
-
- const CTransaction& tx = e.GetTx();
- std::set<std::string> setDepends;
- for (const CTxIn& txin : tx.vin)
- {
- if (pool.exists(GenTxid::Txid(txin.prevout.hash)))
- setDepends.insert(txin.prevout.hash.ToString());
- }
-
- UniValue depends(UniValue::VARR);
- for (const std::string& dep : setDepends)
- {
- depends.push_back(dep);
- }
-
- info.pushKV("depends", depends);
-
- UniValue spent(UniValue::VARR);
- const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash());
- const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst();
- for (const CTxMemPoolEntry& child : children) {
- spent.push_back(child.GetTx().GetHash().ToString());
- }
-
- info.pushKV("spentby", spent);
-
- // Add opt-in RBF status
- bool rbfStatus = false;
- RBFTransactionState rbfState = IsRBFOptIn(tx, pool);
- if (rbfState == RBFTransactionState::UNKNOWN) {
- throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool");
- } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) {
- rbfStatus = true;
- }
-
- info.pushKV("bip125-replaceable", rbfStatus);
- info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash()));
-}
-
-UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence)
-{
- if (verbose) {
- if (include_mempool_sequence) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values.");
- }
- LOCK(pool.cs);
- UniValue o(UniValue::VOBJ);
- for (const CTxMemPoolEntry& e : pool.mapTx) {
- const uint256& hash = e.GetTx().GetHash();
- UniValue info(UniValue::VOBJ);
- entryToJSON(pool, info, e);
- // Mempool has unique entries so there is no advantage in using
- // UniValue::pushKV, which checks if the key already exists in O(N).
- // UniValue::__pushKV is used instead which currently is O(1).
- o.__pushKV(hash.ToString(), info);
- }
- return o;
- } else {
- uint64_t mempool_sequence;
- std::vector<uint256> vtxid;
- {
- LOCK(pool.cs);
- pool.queryHashes(vtxid);
- mempool_sequence = pool.GetSequence();
- }
- UniValue a(UniValue::VARR);
- for (const uint256& hash : vtxid)
- a.push_back(hash.ToString());
-
- if (!include_mempool_sequence) {
- return a;
- } else {
- UniValue o(UniValue::VOBJ);
- o.pushKV("txids", a);
- o.pushKV("mempool_sequence", mempool_sequence);
- return o;
- }
- }
-}
-
-static RPCHelpMan getrawmempool()
-{
- return RPCHelpMan{"getrawmempool",
- "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
- "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n",
- {
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
- {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."},
- },
- {
- RPCResult{"for verbose = false",
- RPCResult::Type::ARR, "", "",
- {
- {RPCResult::Type::STR_HEX, "", "The transaction id"},
- }},
- RPCResult{"for verbose = true",
- RPCResult::Type::OBJ_DYN, "", "",
- {
- {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
- }},
- RPCResult{"for verbose = false and mempool_sequence = true",
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::ARR, "txids", "",
- {
- {RPCResult::Type::STR_HEX, "", "The transaction id"},
- }},
- {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."},
- }},
- },
- RPCExamples{
- HelpExampleCli("getrawmempool", "true")
- + HelpExampleRpc("getrawmempool", "true")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- bool fVerbose = false;
- if (!request.params[0].isNull())
- fVerbose = request.params[0].get_bool();
-
- bool include_mempool_sequence = false;
- if (!request.params[1].isNull()) {
- include_mempool_sequence = request.params[1].get_bool();
- }
-
- return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence);
-},
- };
-}
-
-static RPCHelpMan getmempoolancestors()
-{
- return RPCHelpMan{"getmempoolancestors",
- "\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
- },
- {
- RPCResult{"for verbose = false",
- RPCResult::Type::ARR, "", "",
- {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}},
- RPCResult{"for verbose = true",
- RPCResult::Type::OBJ_DYN, "", "",
- {
- {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
- }},
- },
- RPCExamples{
- HelpExampleCli("getmempoolancestors", "\"mytxid\"")
- + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- bool fVerbose = false;
- if (!request.params[1].isNull())
- fVerbose = request.params[1].get_bool();
-
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
-
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
-
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
- }
-
- CTxMemPool::setEntries setAncestors;
- uint64_t noLimit = std::numeric_limits<uint64_t>::max();
- std::string dummy;
- mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
-
- if (!fVerbose) {
- UniValue o(UniValue::VARR);
- for (CTxMemPool::txiter ancestorIt : setAncestors) {
- o.push_back(ancestorIt->GetTx().GetHash().ToString());
- }
- return o;
- } else {
- UniValue o(UniValue::VOBJ);
- for (CTxMemPool::txiter ancestorIt : setAncestors) {
- const CTxMemPoolEntry &e = *ancestorIt;
- const uint256& _hash = e.GetTx().GetHash();
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- o.pushKV(_hash.ToString(), info);
- }
- return o;
- }
-},
- };
-}
-
-static RPCHelpMan getmempooldescendants()
-{
- return RPCHelpMan{"getmempooldescendants",
- "\nIf txid is in the mempool, returns all in-mempool descendants.\n",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
- {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
- },
- {
- RPCResult{"for verbose = false",
- RPCResult::Type::ARR, "", "",
- {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}},
- RPCResult{"for verbose = true",
- RPCResult::Type::OBJ_DYN, "", "",
- {
- {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
- }},
- },
- RPCExamples{
- HelpExampleCli("getmempooldescendants", "\"mytxid\"")
- + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- bool fVerbose = false;
- if (!request.params[1].isNull())
- fVerbose = request.params[1].get_bool();
-
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
-
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
-
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
- }
-
- CTxMemPool::setEntries setDescendants;
- mempool.CalculateDescendants(it, setDescendants);
- // CTxMemPool::CalculateDescendants will include the given tx
- setDescendants.erase(it);
-
- if (!fVerbose) {
- UniValue o(UniValue::VARR);
- for (CTxMemPool::txiter descendantIt : setDescendants) {
- o.push_back(descendantIt->GetTx().GetHash().ToString());
- }
-
- return o;
- } else {
- UniValue o(UniValue::VOBJ);
- for (CTxMemPool::txiter descendantIt : setDescendants) {
- const CTxMemPoolEntry &e = *descendantIt;
- const uint256& _hash = e.GetTx().GetHash();
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- o.pushKV(_hash.ToString(), info);
- }
- return o;
- }
-},
- };
-}
-
-static RPCHelpMan getmempoolentry()
-{
- return RPCHelpMan{"getmempoolentry",
- "\nReturns mempool data for given transaction\n",
- {
- {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
- },
- RPCResult{
- RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
- RPCExamples{
- HelpExampleCli("getmempoolentry", "\"mytxid\"")
- + HelpExampleRpc("getmempoolentry", "\"mytxid\"")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- uint256 hash = ParseHashV(request.params[0], "parameter 1");
-
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
- LOCK(mempool.cs);
-
- CTxMemPool::txiter it = mempool.mapTx.find(hash);
- if (it == mempool.mapTx.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
- }
-
- const CTxMemPoolEntry &e = *it;
- UniValue info(UniValue::VOBJ);
- entryToJSON(mempool, info, e);
- return info;
-},
- };
-}
-
static RPCHelpMan getblockfrompeer()
{
return RPCHelpMan{
@@ -1482,7 +1116,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo
// BIP9 status
bip9.pushKV("status", get_state_name(current_state));
bip9.pushKV("since", g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id));
- bip9.pushKV("status-next", get_state_name(next_state));
+ bip9.pushKV("status_next", get_state_name(next_state));
// BIP9 signalling status, if applicable
if (has_signal) {
@@ -1624,7 +1258,7 @@ const std::vector<RPCResult> RPCHelpForDeployment{
{RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"},
{RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"},
{RPCResult::Type::NUM, "since", "height of the first block to which the status applies"},
- {RPCResult::Type::STR, "status-next", "status of deployment at the next block"},
+ {RPCResult::Type::STR, "status_next", "status of deployment at the next block"},
{RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)",
{
{RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"},
@@ -1810,53 +1444,6 @@ static RPCHelpMan getchaintips()
};
}
-UniValue MempoolInfoToJSON(const CTxMemPool& pool)
-{
- // Make sure this call is atomic in the pool.
- LOCK(pool.cs);
- UniValue ret(UniValue::VOBJ);
- ret.pushKV("loaded", pool.IsLoaded());
- ret.pushKV("size", (int64_t)pool.size());
- ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
- ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
- ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee()));
- size_t maxmempool = gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
- ret.pushKV("maxmempool", (int64_t) maxmempool);
- ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));
- ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
- ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
- return ret;
-}
-
-static RPCHelpMan getmempoolinfo()
-{
- return RPCHelpMan{"getmempoolinfo",
- "\nReturns details on the active state of the TX memory pool.\n",
- {},
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"},
- {RPCResult::Type::NUM, "size", "Current tx count"},
- {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"},
- {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"},
- {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"},
- {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
- {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
- {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
- {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}
- }},
- RPCExamples{
- HelpExampleCli("getmempoolinfo", "")
- + HelpExampleRpc("getmempoolinfo", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- return MempoolInfoToJSON(EnsureAnyMemPool(request.context));
-},
- };
-}
-
static RPCHelpMan preciousblock()
{
return RPCHelpMan{"preciousblock",
@@ -2353,41 +1940,6 @@ static RPCHelpMan getblockstats()
};
}
-static RPCHelpMan savemempool()
-{
- return RPCHelpMan{"savemempool",
- "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
- {},
- RPCResult{
- RPCResult::Type::OBJ, "", "",
- {
- {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"},
- }},
- RPCExamples{
- HelpExampleCli("savemempool", "")
- + HelpExampleRpc("savemempool", "")
- },
- [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
-{
- const ArgsManager& args{EnsureAnyArgsman(request.context)};
- const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
-
- if (!mempool.IsLoaded()) {
- throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
- }
-
- if (!DumpMempool(mempool)) {
- throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk");
- }
-
- UniValue ret(UniValue::VOBJ);
- ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string());
-
- return ret;
-},
- };
-}
-
namespace {
//! Search for a given set of pubkey scripts
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point)
@@ -2826,6 +2378,7 @@ UniValue CreateUTXOSnapshot(
return result;
}
+
void RegisterBlockchainRPCCommands(CRPCTable &t)
{
// clang-format off
@@ -2844,15 +2397,9 @@ static const CRPCCommand commands[] =
{ "blockchain", &getchaintips, },
{ "blockchain", &getdifficulty, },
{ "blockchain", &getdeploymentinfo, },
- { "blockchain", &getmempoolancestors, },
- { "blockchain", &getmempooldescendants, },
- { "blockchain", &getmempoolentry, },
- { "blockchain", &getmempoolinfo, },
- { "blockchain", &getrawmempool, },
{ "blockchain", &gettxout, },
{ "blockchain", &gettxoutsetinfo, },
{ "blockchain", &pruneblockchain, },
- { "blockchain", &savemempool, },
{ "blockchain", &verifychain, },
{ "blockchain", &preciousblock, },
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index 1f51d7c1ad..a8c6d171cc 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -20,7 +20,6 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
class CChainState;
-class CTxMemPool;
class UniValue;
namespace node {
struct NodeContext;
@@ -42,12 +41,6 @@ void RPCNotifyBlockChange(const CBlockIndex*);
/** Block description to JSON */
UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main);
-/** Mempool information to JSON */
-UniValue MempoolInfoToJSON(const CTxMemPool& pool);
-
-/** Mempool to JSON */
-UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false);
-
/** Block header to JSON */
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main);
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
new file mode 100644
index 0000000000..bc7ef0c08e
--- /dev/null
+++ b/src/rpc/mempool.cpp
@@ -0,0 +1,476 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <rpc/blockchain.h>
+
+#include <core_io.h>
+#include <fs.h>
+#include <policy/rbf.h>
+#include <primitives/transaction.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <txmempool.h>
+#include <univalue.h>
+#include <validation.h>
+
+static std::vector<RPCResult> MempoolEntryDescription() { return {
+ RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
+ RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true,
+ "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true,
+ "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT +
+ " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
+ RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
+ RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
+ RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true,
+ "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " +
+ CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
+ RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true,
+ "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " +
+ CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"},
+ RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
+ RPCResult{RPCResult::Type::OBJ, "fees", "",
+ {
+ RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
+ RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT},
+ }},
+ RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction",
+ {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}},
+ RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction",
+ {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}},
+ RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"},
+ RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"},
+};}
+
+static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs)
+{
+ AssertLockHeld(pool.cs);
+
+ info.pushKV("vsize", (int)e.GetTxSize());
+ info.pushKV("weight", (int)e.GetTxWeight());
+ // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24
+ const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")};
+ if (deprecated_fee_fields_enabled) {
+ info.pushKV("fee", ValueFromAmount(e.GetFee()));
+ info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
+ }
+ info.pushKV("time", count_seconds(e.GetTime()));
+ info.pushKV("height", (int)e.GetHeight());
+ info.pushKV("descendantcount", e.GetCountWithDescendants());
+ info.pushKV("descendantsize", e.GetSizeWithDescendants());
+ if (deprecated_fee_fields_enabled) {
+ info.pushKV("descendantfees", e.GetModFeesWithDescendants());
+ }
+ info.pushKV("ancestorcount", e.GetCountWithAncestors());
+ info.pushKV("ancestorsize", e.GetSizeWithAncestors());
+ if (deprecated_fee_fields_enabled) {
+ info.pushKV("ancestorfees", e.GetModFeesWithAncestors());
+ }
+ info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString());
+
+ UniValue fees(UniValue::VOBJ);
+ fees.pushKV("base", ValueFromAmount(e.GetFee()));
+ fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
+ fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors()));
+ fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants()));
+ info.pushKV("fees", fees);
+
+ const CTransaction& tx = e.GetTx();
+ std::set<std::string> setDepends;
+ for (const CTxIn& txin : tx.vin)
+ {
+ if (pool.exists(GenTxid::Txid(txin.prevout.hash)))
+ setDepends.insert(txin.prevout.hash.ToString());
+ }
+
+ UniValue depends(UniValue::VARR);
+ for (const std::string& dep : setDepends)
+ {
+ depends.push_back(dep);
+ }
+
+ info.pushKV("depends", depends);
+
+ UniValue spent(UniValue::VARR);
+ const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash());
+ const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst();
+ for (const CTxMemPoolEntry& child : children) {
+ spent.push_back(child.GetTx().GetHash().ToString());
+ }
+
+ info.pushKV("spentby", spent);
+
+ // Add opt-in RBF status
+ bool rbfStatus = false;
+ RBFTransactionState rbfState = IsRBFOptIn(tx, pool);
+ if (rbfState == RBFTransactionState::UNKNOWN) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool");
+ } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) {
+ rbfStatus = true;
+ }
+
+ info.pushKV("bip125-replaceable", rbfStatus);
+ info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash()));
+}
+
+UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence)
+{
+ if (verbose) {
+ if (include_mempool_sequence) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values.");
+ }
+ LOCK(pool.cs);
+ UniValue o(UniValue::VOBJ);
+ for (const CTxMemPoolEntry& e : pool.mapTx) {
+ const uint256& hash = e.GetTx().GetHash();
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(pool, info, e);
+ // Mempool has unique entries so there is no advantage in using
+ // UniValue::pushKV, which checks if the key already exists in O(N).
+ // UniValue::__pushKV is used instead which currently is O(1).
+ o.__pushKV(hash.ToString(), info);
+ }
+ return o;
+ } else {
+ uint64_t mempool_sequence;
+ std::vector<uint256> vtxid;
+ {
+ LOCK(pool.cs);
+ pool.queryHashes(vtxid);
+ mempool_sequence = pool.GetSequence();
+ }
+ UniValue a(UniValue::VARR);
+ for (const uint256& hash : vtxid)
+ a.push_back(hash.ToString());
+
+ if (!include_mempool_sequence) {
+ return a;
+ } else {
+ UniValue o(UniValue::VOBJ);
+ o.pushKV("txids", a);
+ o.pushKV("mempool_sequence", mempool_sequence);
+ return o;
+ }
+ }
+}
+
+RPCHelpMan getrawmempool()
+{
+ return RPCHelpMan{"getrawmempool",
+ "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n"
+ "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n",
+ {
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
+ {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."},
+ },
+ {
+ RPCResult{"for verbose = false",
+ RPCResult::Type::ARR, "", "",
+ {
+ {RPCResult::Type::STR_HEX, "", "The transaction id"},
+ }},
+ RPCResult{"for verbose = true",
+ RPCResult::Type::OBJ_DYN, "", "",
+ {
+ {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
+ }},
+ RPCResult{"for verbose = false and mempool_sequence = true",
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "txids", "",
+ {
+ {RPCResult::Type::STR_HEX, "", "The transaction id"},
+ }},
+ {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getrawmempool", "true")
+ + HelpExampleRpc("getrawmempool", "true")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ bool fVerbose = false;
+ if (!request.params[0].isNull())
+ fVerbose = request.params[0].get_bool();
+
+ bool include_mempool_sequence = false;
+ if (!request.params[1].isNull()) {
+ include_mempool_sequence = request.params[1].get_bool();
+ }
+
+ return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence);
+},
+ };
+}
+
+RPCHelpMan getmempoolancestors()
+{
+ return RPCHelpMan{"getmempoolancestors",
+ "\nIf txid is in the mempool, returns all in-mempool ancestors.\n",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
+ },
+ {
+ RPCResult{"for verbose = false",
+ RPCResult::Type::ARR, "", "",
+ {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}},
+ RPCResult{"for verbose = true",
+ RPCResult::Type::OBJ_DYN, "", "",
+ {
+ {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getmempoolancestors", "\"mytxid\"")
+ + HelpExampleRpc("getmempoolancestors", "\"mytxid\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ bool fVerbose = false;
+ if (!request.params[1].isNull())
+ fVerbose = request.params[1].get_bool();
+
+ uint256 hash = ParseHashV(request.params[0], "parameter 1");
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ CTxMemPool::txiter it = mempool.mapTx.find(hash);
+ if (it == mempool.mapTx.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ }
+
+ CTxMemPool::setEntries setAncestors;
+ uint64_t noLimit = std::numeric_limits<uint64_t>::max();
+ std::string dummy;
+ mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false);
+
+ if (!fVerbose) {
+ UniValue o(UniValue::VARR);
+ for (CTxMemPool::txiter ancestorIt : setAncestors) {
+ o.push_back(ancestorIt->GetTx().GetHash().ToString());
+ }
+ return o;
+ } else {
+ UniValue o(UniValue::VOBJ);
+ for (CTxMemPool::txiter ancestorIt : setAncestors) {
+ const CTxMemPoolEntry &e = *ancestorIt;
+ const uint256& _hash = e.GetTx().GetHash();
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(mempool, info, e);
+ o.pushKV(_hash.ToString(), info);
+ }
+ return o;
+ }
+},
+ };
+}
+
+RPCHelpMan getmempooldescendants()
+{
+ return RPCHelpMan{"getmempooldescendants",
+ "\nIf txid is in the mempool, returns all in-mempool descendants.\n",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
+ {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"},
+ },
+ {
+ RPCResult{"for verbose = false",
+ RPCResult::Type::ARR, "", "",
+ {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}},
+ RPCResult{"for verbose = true",
+ RPCResult::Type::OBJ_DYN, "", "",
+ {
+ {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()},
+ }},
+ },
+ RPCExamples{
+ HelpExampleCli("getmempooldescendants", "\"mytxid\"")
+ + HelpExampleRpc("getmempooldescendants", "\"mytxid\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ bool fVerbose = false;
+ if (!request.params[1].isNull())
+ fVerbose = request.params[1].get_bool();
+
+ uint256 hash = ParseHashV(request.params[0], "parameter 1");
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ CTxMemPool::txiter it = mempool.mapTx.find(hash);
+ if (it == mempool.mapTx.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ }
+
+ CTxMemPool::setEntries setDescendants;
+ mempool.CalculateDescendants(it, setDescendants);
+ // CTxMemPool::CalculateDescendants will include the given tx
+ setDescendants.erase(it);
+
+ if (!fVerbose) {
+ UniValue o(UniValue::VARR);
+ for (CTxMemPool::txiter descendantIt : setDescendants) {
+ o.push_back(descendantIt->GetTx().GetHash().ToString());
+ }
+
+ return o;
+ } else {
+ UniValue o(UniValue::VOBJ);
+ for (CTxMemPool::txiter descendantIt : setDescendants) {
+ const CTxMemPoolEntry &e = *descendantIt;
+ const uint256& _hash = e.GetTx().GetHash();
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(mempool, info, e);
+ o.pushKV(_hash.ToString(), info);
+ }
+ return o;
+ }
+},
+ };
+}
+
+RPCHelpMan getmempoolentry()
+{
+ return RPCHelpMan{"getmempoolentry",
+ "\nReturns mempool data for given transaction\n",
+ {
+ {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "", MempoolEntryDescription()},
+ RPCExamples{
+ HelpExampleCli("getmempoolentry", "\"mytxid\"")
+ + HelpExampleRpc("getmempoolentry", "\"mytxid\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ uint256 hash = ParseHashV(request.params[0], "parameter 1");
+
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+ LOCK(mempool.cs);
+
+ CTxMemPool::txiter it = mempool.mapTx.find(hash);
+ if (it == mempool.mapTx.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
+ }
+
+ const CTxMemPoolEntry &e = *it;
+ UniValue info(UniValue::VOBJ);
+ entryToJSON(mempool, info, e);
+ return info;
+},
+ };
+}
+
+UniValue MempoolInfoToJSON(const CTxMemPool& pool)
+{
+ // Make sure this call is atomic in the pool.
+ LOCK(pool.cs);
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("loaded", pool.IsLoaded());
+ ret.pushKV("size", (int64_t)pool.size());
+ ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize());
+ ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage());
+ ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee()));
+ int64_t maxmempool{gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000};
+ ret.pushKV("maxmempool", maxmempool);
+ ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));
+ ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()));
+ ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
+ return ret;
+}
+
+RPCHelpMan getmempoolinfo()
+{
+ return RPCHelpMan{"getmempoolinfo",
+ "\nReturns details on the active state of the TX memory pool.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"},
+ {RPCResult::Type::NUM, "size", "Current tx count"},
+ {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"},
+ {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"},
+ {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"},
+ {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"},
+ {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"},
+ {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
+ {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}
+ }},
+ RPCExamples{
+ HelpExampleCli("getmempoolinfo", "")
+ + HelpExampleRpc("getmempoolinfo", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ return MempoolInfoToJSON(EnsureAnyMemPool(request.context));
+},
+ };
+}
+
+RPCHelpMan savemempool()
+{
+ return RPCHelpMan{"savemempool",
+ "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"},
+ }},
+ RPCExamples{
+ HelpExampleCli("savemempool", "")
+ + HelpExampleRpc("savemempool", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ const ArgsManager& args{EnsureAnyArgsman(request.context)};
+ const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
+
+ if (!mempool.IsLoaded()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet");
+ }
+
+ if (!DumpMempool(mempool)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk");
+ }
+
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string());
+
+ return ret;
+},
+ };
+}
+
+void RegisterMempoolRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ // category actor (function)
+ // -------- ----------------
+ {"blockchain", &getmempoolancestors},
+ {"blockchain", &getmempooldescendants},
+ {"blockchain", &getmempoolentry},
+ {"blockchain", &getmempoolinfo},
+ {"blockchain", &getrawmempool},
+ {"blockchain", &savemempool},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}
diff --git a/src/rpc/mempool.h b/src/rpc/mempool.h
new file mode 100644
index 0000000000..229d7d52dd
--- /dev/null
+++ b/src/rpc/mempool.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2017-2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_RPC_MEMPOOL_H
+#define BITCOIN_RPC_MEMPOOL_H
+
+class CTxMemPool;
+class UniValue;
+
+/** Mempool information to JSON */
+UniValue MempoolInfoToJSON(const CTxMemPool& pool);
+
+/** Mempool to JSON */
+UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false);
+
+#endif // BITCOIN_RPC_MEMPOOL_H
diff --git a/src/rpc/register.h b/src/rpc/register.h
index c5055cc9d7..cc3a5e0a63 100644
--- a/src/rpc/register.h
+++ b/src/rpc/register.h
@@ -11,6 +11,8 @@ class CRPCTable;
/** Register block chain RPC commands */
void RegisterBlockchainRPCCommands(CRPCTable &tableRPC);
+/** Register mempool RPC commands */
+void RegisterMempoolRPCCommands(CRPCTable&);
/** Register P2P networking RPC commands */
void RegisterNetRPCCommands(CRPCTable &tableRPC);
/** Register miscellaneous RPC commands */
@@ -25,6 +27,7 @@ void RegisterSignerRPCCommands(CRPCTable &tableRPC);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
+ RegisterMempoolRPCCommands(t);
RegisterNetRPCCommands(t);
RegisterMiscRPCCommands(t);
RegisterMiningRPCCommands(t);
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index 806b3169cd..b77c78769f 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -398,13 +398,7 @@ void TaprootSpendData::Merge(TaprootSpendData other)
merkle_root = other.merkle_root;
}
for (auto& [key, control_blocks] : other.scripts) {
- // Once P0083R3 is supported by all our targeted platforms,
- // this loop body can be replaced with:
- // scripts[key].merge(std::move(control_blocks));
- auto& target = scripts[key];
- for (auto& control_block: control_blocks) {
- target.insert(std::move(control_block));
- }
+ scripts[key].merge(std::move(control_blocks));
}
}
diff --git a/src/test/fs_tests.cpp b/src/test/fs_tests.cpp
index 5875f0218f..d2554483d4 100644
--- a/src/test/fs_tests.cpp
+++ b/src/test/fs_tests.cpp
@@ -46,8 +46,8 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream)
{
fs::path tmpfolder = m_args.GetDataDirBase();
// tmpfile1 should be the same as tmpfile2
- fs::path tmpfile1 = tmpfolder / "fs_tests_₿_🏃";
- fs::path tmpfile2 = tmpfolder / "fs_tests_₿_🏃";
+ fs::path tmpfile1 = tmpfolder / fs::u8path("fs_tests_₿_🏃");
+ fs::path tmpfile2 = tmpfolder / fs::u8path("fs_tests_₿_🏃");
{
std::ofstream file{tmpfile1};
file << "bitcoin";
@@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream)
}
{
// Join an absolute path and a relative path.
- fs::path p = fsbridge::AbsPathJoin(tmpfolder, "fs_tests_₿_🏃");
+ fs::path p = fsbridge::AbsPathJoin(tmpfolder, fs::u8path("fs_tests_₿_🏃"));
BOOST_CHECK(p.is_absolute());
BOOST_CHECK_EQUAL(tmpfile1, p);
}
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index c453dae701..f6c1d1efad 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -30,10 +30,10 @@ using node::CBlockTemplate;
namespace miner_tests {
struct MinerTestingSetup : public TestingSetup {
void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
- bool TestSequenceLocks(const CTransaction& tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
+ bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
{
CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool);
- return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), view_mempool, tx, flags);
+ return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx);
}
BlockAssembler AssemblerForTest(const CChainParams& params);
};
@@ -410,7 +410,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
// non-final txs in mempool
SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1);
- int flags = LOCKTIME_VERIFY_SEQUENCE|LOCKTIME_MEDIAN_TIME_PAST;
+ const int flags{LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST};
// height map
std::vector<int> prevheights;
@@ -429,8 +429,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.nLockTime = 0;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
- BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes
- BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
+ BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
{
CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip();
@@ -443,8 +443,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
prevheights[0] = baseheight + 2;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes
- BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
+ BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++)
m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast
@@ -464,8 +464,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime fails
- BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
+ BOOST_CHECK(!CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime fails
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block
// absolute time locked
@@ -475,8 +475,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
prevheights[0] = baseheight + 4;
hash = tx.GetHash();
m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx));
- BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime fails
- BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
+ BOOST_CHECK(!CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime fails
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later
// mempool-dependent transactions (not added)
@@ -484,14 +484,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
prevheights[0] = m_node.chainman->ActiveChain().Tip()->nHeight + 1;
tx.nLockTime = 0;
tx.vin[0].nSequence = 0;
- BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes
- BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
+ BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
tx.vin[0].nSequence = 1;
- BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG;
- BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass
+ BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass
tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1;
- BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail
+ BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
diff --git a/src/test/script_segwit_tests.cpp b/src/test/script_segwit_tests.cpp
new file mode 100644
index 0000000000..2bad59805f
--- /dev/null
+++ b/src/test/script_segwit_tests.cpp
@@ -0,0 +1,164 @@
+// Copyright (c) 2012-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <script/script.h>
+#include <test/util/setup_common.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(script_segwit_tests, BasicTestingSetup)
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Valid)
+{
+ uint256 dummy;
+ CScript p2wsh;
+ p2wsh << OP_0 << ToByteVector(dummy);
+ BOOST_CHECK(p2wsh.IsPayToWitnessScriptHash());
+
+ std::vector<unsigned char> bytes = {OP_0, 32};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_NotOp0)
+{
+ uint256 dummy;
+ CScript notp2wsh;
+ notp2wsh << OP_1 << ToByteVector(dummy);
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Size)
+{
+ uint160 dummy;
+ CScript notp2wsh;
+ notp2wsh << OP_0 << ToByteVector(dummy);
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Nop)
+{
+ uint256 dummy;
+ CScript notp2wsh;
+ notp2wsh << OP_0 << OP_NOP << ToByteVector(dummy);
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_EmptyScript)
+{
+ CScript notp2wsh;
+ BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash());
+}
+
+BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Pushdata)
+{
+ // A script is not P2WSH if OP_PUSHDATA is used to push the hash.
+ std::vector<unsigned char> bytes = {OP_0, OP_PUSHDATA1, 32};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+
+ bytes = {OP_0, OP_PUSHDATA2, 32, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+
+ bytes = {OP_0, OP_PUSHDATA4, 32, 0, 0, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash());
+}
+
+namespace {
+
+bool IsExpectedWitnessProgram(const CScript& script, const int expectedVersion, const std::vector<unsigned char>& expectedProgram)
+{
+ int actualVersion;
+ std::vector<unsigned char> actualProgram;
+ if (!script.IsWitnessProgram(actualVersion, actualProgram)) {
+ return false;
+ }
+ BOOST_CHECK_EQUAL(actualVersion, expectedVersion);
+ BOOST_CHECK(actualProgram == expectedProgram);
+ return true;
+}
+
+bool IsNoWitnessProgram(const CScript& script)
+{
+ int dummyVersion;
+ std::vector<unsigned char> dummyProgram;
+ return !script.IsWitnessProgram(dummyVersion, dummyProgram);
+}
+
+} // anonymous namespace
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Valid)
+{
+ // Witness programs have a minimum data push of 2 bytes.
+ std::vector<unsigned char> program = {42, 18};
+ CScript wit;
+ wit << OP_0 << program;
+ BOOST_CHECK(IsExpectedWitnessProgram(wit, 0, program));
+
+ wit.clear();
+ // Witness programs have a maximum data push of 40 bytes.
+ program.resize(40);
+ wit << OP_16 << program;
+ BOOST_CHECK(IsExpectedWitnessProgram(wit, 16, program));
+
+ program.resize(32);
+ std::vector<unsigned char> bytes = {OP_5, static_cast<unsigned char>(program.size())};
+ bytes.insert(bytes.end(), program.begin(), program.end());
+ BOOST_CHECK(IsExpectedWitnessProgram(CScript(bytes.begin(), bytes.end()), 5, program));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Version)
+{
+ std::vector<unsigned char> program(10);
+ CScript nowit;
+ nowit << OP_1NEGATE << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Size)
+{
+ std::vector<unsigned char> program(1);
+ CScript nowit;
+ nowit << OP_0 << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+
+ nowit.clear();
+ program.resize(41);
+ nowit << OP_0 << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Nop)
+{
+ std::vector<unsigned char> program(10);
+ CScript nowit;
+ nowit << OP_0 << OP_NOP << program;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_EmptyScript)
+{
+ CScript nowit;
+ BOOST_CHECK(IsNoWitnessProgram(nowit));
+}
+
+BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Pushdata)
+{
+ // A script is no witness program if OP_PUSHDATA is used to push the hash.
+ std::vector<unsigned char> bytes = {OP_0, OP_PUSHDATA1, 32};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end())));
+
+ bytes = {OP_0, OP_PUSHDATA2, 32, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end())));
+
+ bytes = {OP_0, OP_PUSHDATA4, 32, 0, 0, 0};
+ bytes.insert(bytes.end(), 32, 0);
+ BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end())));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 9f78215de2..1881573e7a 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -17,6 +17,7 @@
#include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC
#include <util/moneystr.h>
#include <util/overflow.h>
+#include <util/readwritefile.h>
#include <util/spanparsing.h>
#include <util/strencodings.h>
#include <util/string.h>
@@ -24,9 +25,10 @@
#include <util/vector.h>
#include <array>
-#include <optional>
+#include <fstream>
#include <limits>
#include <map>
+#include <optional>
#include <stdint.h>
#include <string.h>
#include <thread>
@@ -2592,4 +2594,49 @@ BOOST_AUTO_TEST_CASE(util_ParseByteUnits)
BOOST_CHECK(!ParseByteUnits("1x", noop));
}
+BOOST_AUTO_TEST_CASE(util_ReadBinaryFile)
+{
+ fs::path tmpfolder = m_args.GetDataDirBase();
+ fs::path tmpfile = tmpfolder / "read_binary.dat";
+ std::string expected_text;
+ for (int i = 0; i < 30; i++) {
+ expected_text += "0123456789";
+ }
+ {
+ std::ofstream file{tmpfile};
+ file << expected_text;
+ }
+ {
+ // read all contents in file
+ auto [valid, text] = ReadBinaryFile(tmpfile);
+ BOOST_CHECK(valid);
+ BOOST_CHECK_EQUAL(text, expected_text);
+ }
+ {
+ // read half contents in file
+ auto [valid, text] = ReadBinaryFile(tmpfile, expected_text.size() / 2);
+ BOOST_CHECK(valid);
+ BOOST_CHECK_EQUAL(text, expected_text.substr(0, expected_text.size() / 2));
+ }
+ {
+ // read from non-existent file
+ fs::path invalid_file = tmpfolder / "invalid_binary.dat";
+ auto [valid, text] = ReadBinaryFile(invalid_file);
+ BOOST_CHECK(!valid);
+ BOOST_CHECK(text.empty());
+ }
+}
+
+BOOST_AUTO_TEST_CASE(util_WriteBinaryFile)
+{
+ fs::path tmpfolder = m_args.GetDataDirBase();
+ fs::path tmpfile = tmpfolder / "write_binary.dat";
+ std::string expected_text = "bitcoin";
+ auto valid = WriteBinaryFile(tmpfile, expected_text);
+ std::string actual_text;
+ std::ifstream file{tmpfile};
+ file >> actual_text;
+ BOOST_CHECK(valid);
+ BOOST_CHECK_EQUAL(actual_text, expected_text);
+}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp
index a45c41d367..628e6a3980 100644
--- a/src/util/readwritefile.cpp
+++ b/src/util/readwritefile.cpp
@@ -18,7 +18,7 @@ std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxs
std::string retval;
char buffer[128];
do {
- const size_t n = fread(buffer, 1, sizeof(buffer), f);
+ const size_t n = fread(buffer, 1, std::min(sizeof(buffer), maxsize - retval.size()), f);
// Check for reading errors so we don't return any data if we couldn't
// read the entire file (or up to maxsize)
if (ferror(f)) {
@@ -26,7 +26,7 @@ std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxs
return std::make_pair(false,"");
}
retval.append(buffer, buffer+n);
- } while (!feof(f) && retval.size() <= maxsize);
+ } while (!feof(f) && retval.size() < maxsize);
fclose(f);
return std::make_pair(true,retval);
}
diff --git a/src/validation.cpp b/src/validation.cpp
index 58488be3a7..f399acb9fd 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -161,20 +161,12 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
std::vector<CScriptCheck>* pvChecks = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags)
+bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& tx)
{
AssertLockHeld(cs_main);
assert(active_chain_tip); // TODO: Make active_chain_tip a reference
- // By convention a negative value for flags indicates that the
- // current network-enforced consensus rules should be used. In
- // a future soft-fork scenario that would mean checking which
- // rules would be enforced for the next block and setting the
- // appropriate flags. At the present time no soft-forks are
- // scheduled, so no flags are set.
- flags = std::max(flags, 0);
-
- // CheckFinalTx() uses active_chain_tip.Height()+1 to evaluate
+ // CheckFinalTxAtTip() uses active_chain_tip.Height()+1 to evaluate
// nLockTime because when IsFinalTx() is called within
// AcceptBlock(), the height of the block *being*
// evaluated is what is used. Thus if we want to know if a
@@ -186,18 +178,15 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i
// less than the median time of the previous block they're contained in.
// When the next block is created its previous block will be the current
// chain tip, so we use that to calculate the median time passed to
- // IsFinalTx() if LOCKTIME_MEDIAN_TIME_PAST is set.
- const int64_t nBlockTime = (flags & LOCKTIME_MEDIAN_TIME_PAST)
- ? active_chain_tip->GetMedianTimePast()
- : GetAdjustedTime();
+ // IsFinalTx().
+ const int64_t nBlockTime{active_chain_tip->GetMedianTimePast()};
return IsFinalTx(tx, nBlockHeight, nBlockTime);
}
-bool CheckSequenceLocks(CBlockIndex* tip,
+bool CheckSequenceLocksAtTip(CBlockIndex* tip,
const CCoinsView& coins_view,
const CTransaction& tx,
- int flags,
LockPoints* lp,
bool useExistingLockPoints)
{
@@ -205,7 +194,7 @@ bool CheckSequenceLocks(CBlockIndex* tip,
CBlockIndex index;
index.pprev = tip;
- // CheckSequenceLocks() uses active_chainstate.m_chain.Height()+1 to evaluate
+ // CheckSequenceLocksAtTip() uses active_chainstate.m_chain.Height()+1 to evaluate
// height based locks because when SequenceLocks() is called within
// ConnectBlock(), the height of the block *being*
// evaluated is what is used.
@@ -235,7 +224,7 @@ bool CheckSequenceLocks(CBlockIndex* tip,
prevheights[txinIndex] = coin.nHeight;
}
}
- lockPair = CalculateSequenceLocks(tx, flags, prevheights, index);
+ lockPair = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index);
if (lp) {
lp->height = lockPair.first;
lp->time = lockPair.second;
@@ -251,7 +240,7 @@ bool CheckSequenceLocks(CBlockIndex* tip,
// lockPair from CalculateSequenceLocks against tip+1. We know
// EvaluateSequenceLocks will fail if there was a non-zero sequence
// lock on a mempool input, so we can use the return value of
- // CheckSequenceLocks to indicate the LockPoints validity
+ // CheckSequenceLocksAtTip to indicate the LockPoints validity
int maxInputHeight = 0;
for (const int height : prevheights) {
// Can ignore mempool inputs since we'll fail if they had non-zero locks
@@ -341,26 +330,26 @@ void CChainState::MaybeUpdateMempoolForReorg(
// Also updates valid entries' cached LockPoints if needed.
// If false, the tx is still valid and its lockpoints are updated.
// If true, the tx would be invalid in the next block; remove this entry and all of its descendants.
- const auto filter_final_and_mature = [this, flags=STANDARD_LOCKTIME_VERIFY_FLAGS](CTxMemPool::txiter it)
+ const auto filter_final_and_mature = [this](CTxMemPool::txiter it)
EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main) {
AssertLockHeld(m_mempool->cs);
AssertLockHeld(::cs_main);
const CTransaction& tx = it->GetTx();
// The transaction must be final.
- if (!CheckFinalTx(m_chain.Tip(), tx, flags)) return true;
+ if (!CheckFinalTxAtTip(m_chain.Tip(), tx)) return true;
LockPoints lp = it->GetLockPoints();
const bool validLP{TestLockPointValidity(m_chain, lp)};
CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool);
- // CheckSequenceLocks checks if the transaction will be final in the next block to be
+ // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be
// created on top of the new chain. We use useExistingLockPoints=false so that, instead of
// using the information in lp (which might now refer to a block that no longer exists in
// the chain), it will update lp to contain LockPoints relevant to the new chain.
- if (!CheckSequenceLocks(m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) {
- // If CheckSequenceLocks fails, remove the tx and don't depend on the LockPoints.
+ if (!CheckSequenceLocksAtTip(m_chain.Tip(), view_mempool, tx, &lp, validLP)) {
+ // If CheckSequenceLocksAtTip fails, remove the tx and don't depend on the LockPoints.
return true;
} else if (!validLP) {
- // If CheckSequenceLocks succeeded, it also updated the LockPoints.
+ // If CheckSequenceLocksAtTip succeeded, it also updated the LockPoints.
// Now update the mempool entry lockpoints as well.
m_mempool->mapTx.modify(it, [&lp](CTxMemPoolEntry& e) { e.UpdateLockPoints(lp); });
}
@@ -511,9 +500,26 @@ public:
/* m_package_submission */ true,
};
}
- // No default ctor to avoid exposing details to clients and allowing the possibility of
+
+ private:
+ // Private ctor to avoid exposing details to clients and allowing the possibility of
// mixing up the order of the arguments. Use static functions above instead.
- ATMPArgs() = delete;
+ ATMPArgs(const CChainParams& chainparams,
+ int64_t accept_time,
+ bool bypass_limits,
+ std::vector<COutPoint>& coins_to_uncache,
+ bool test_accept,
+ bool allow_bip125_replacement,
+ bool package_submission)
+ : m_chainparams{chainparams},
+ m_accept_time{accept_time},
+ m_bypass_limits{bypass_limits},
+ m_coins_to_uncache{coins_to_uncache},
+ m_test_accept{test_accept},
+ m_allow_bip125_replacement{allow_bip125_replacement},
+ m_package_submission{package_submission}
+ {
+ }
};
// Single transaction acceptance
@@ -688,8 +694,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
- if (!CheckFinalTx(m_active_chainstate.m_chain.Tip(), tx, STANDARD_LOCKTIME_VERIFY_FLAGS))
+ if (!CheckFinalTxAtTip(m_active_chainstate.m_chain.Tip(), tx)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
+ }
if (m_pool.exists(GenTxid::Wtxid(tx.GetWitnessHash()))) {
// Exact transaction already exists in the mempool.
@@ -769,8 +776,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// be mined yet.
// Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's
// backend was removed, it no longer pulls coins from the mempool.
- if (!CheckSequenceLocks(m_active_chainstate.m_chain.Tip(), m_view, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
+ if (!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx, &lp)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
+ }
// The mempool holds txs for the next block, so pass height+1 to CheckTxInputs
if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) {
diff --git a/src/validation.h b/src/validation.h
index 8833a4813e..b13e7f3d8b 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -273,16 +273,12 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
const Package& txns, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-/** Transaction validation functions */
+/* Transaction policy functions */
/**
* Check if transaction will be final in the next block to be created.
- *
- * Calls IsFinalTx() with current block height and appropriate block time.
- *
- * See consensus/consensus.h for flag definitions.
*/
-bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags = -1) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Check if transaction will be BIP68 final in the next block to be created on top of tip.
@@ -299,14 +295,11 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i
* Optionally stores in LockPoints the resulting height and time calculated and the hash
* of the block needed for calculation or skips the calculation and uses the LockPoints
* passed in for evaluation.
- * The LockPoints should not be considered valid if CheckSequenceLocks returns false.
- *
- * See consensus/consensus.h for flag definitions.
+ * The LockPoints should not be considered valid if CheckSequenceLocksAtTip returns false.
*/
-bool CheckSequenceLocks(CBlockIndex* tip,
+bool CheckSequenceLocksAtTip(CBlockIndex* tip,
const CCoinsView& coins_view,
const CTransaction& tx,
- int flags,
LockPoints* lp = nullptr,
bool useExistingLockPoints = false);
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 23faad027f..513572da45 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -163,6 +163,8 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
result.AddInput(utxo_pool.at(i));
}
}
+ result.ComputeAndSetWaste(CAmount{0});
+ assert(best_waste == result.GetWaste());
return result;
}
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 433b5a1815..072879a42a 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -659,7 +659,7 @@ RPCHelpMan fundrawtransaction()
{"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
"Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
"If that happens, you will need to fund the transaction with different inputs and republish it."},
- {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"},
+ {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
{"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
@@ -1056,7 +1056,7 @@ RPCHelpMan send()
"Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
"If that happens, you will need to fund the transaction with different inputs and republish it."},
{"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
- {"change_address", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"},
+ {"change_address", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
{"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
@@ -1351,7 +1351,7 @@ RPCHelpMan walletcreatefundedpsbt()
{"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
"Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
"If that happens, you will need to fund the transaction with different inputs and republish it."},
- {"changeAddress", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"},
+ {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
{"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only"},
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 883a3c102b..1380f1a77a 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -315,7 +315,9 @@ static RPCHelpMan createwallet()
{"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."},
{"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
- {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
+ {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."
+ " Setting to \"false\" will create a legacy wallet; however, the legacy wallet type is being deprecated and"
+ " support for creating and opening legacy wallets will be removed in the future."},
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
{"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
},
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 83eaececc1..a707ef89d2 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -379,7 +379,6 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
- bnb_result->ComputeAndSetWaste(CAmount(0));
results.push_back(*bnb_result);
}
@@ -583,12 +582,13 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
}
/**
- * Return a height-based locktime for new transactions (uses the height of the
+ * Set a height-based locktime for new transactions (uses the height of the
* current chain tip unless we are not synced with the current chain
*/
-static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height)
+static void DiscourageFeeSniping(CMutableTransaction& tx, interfaces::Chain& chain, const uint256& block_hash, int block_height)
{
- uint32_t locktime;
+ // All inputs must be added by now
+ assert(!tx.vin.empty());
// Discourage fee sniping.
//
// For a large miner the value of the transactions in the best block and
@@ -610,22 +610,34 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin
// now we ensure code won't be written that makes assumptions about
// nLockTime that preclude a fix later.
if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
- locktime = block_height;
+ tx.nLockTime = block_height;
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
- if (GetRandInt(10) == 0)
- locktime = std::max(0, (int)locktime - GetRandInt(100));
+ if (GetRandInt(10) == 0) {
+ tx.nLockTime = std::max(0, int(tx.nLockTime) - GetRandInt(100));
+ }
} else {
// If our chain is lagging behind, we can't discourage fee sniping nor help
// the privacy of high-latency transactions. To avoid leaking a potentially
// unique "nLockTime fingerprint", set nLockTime to a constant.
- locktime = 0;
+ tx.nLockTime = 0;
+ }
+ // Sanity check all values
+ assert(tx.nLockTime < LOCKTIME_THRESHOLD); // Type must be block height
+ assert(tx.nLockTime <= uint64_t(block_height));
+ for (const auto& in : tx.vin) {
+ // Can not be FINAL for locktime to work
+ assert(in.nSequence != CTxIn::SEQUENCE_FINAL);
+ // May be MAX NONFINAL to disable both BIP68 and BIP125
+ if (in.nSequence == CTxIn::MAX_SEQUENCE_NONFINAL) continue;
+ // May be MAX BIP125 to disable BIP68 and enable BIP125
+ if (in.nSequence == MAX_BIP125_RBF_SEQUENCE) continue;
+ // The wallet does not support any other sequence-use right now.
+ assert(false);
}
- assert(locktime < LOCKTIME_THRESHOLD);
- return locktime;
}
static bool CreateTransactionInternal(
@@ -642,7 +654,6 @@ static bool CreateTransactionInternal(
AssertLockHeld(wallet.cs_wallet);
CMutableTransaction txNew; // The resulting transaction that we make
- txNew.nLockTime = GetLocktimeForNewTransaction(wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
@@ -788,8 +799,8 @@ static bool CreateTransactionInternal(
// Shuffle selected coins and fill in final vin
std::vector<CInputCoin> selected_coins = result->GetShuffledInputVector();
- // Note how the sequence number is set to non-maxint so that
- // the nLockTime set above actually works.
+ // The sequence number is set to non-maxint so that DiscourageFeeSniping
+ // works.
//
// BIP125 defines opt-in RBF as any nSequence < maxint-1, so
// we use the highest possible value in that range (maxint-2)
@@ -800,6 +811,7 @@ static bool CreateTransactionInternal(
for (const auto& coin : selected_coins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
}
+ DiscourageFeeSniping(txNew, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
// Calculate the transaction fee
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index 2b2181e70b..55949e6e7a 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -37,6 +37,22 @@ static void ErrorLogCallback(void* arg, int code, const char* msg)
LogPrintf("SQLite Error. Code: %d. Message: %s\n", code, msg);
}
+static bool BindBlobToStatement(sqlite3_stmt* stmt,
+ int index,
+ Span<const std::byte> blob,
+ const std::string& description)
+{
+ int res = sqlite3_bind_blob(stmt, index, blob.data(), blob.size(), SQLITE_STATIC);
+ if (res != SQLITE_OK) {
+ LogPrintf("Unable to bind %s to statement: %s\n", description, sqlite3_errstr(res));
+ sqlite3_clear_bindings(stmt);
+ sqlite3_reset(stmt);
+ return false;
+ }
+
+ return true;
+}
+
static std::optional<int> ReadPragmaInteger(sqlite3* db, const std::string& key, const std::string& description, bilingual_str& error)
{
std::string stmt_text = strprintf("PRAGMA %s", key);
@@ -377,14 +393,8 @@ bool SQLiteBatch::ReadKey(CDataStream&& key, CDataStream& value)
assert(m_read_stmt);
// Bind: leftmost parameter in statement is index 1
- int res = sqlite3_bind_blob(m_read_stmt, 1, key.data(), key.size(), SQLITE_STATIC);
- if (res != SQLITE_OK) {
- LogPrintf("%s: Unable to bind statement: %s\n", __func__, sqlite3_errstr(res));
- sqlite3_clear_bindings(m_read_stmt);
- sqlite3_reset(m_read_stmt);
- return false;
- }
- res = sqlite3_step(m_read_stmt);
+ if (!BindBlobToStatement(m_read_stmt, 1, key, "key")) return false;
+ int res = sqlite3_step(m_read_stmt);
if (res != SQLITE_ROW) {
if (res != SQLITE_DONE) {
// SQLITE_DONE means "not found", don't log an error in that case.
@@ -418,23 +428,11 @@ bool SQLiteBatch::WriteKey(CDataStream&& key, CDataStream&& value, bool overwrit
// Bind: leftmost parameter in statement is index 1
// Insert index 1 is key, 2 is value
- int res = sqlite3_bind_blob(stmt, 1, key.data(), key.size(), SQLITE_STATIC);
- if (res != SQLITE_OK) {
- LogPrintf("%s: Unable to bind key to statement: %s\n", __func__, sqlite3_errstr(res));
- sqlite3_clear_bindings(stmt);
- sqlite3_reset(stmt);
- return false;
- }
- res = sqlite3_bind_blob(stmt, 2, value.data(), value.size(), SQLITE_STATIC);
- if (res != SQLITE_OK) {
- LogPrintf("%s: Unable to bind value to statement: %s\n", __func__, sqlite3_errstr(res));
- sqlite3_clear_bindings(stmt);
- sqlite3_reset(stmt);
- return false;
- }
+ if (!BindBlobToStatement(stmt, 1, key, "key")) return false;
+ if (!BindBlobToStatement(stmt, 2, value, "value")) return false;
// Execute
- res = sqlite3_step(stmt);
+ int res = sqlite3_step(stmt);
sqlite3_clear_bindings(stmt);
sqlite3_reset(stmt);
if (res != SQLITE_DONE) {
@@ -449,16 +447,10 @@ bool SQLiteBatch::EraseKey(CDataStream&& key)
assert(m_delete_stmt);
// Bind: leftmost parameter in statement is index 1
- int res = sqlite3_bind_blob(m_delete_stmt, 1, key.data(), key.size(), SQLITE_STATIC);
- if (res != SQLITE_OK) {
- LogPrintf("%s: Unable to bind statement: %s\n", __func__, sqlite3_errstr(res));
- sqlite3_clear_bindings(m_delete_stmt);
- sqlite3_reset(m_delete_stmt);
- return false;
- }
+ if (!BindBlobToStatement(m_delete_stmt, 1, key, "key")) return false;
// Execute
- res = sqlite3_step(m_delete_stmt);
+ int res = sqlite3_step(m_delete_stmt);
sqlite3_clear_bindings(m_delete_stmt);
sqlite3_reset(m_delete_stmt);
if (res != SQLITE_DONE) {
@@ -473,18 +465,11 @@ bool SQLiteBatch::HasKey(CDataStream&& key)
assert(m_read_stmt);
// Bind: leftmost parameter in statement is index 1
- bool ret = false;
- int res = sqlite3_bind_blob(m_read_stmt, 1, key.data(), key.size(), SQLITE_STATIC);
- if (res == SQLITE_OK) {
- res = sqlite3_step(m_read_stmt);
- if (res == SQLITE_ROW) {
- ret = true;
- }
- }
-
+ if (!BindBlobToStatement(m_read_stmt, 1, key, "key")) return false;
+ int res = sqlite3_step(m_read_stmt);
sqlite3_clear_bindings(m_read_stmt);
sqlite3_reset(m_read_stmt);
- return ret;
+ return res == SQLITE_ROW;
}
bool SQLiteBatch::StartCursor()
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index b953f402a2..62053ae8d2 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -68,7 +68,6 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input
SignatureData sigdata;
BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true) != TransactionError::OK);
- //BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK);
}
BOOST_AUTO_TEST_CASE(parse_hd_keypath)
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 6cf9f9ce74..261d042529 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -354,6 +354,11 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
// Write the wallet settings
UpdateWalletSetting(*context.chain, name, load_on_start, warnings);
+ // Legacy wallets are being deprecated, warn if a newly created wallet is legacy
+ if (!(wallet_creation_flags & WALLET_FLAG_DESCRIPTORS)) {
+ warnings.push_back(_("Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future."));
+ }
+
status = DatabaseStatus::SUCCESS;
return wallet;
}