diff options
Diffstat (limited to 'src')
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; } |