diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.bench.include | 1 | ||||
-rw-r--r-- | src/bench/addrman.cpp | 140 | ||||
-rw-r--r-- | src/net.h | 6 | ||||
-rw-r--r-- | src/net_processing.cpp | 63 | ||||
-rw-r--r-- | src/node/transaction.h | 8 | ||||
-rw-r--r-- | src/psbt.h | 4 | ||||
-rw-r--r-- | src/qt/bitcoingui.cpp | 8 | ||||
-rw-r--r-- | src/qt/bitcoingui.h | 3 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.cpp | 136 | ||||
-rw-r--r-- | src/qt/sendcoinsdialog.h | 3 | ||||
-rw-r--r-- | src/qt/walletframe.cpp | 8 | ||||
-rw-r--r-- | src/qt/walletframe.h | 3 | ||||
-rw-r--r-- | src/qt/walletview.cpp | 78 | ||||
-rw-r--r-- | src/qt/walletview.h | 2 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 6 | ||||
-rw-r--r-- | src/test/fuzz/process_message.cpp | 24 | ||||
-rw-r--r-- | src/util/system.cpp | 6 | ||||
-rw-r--r-- | src/util/system.h | 8 | ||||
-rw-r--r-- | src/validation.cpp | 19 | ||||
-rw-r--r-- | src/validation.h | 79 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 22 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 15 |
22 files changed, 464 insertions, 178 deletions
diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index eae8b1fcd1..766c0fca54 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -12,6 +12,7 @@ GENERATED_BENCH_FILES = $(RAW_BENCH_FILES:.raw=.raw.h) bench_bench_bitcoin_SOURCES = \ $(RAW_BENCH_FILES) \ + bench/addrman.cpp \ bench/bench_bitcoin.cpp \ bench/bench.cpp \ bench/bench.h \ diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp new file mode 100644 index 0000000000..cc260df2b8 --- /dev/null +++ b/src/bench/addrman.cpp @@ -0,0 +1,140 @@ +// Copyright (c) 2020-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 <addrman.h> +#include <bench/bench.h> +#include <random.h> +#include <util/time.h> + +#include <vector> + +/* A "source" is a source address from which we have received a bunch of other addresses. */ + +static constexpr size_t NUM_SOURCES = 64; +static constexpr size_t NUM_ADDRESSES_PER_SOURCE = 256; + +static std::vector<CAddress> g_sources; +static std::vector<std::vector<CAddress>> g_addresses; + +static void CreateAddresses() +{ + if (g_sources.size() > 0) { // already created + return; + } + + FastRandomContext rng(uint256(std::vector<unsigned char>(32, 123))); + + auto randAddr = [&rng]() { + in6_addr addr; + memcpy(&addr, rng.randbytes(sizeof(addr)).data(), sizeof(addr)); + + uint16_t port; + memcpy(&port, rng.randbytes(sizeof(port)).data(), sizeof(port)); + if (port == 0) { + port = 1; + } + + CAddress ret(CService(addr, port), NODE_NETWORK); + + ret.nTime = GetAdjustedTime(); + + return ret; + }; + + for (size_t source_i = 0; source_i < NUM_SOURCES; ++source_i) { + g_sources.emplace_back(randAddr()); + g_addresses.emplace_back(); + for (size_t addr_i = 0; addr_i < NUM_ADDRESSES_PER_SOURCE; ++addr_i) { + g_addresses[source_i].emplace_back(randAddr()); + } + } +} + +static void AddAddressesToAddrMan(CAddrMan& addrman) +{ + for (size_t source_i = 0; source_i < NUM_SOURCES; ++source_i) { + addrman.Add(g_addresses[source_i], g_sources[source_i]); + } +} + +static void FillAddrMan(CAddrMan& addrman) +{ + CreateAddresses(); + + AddAddressesToAddrMan(addrman); +} + +/* Benchmarks */ + +static void AddrManAdd(benchmark::State& state) +{ + CreateAddresses(); + + CAddrMan addrman; + + while (state.KeepRunning()) { + AddAddressesToAddrMan(addrman); + addrman.Clear(); + } +} + +static void AddrManSelect(benchmark::State& state) +{ + CAddrMan addrman; + + FillAddrMan(addrman); + + while (state.KeepRunning()) { + const auto& address = addrman.Select(); + assert(address.GetPort() > 0); + } +} + +static void AddrManGetAddr(benchmark::State& state) +{ + CAddrMan addrman; + + FillAddrMan(addrman); + + while (state.KeepRunning()) { + const auto& addresses = addrman.GetAddr(); + assert(addresses.size() > 0); + } +} + +static void AddrManGood(benchmark::State& state) +{ + /* Create many CAddrMan objects - one to be modified at each loop iteration. + * This is necessary because the CAddrMan::Good() method modifies the + * object, affecting the timing of subsequent calls to the same method and + * we want to do the same amount of work in every loop iteration. */ + + const uint64_t numLoops = state.m_num_iters * state.m_num_evals; + + std::vector<CAddrMan> addrmans(numLoops); + for (auto& addrman : addrmans) { + FillAddrMan(addrman); + } + + auto markSomeAsGood = [](CAddrMan& addrman) { + for (size_t source_i = 0; source_i < NUM_SOURCES; ++source_i) { + for (size_t addr_i = 0; addr_i < NUM_ADDRESSES_PER_SOURCE; ++addr_i) { + if (addr_i % 32 == 0) { + addrman.Good(g_addresses[source_i][addr_i]); + } + } + } + }; + + uint64_t i = 0; + while (state.KeepRunning()) { + markSomeAsGood(addrmans.at(i)); + ++i; + } +} + +BENCHMARK(AddrManAdd, 5); +BENCHMARK(AddrManSelect, 1000000); +BENCHMARK(AddrManGetAddr, 500); +BENCHMARK(AddrManGood, 2); @@ -45,16 +45,10 @@ static const bool DEFAULT_WHITELISTRELAY = true; /** Default for -whitelistforcerelay. */ static const bool DEFAULT_WHITELISTFORCERELAY = false; -/** Time between pings automatically sent out for latency probing and keepalive (in seconds). */ -static const int PING_INTERVAL = 2 * 60; /** Time after which to disconnect, after waiting for a ping response (or inactivity). */ static const int TIMEOUT_INTERVAL = 20 * 60; /** Run the feeler connection loop once every 2 minutes or 120 seconds. **/ static const int FEELER_INTERVAL = 120; -/** The maximum number of entries in an 'inv' protocol message */ -static const unsigned int MAX_INV_SZ = 50000; -/** The maximum number of entries in a locator */ -static const unsigned int MAX_LOCATOR_SZ = 101; /** The maximum number of new addresses to accumulate before announcing. */ static const unsigned int MAX_ADDR_TO_SEND = 1000; /** Maximum length of incoming protocol messages (no message over 4 MB is currently acceptable). */ diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 96e9178b6e..26327ac6eb 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -64,6 +64,12 @@ static constexpr int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60; /// Age after which a block is considered historical for purposes of rate /// limiting block relay. Set to one week, denominated in seconds. static constexpr int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60; +/** Time between pings automatically sent out for latency probing and keepalive (in seconds). */ +static const int PING_INTERVAL = 2 * 60; +/** The maximum number of entries in a locator */ +static const unsigned int MAX_LOCATOR_SZ = 101; +/** The maximum number of entries in an 'inv' protocol message */ +static const unsigned int MAX_INV_SZ = 50000; /** Maximum number of in-flight transactions from a peer */ static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100; /** Maximum number of announced transactions from a peer */ @@ -80,7 +86,47 @@ static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY, "To preserve security, MAX_GETDATA_RANDOM_DELAY should not exceed INBOUND_PEER_DELAY"); /** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */ static const unsigned int MAX_GETDATA_SZ = 1000; - +/** Number of blocks that can be requested at any given time from a single peer. */ +static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16; +/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */ +static const unsigned int BLOCK_STALLING_TIMEOUT = 2; +/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends + * less than this number, we reached its tip. Changing this value is a protocol upgrade. */ +static const unsigned int MAX_HEADERS_RESULTS = 2000; +/** Maximum depth of blocks we're willing to serve as compact blocks to peers + * when requested. For older blocks, a regular BLOCK response will be sent. */ +static const int MAX_CMPCTBLOCK_DEPTH = 5; +/** Maximum depth of blocks we're willing to respond to GETBLOCKTXN requests for. */ +static const int MAX_BLOCKTXN_DEPTH = 10; +/** Size of the "block download window": how far ahead of our current height do we fetch? + * Larger windows tolerate larger download speed differences between peer, but increase the potential + * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably + * want to make this a per-peer adaptive value at some point. */ +static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; +/** Block download timeout base, expressed in millionths of the block interval (i.e. 10 min) */ +static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 1000000; +/** Additional block download timeout per parallel downloading peer (i.e. 5 min) */ +static const int64_t BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 500000; +/** Maximum number of headers to announce when relaying blocks with headers message.*/ +static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; +/** Maximum number of unconnecting headers announcements before DoS score */ +static const int MAX_UNCONNECTING_HEADERS = 10; +/** Minimum blocks required to signal NODE_NETWORK_LIMITED */ +static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; +/** Average delay between local address broadcasts */ +static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24}; +/** Average delay between peer address broadcasts */ +static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30}; +/** Average delay between trickled inventory transmissions in seconds. + * Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */ +static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; +/** Maximum number of inventory items to send per transmission. + * Limits the impact of low-fee transaction floods. */ +static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL; +/** Average delay between feefilter broadcasts in seconds. */ +static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; +/** Maximum feefilter broadcast delay after significant change. */ +static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; struct COrphanTx { // When modifying, adapt the copy of this definition in tests/DoS_tests. @@ -97,21 +143,6 @@ void EraseOrphansFor(NodeId peer); /** Increase a node's misbehavior score. */ void Misbehaving(NodeId nodeid, int howmuch, const std::string& message="") EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Average delay between local address broadcasts */ -static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24}; -/** Average delay between peer address broadcasts */ -static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30}; -/** Average delay between trickled inventory transmissions in seconds. - * Blocks and whitelisted receivers bypass this, outbound peers get half this delay. */ -static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; -/** Maximum number of inventory items to send per transmission. - * Limits the impact of low-fee transaction floods. */ -static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_INTERVAL; -/** Average delay between feefilter broadcasts in seconds. */ -static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; -/** Maximum feefilter broadcast delay after significant change. */ -static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; - // Internal stuff namespace { /** Number of nodes with fSyncStarted. */ diff --git a/src/node/transaction.h b/src/node/transaction.h index 98d65bc4dd..6491700d44 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -6,11 +6,19 @@ #define BITCOIN_NODE_TRANSACTION_H #include <attributes.h> +#include <policy/feerate.h> #include <primitives/transaction.h> #include <util/error.h> struct NodeContext; +/** Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls. + * Also used by the GUI when broadcasting a completed PSBT. + * By default, a transaction with a fee rate higher than this will be rejected + * by these RPCs and the GUI. This can be overridden with the maxfeerate argument. + */ +static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10}; + /** * Submit a transaction to the mempool and (optionally) relay it to all P2P peers. * diff --git a/src/psbt.h b/src/psbt.h index 83a729217b..af57994f3a 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -40,6 +40,10 @@ static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02; // as a 0 length key which indicates that this is the separator. The separator has no value. static constexpr uint8_t PSBT_SEPARATOR = 0x00; +// BIP 174 does not specify a maximum file size, but we set a limit anyway +// to prevent reading a stream indefinately and running out of memory. +const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MiB + /** A structure for PSBTs which contain per-input information */ struct PSBTInput { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 75f3e9bf45..71e4d59ea7 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -317,6 +317,8 @@ void BitcoinGUI::createActions() signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them")); verifyMessageAction = new QAction(tr("&Verify message..."), this); verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses")); + m_load_psbt_action = new QAction(tr("Load PSBT..."), this); + m_load_psbt_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction")); openRPCConsoleAction = new QAction(tr("Node window"), this); openRPCConsoleAction->setStatusTip(tr("Open node debugging and diagnostic console")); @@ -366,6 +368,7 @@ void BitcoinGUI::createActions() connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); }); + connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); @@ -438,6 +441,7 @@ void BitcoinGUI::createMenuBar() file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); + file->addAction(m_load_psbt_action); file->addSeparator(); } file->addAction(quitAction); @@ -854,6 +858,10 @@ void BitcoinGUI::gotoVerifyMessageTab(QString addr) { if (walletFrame) walletFrame->gotoVerifyMessageTab(addr); } +void BitcoinGUI::gotoLoadPSBT() +{ + if (walletFrame) walletFrame->gotoLoadPSBT(); +} #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 9b5523f625..70366e12d1 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -135,6 +135,7 @@ private: QAction* usedReceivingAddressesAction = nullptr; QAction* signMessageAction = nullptr; QAction* verifyMessageAction = nullptr; + QAction* m_load_psbt_action = nullptr; QAction* aboutAction = nullptr; QAction* receiveCoinsAction = nullptr; QAction* receiveCoinsMenuAction = nullptr; @@ -270,6 +271,8 @@ public Q_SLOTS: void gotoSignMessageTab(QString addr = ""); /** Show Sign/Verify Message dialog and switch to verify message tab */ void gotoVerifyMessageTab(QString addr = ""); + /** Show load Partially Signed Bitcoin Transaction dialog */ + void gotoLoadPSBT(); /** Show open dialog */ void openClicked(); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 345c258845..7b389a48d6 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -219,11 +219,8 @@ SendCoinsDialog::~SendCoinsDialog() delete ui; } -void SendCoinsDialog::on_sendButton_clicked() +bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text) { - if(!model || !model->getOptionsModel()) - return; - QList<SendCoinsRecipient> recipients; bool valid = true; @@ -246,7 +243,7 @@ void SendCoinsDialog::on_sendButton_clicked() if(!valid || recipients.isEmpty()) { - return; + return false; } fNewRecipientAllowed = false; @@ -255,11 +252,11 @@ void SendCoinsDialog::on_sendButton_clicked() { // Unlock wallet was cancelled fNewRecipientAllowed = true; - return; + return false; } // prepare transaction for getting txFee earlier - WalletModelTransaction currentTransaction(recipients); + m_current_transaction = MakeUnique<WalletModelTransaction>(recipients); WalletModel::SendCoinsReturn prepareStatus; // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled @@ -269,22 +266,20 @@ void SendCoinsDialog::on_sendButton_clicked() updateCoinControlState(ctrl); - prepareStatus = model->prepareTransaction(currentTransaction, ctrl); + prepareStatus = model->prepareTransaction(*m_current_transaction, ctrl); // process prepareStatus and on error generate message shown to user processSendCoinsReturn(prepareStatus, - BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), m_current_transaction->getTransactionFee())); if(prepareStatus.status != WalletModel::OK) { fNewRecipientAllowed = true; - return; + return false; } - CAmount txFee = currentTransaction.getTransactionFee(); - - // Format confirmation message + CAmount txFee = m_current_transaction->getTransactionFee(); QStringList formatted; - for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients()) + for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) { // generate amount string with wallet name in case of multiwallet QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); @@ -311,72 +306,82 @@ void SendCoinsDialog::on_sendButton_clicked() formatted.append(recipientElement); } - QString questionString; if (model->wallet().privateKeysDisabled()) { - questionString.append(tr("Do you want to draft this transaction?")); + question_string.append(tr("Do you want to draft this transaction?")); } else { - questionString.append(tr("Are you sure you want to send?")); + question_string.append(tr("Are you sure you want to send?")); } - questionString.append("<br /><span style='font-size:10pt;'>"); + question_string.append("<br /><span style='font-size:10pt;'>"); if (model->wallet().privateKeysDisabled()) { - questionString.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME)); + question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME)); } else { - questionString.append(tr("Please, review your transaction.")); + question_string.append(tr("Please, review your transaction.")); } - questionString.append("</span>%1"); + question_string.append("</span>%1"); if(txFee > 0) { // append fee string if a fee is required - questionString.append("<hr /><b>"); - questionString.append(tr("Transaction fee")); - questionString.append("</b>"); + question_string.append("<hr /><b>"); + question_string.append(tr("Transaction fee")); + question_string.append("</b>"); // append transaction size - questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB): "); + question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): "); // append transaction fee value - questionString.append("<span style='color:#aa0000; font-weight:bold;'>"); - questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); - questionString.append("</span><br />"); + question_string.append("<span style='color:#aa0000; font-weight:bold;'>"); + question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); + question_string.append("</span><br />"); // append RBF message according to transaction's signalling - questionString.append("<span style='font-size:10pt; font-weight:normal;'>"); + question_string.append("<span style='font-size:10pt; font-weight:normal;'>"); if (ui->optInRBF->isChecked()) { - questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125).")); + question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125).")); } else { - questionString.append(tr("Not signalling Replace-By-Fee, BIP-125.")); + question_string.append(tr("Not signalling Replace-By-Fee, BIP-125.")); } - questionString.append("</span>"); + question_string.append("</span>"); } // add total amount in all subdivision units - questionString.append("<hr />"); - CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; + question_string.append("<hr />"); + CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee; QStringList alternativeUnits; for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { if(u != model->getOptionsModel()->getDisplayUnit()) alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); } - questionString.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount")) + question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount")) .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))); - questionString.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>") + question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>") .arg(alternativeUnits.join(" " + tr("or") + " "))); - QString informative_text; - QString detailed_text; if (formatted.size() > 1) { - questionString = questionString.arg(""); + question_string = question_string.arg(""); informative_text = tr("To review recipient list click \"Show Details...\""); detailed_text = formatted.join("\n\n"); } else { - questionString = questionString.arg("<br /><br />" + formatted.at(0)); + question_string = question_string.arg("<br /><br />" + formatted.at(0)); } + + return true; +} + +void SendCoinsDialog::on_sendButton_clicked() +{ + if(!model || !model->getOptionsModel()) + return; + + QString question_string, informative_text, detailed_text; + if (!PrepareSendText(question_string, informative_text, detailed_text)) return; + assert(m_current_transaction); + const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins"); - const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Copy PSBT to clipboard") : tr("Send"); - SendConfirmationDialog confirmationDialog(confirmation, questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this); + const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send"); + SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this); confirmationDialog.exec(); QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result()); @@ -388,7 +393,7 @@ void SendCoinsDialog::on_sendButton_clicked() bool send_failure = false; if (model->wallet().privateKeysDisabled()) { - CMutableTransaction mtx = CMutableTransaction{*(currentTransaction.getWtx())}; + CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; PartiallySignedTransaction psbtx(mtx); bool complete = false; const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete); @@ -398,15 +403,51 @@ void SendCoinsDialog::on_sendButton_clicked() CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); - Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", CClientUIInterface::MSG_INFORMATION); + 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, + tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter); + if (filename.isEmpty()) { + return; + } + std::ofstream out(filename.toLocal8Bit().data()); + 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); + } } else { // now send the prepared transaction - WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); + 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(currentTransaction.getWtx()->GetHash()); + Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash()); } else { send_failure = true; } @@ -417,10 +458,13 @@ void SendCoinsDialog::on_sendButton_clicked() coinControlUpdateLabels(); } fNewRecipientAllowed = true; + m_current_transaction.reset(); } void SendCoinsDialog::clear() { + m_current_transaction.reset(); + // Clear coin control settings CoinControlDialog::coinControl()->UnSelectAll(); ui->checkBoxCoinControlChange->setChecked(false); diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 86422c4030..36bc2a846b 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -60,6 +60,7 @@ private: Ui::SendCoinsDialog *ui; ClientModel *clientModel; WalletModel *model; + std::unique_ptr<WalletModelTransaction> m_current_transaction; bool fNewRecipientAllowed; bool fFeeMinimized; const PlatformStyle *platformStyle; @@ -69,6 +70,8 @@ private: // Additional parameter msgArg can be used via .arg(msgArg). void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg = QString()); void minimizeFeeSection(bool fMinimize); + // Format confirmation message + bool PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text); void updateFeeMinimizedLabel(); // Update the passed in CCoinControl with state from the GUI void updateCoinControlState(CCoinControl& ctrl); diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index dac3326cc4..02a9583ae9 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -163,6 +163,14 @@ void WalletFrame::gotoVerifyMessageTab(QString addr) walletView->gotoVerifyMessageTab(addr); } +void WalletFrame::gotoLoadPSBT() +{ + WalletView *walletView = currentWalletView(); + if (walletView) { + walletView->gotoLoadPSBT(); + } +} + void WalletFrame::encryptWallet(bool status) { WalletView *walletView = currentWalletView(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 20fad08b0e..d90ade5005 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -78,6 +78,9 @@ public Q_SLOTS: /** Show Sign/Verify Message dialog and switch to verify message tab */ void gotoVerifyMessageTab(QString addr = ""); + /** Load Partially Signed Bitcoin Transaction */ + void gotoLoadPSBT(); + /** Encrypt the wallet */ void encryptWallet(bool status); /** Backup the wallet */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 5b58e5fc1f..5d9b420df7 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -4,6 +4,9 @@ #include <qt/walletview.h> +#include <node/psbt.h> +#include <node/transaction.h> +#include <policy/policy.h> #include <qt/addressbookpage.h> #include <qt/askpassphrasedialog.h> #include <qt/clientmodel.h> @@ -20,6 +23,7 @@ #include <interfaces/node.h> #include <ui_interface.h> +#include <util/strencodings.h> #include <QAction> #include <QActionGroup> @@ -197,6 +201,80 @@ void WalletView::gotoVerifyMessageTab(QString addr) signVerifyMessageDialog->setAddress_VM(addr); } +void WalletView::gotoLoadPSBT() +{ + QString filename = GUIUtil::getOpenFileName(this, + tr("Load Transaction Data"), QString(), + tr("Partially Signed Transaction (*.psbt)"), nullptr); + if (filename.isEmpty()) return; + if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) { + Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR); + return; + } + std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary); + std::string data(std::istreambuf_iterator<char>{in}, {}); + + std::string error; + PartiallySignedTransaction psbtx; + if (!DecodeRawPSBT(psbtx, data, error)) { + Q_EMIT message(tr("Error"), tr("Unable to decode PSBT file") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR); + return; + } + + CMutableTransaction mtx; + bool complete = false; + PSBTAnalysis analysis = AnalyzePSBT(psbtx); + QMessageBox msgBox; + msgBox.setText("PSBT"); + switch (analysis.next) { + case PSBTRole::CREATOR: + case PSBTRole::UPDATER: + msgBox.setInformativeText("PSBT is incomplete. Copy to clipboard for manual inspection?"); + break; + case PSBTRole::SIGNER: + msgBox.setInformativeText("Transaction needs more signatures. Copy to clipboard?"); + break; + case PSBTRole::FINALIZER: + case PSBTRole::EXTRACTOR: + complete = FinalizeAndExtractPSBT(psbtx, mtx); + if (complete) { + msgBox.setInformativeText(tr("Would you like to send this transaction?")); + } else { + // The analyzer missed something, e.g. if there are final_scriptSig/final_scriptWitness + // but with invalid signatures. + msgBox.setInformativeText(tr("There was an unexpected problem processing the PSBT. Copy to clipboard for manual inspection?")); + } + } + + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + switch (msgBox.exec()) { + case QMessageBox::Yes: { + if (complete) { + std::string err_string; + CTransactionRef tx = MakeTransactionRef(mtx); + + TransactionError result = BroadcastTransaction(*clientModel->node().context(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* wait_callback */ false); + if (result == TransactionError::OK) { + Q_EMIT message(tr("Success"), tr("Broadcasted transaction sucessfully."), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL); + } else { + Q_EMIT message(tr("Error"), QString::fromStdString(err_string), CClientUIInterface::MSG_ERROR); + } + } else { + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); + Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", CClientUIInterface::MSG_INFORMATION); + return; + } + } + case QMessageBox::Cancel: + break; + default: + assert(false); + } +} + bool WalletView::handlePaymentRequest(const SendCoinsRecipient& recipient) { return sendCoinsPage->handlePaymentRequest(recipient); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 9100807c0a..11f894e7f8 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -83,6 +83,8 @@ public Q_SLOTS: void gotoSignMessageTab(QString addr = ""); /** Show Sign/Verify Message dialog and switch to verify message tab */ void gotoVerifyMessageTab(QString addr = ""); + /** Load Partially Signed Bitcoin Transaction */ + void gotoLoadPSBT(); /** Show incoming transaction notification for new transactions. diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 2f4e2916e2..063ee1697c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -40,12 +40,6 @@ #include <univalue.h> -/** Maximum fee rate for sendrawtransaction and testmempoolaccept. - * By default, a transaction with a fee rate higher than this will be rejected - * by the RPCs. This can be overridden with the maxfeerate argument. - */ -static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10}; - static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 0ed3721636..c03365199a 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -19,16 +19,13 @@ #include <validationinterface.h> #include <version.h> -#include <algorithm> #include <atomic> #include <cassert> #include <chrono> #include <cstdint> #include <iosfwd> #include <iostream> -#include <map> #include <memory> -#include <set> #include <string> #include <vector> @@ -44,19 +41,6 @@ const std::string LIMIT_TO_MESSAGE_TYPE{TO_STRING(MESSAGE_TYPE)}; const std::string LIMIT_TO_MESSAGE_TYPE; #endif -const std::map<std::string, std::set<std::string>> EXPECTED_DESERIALIZATION_EXCEPTIONS = { - {"CDataStream::read(): end of data: iostream error", {"addr", "block", "blocktxn", "cmpctblock", "feefilter", "filteradd", "filterload", "getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv", "notfound", "ping", "sendcmpct", "tx"}}, - {"CompactSize exceeds limit of type: iostream error", {"cmpctblock"}}, - {"differential value overflow: iostream error", {"getblocktxn"}}, - {"index overflowed 16 bits: iostream error", {"getblocktxn"}}, - {"index overflowed 16-bits: iostream error", {"cmpctblock"}}, - {"indexes overflowed 16 bits: iostream error", {"getblocktxn"}}, - {"non-canonical ReadCompactSize(): iostream error", {"addr", "block", "blocktxn", "cmpctblock", "filteradd", "filterload", "getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv", "notfound", "tx"}}, - {"ReadCompactSize(): size too large: iostream error", {"addr", "block", "blocktxn", "cmpctblock", "filteradd", "filterload", "getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv", "notfound", "tx"}}, - {"Superfluous witness record: iostream error", {"block", "blocktxn", "cmpctblock", "tx"}}, - {"Unknown transaction optional data: iostream error", {"block", "blocktxn", "cmpctblock", "tx"}}, -}; - const TestingSetup* g_setup; } // namespace @@ -91,13 +75,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) g_setup->m_node.peer_logic->InitializeNode(&p2p_node); try { (void)ProcessMessage(&p2p_node, random_message_type, random_bytes_data_stream, GetTimeMillis(), Params(), *g_setup->m_node.mempool, g_setup->m_node.connman.get(), g_setup->m_node.banman.get(), std::atomic<bool>{false}); - } catch (const std::ios_base::failure& e) { - const std::string exception_message{e.what()}; - const auto p = EXPECTED_DESERIALIZATION_EXCEPTIONS.find(exception_message); - if (p == EXPECTED_DESERIALIZATION_EXCEPTIONS.cend() || p->second.count(random_message_type) == 0) { - std::cout << "Unexpected exception when processing message type \"" << random_message_type << "\": " << exception_message << std::endl; - assert(false); - } + } catch (const std::ios_base::failure&) { } SyncWithValidationInterfaceQueue(); } diff --git a/src/util/system.cpp b/src/util/system.cpp index 69a7be96dc..0790ea0d48 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -141,6 +141,12 @@ bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes) return free_bytes_available >= min_disk_space + additional_bytes; } +std::streampos GetFileSize(const char* path, std::streamsize max) { + std::ifstream file(path, std::ios::binary); + file.ignore(max); + return file.gcount(); +} + /** * Interpret a string argument as a boolean. * diff --git a/src/util/system.h b/src/util/system.h index 96f51e6074..a5eea5dfab 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -63,6 +63,14 @@ void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name bool DirIsWritable(const fs::path& directory); bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0); +/** Get the size of a file by scanning it. + * + * @param[in] path The file path + * @param[in] max Stop seeking beyond this limit + * @return The file size or max + */ +std::streampos GetFileSize(const char* path, std::streamsize max = std::numeric_limits<std::streamsize>::max()); + /** Release all directory locks. This is used for unit testing only, at runtime * the global destructor will take care of the locks. */ diff --git a/src/validation.cpp b/src/validation.cpp index fb635b4202..25975e3e31 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -59,6 +59,25 @@ #define MICRO 0.000001 #define MILLI 0.001 +/** + * An extra transaction can be added to a package, as long as it only has one + * ancestor and is no larger than this. Not really any reason to make this + * configurable as it doesn't materially change DoS parameters. + */ +static const unsigned int EXTRA_DESCENDANT_TX_SIZE_LIMIT = 10000; +/** Maximum kilobytes for transactions to store for processing during reorg */ +static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20000; +/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */ +static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB +/** The pre-allocation chunk size for rev?????.dat files (since 0.8) */ +static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB +/** Time to wait (in seconds) between writing blocks/block index to disk. */ +static const unsigned int DATABASE_WRITE_INTERVAL = 60 * 60; +/** Time to wait (in seconds) between flushing chainstate to disk. */ +static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60; +/** Maximum age of our tip in seconds for us to be considered current for fee estimation */ +static const int64_t MAX_FEE_ESTIMATION_TIP_AGE = 3 * 60 * 60; + bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIndex *pb) const { // First sort by most total work, ... if (pa->nChainWork > pb->nChainWork) return false; diff --git a/src/validation.h b/src/validation.h index dbf7aa28db..91b1ba6497 100644 --- a/src/validation.h +++ b/src/validation.h @@ -60,57 +60,15 @@ static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101; static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25; /** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */ static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101; -/** - * An extra transaction can be added to a package, as long as it only has one - * ancestor and is no larger than this. Not really any reason to make this - * configurable as it doesn't materially change DoS parameters. - */ -static const unsigned int EXTRA_DESCENDANT_TX_SIZE_LIMIT = 10000; /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 336; -/** Maximum kilobytes for transactions to store for processing during reorg */ -static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20000; /** The maximum size of a blk?????.dat file (since 0.8) */ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB -/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */ -static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB -/** The pre-allocation chunk size for rev?????.dat files (since 0.8) */ -static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB - /** Maximum number of dedicated script-checking threads allowed */ static const int MAX_SCRIPTCHECK_THREADS = 15; /** -par default (number of script-checking threads, 0 = auto) */ static const int DEFAULT_SCRIPTCHECK_THREADS = 0; -/** Number of blocks that can be requested at any given time from a single peer. */ -static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16; -/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */ -static const unsigned int BLOCK_STALLING_TIMEOUT = 2; -/** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends - * less than this number, we reached its tip. Changing this value is a protocol upgrade. */ -static const unsigned int MAX_HEADERS_RESULTS = 2000; -/** Maximum depth of blocks we're willing to serve as compact blocks to peers - * when requested. For older blocks, a regular BLOCK response will be sent. */ -static const int MAX_CMPCTBLOCK_DEPTH = 5; -/** Maximum depth of blocks we're willing to respond to GETBLOCKTXN requests for. */ -static const int MAX_BLOCKTXN_DEPTH = 10; -/** Size of the "block download window": how far ahead of our current height do we fetch? - * Larger windows tolerate larger download speed differences between peer, but increase the potential - * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably - * want to make this a per-peer adaptive value at some point. */ -static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; -/** Time to wait (in seconds) between writing blocks/block index to disk. */ -static const unsigned int DATABASE_WRITE_INTERVAL = 60 * 60; -/** Time to wait (in seconds) between flushing chainstate to disk. */ -static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60; -/** Block download timeout base, expressed in millionths of the block interval (i.e. 10 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 1000000; -/** Additional block download timeout per parallel downloading peer (i.e. 5 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 500000; - static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60; -/** Maximum age of our tip in seconds for us to be considered current for fee estimation */ -static const int64_t MAX_FEE_ESTIMATION_TIP_AGE = 3 * 60 * 60; - static const bool DEFAULT_CHECKPOINTS_ENABLED = true; static const bool DEFAULT_TXINDEX = false; static const char* const DEFAULT_BLOCKFILTERINDEX = "0"; @@ -119,15 +77,21 @@ static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100; static const bool DEFAULT_PERSIST_MEMPOOL = true; /** Default for using fee filter */ static const bool DEFAULT_FEEFILTER = true; - -/** Maximum number of headers to announce when relaying blocks with headers message.*/ -static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; - -/** Maximum number of unconnecting headers announcements before DoS score */ -static const int MAX_UNCONNECTING_HEADERS = 10; - /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; +/** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ::ChainActive().Tip() will not be pruned. */ +static const unsigned int MIN_BLOCKS_TO_KEEP = 288; +static const signed int DEFAULT_CHECKBLOCKS = 6; +static const unsigned int DEFAULT_CHECKLEVEL = 3; +// Require that user allocate at least 550 MiB for block & undo files (blk???.dat and rev???.dat) +// At 1MB per block, 288 blocks = 288MB. +// Add 15% for Undo data = 331MB +// Add 20% for Orphan block rate = 397MB +// We want the low water mark after pruning to be at least 397 MB and since we prune in +// full block file chunks, we need the high water mark which triggers the prune to be +// one 128MB block file + added 15% undo data = 147MB greater for a total of 545MB +// Setting the target to >= 550 MiB will make it likely we can respect the target. +static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; struct BlockHasher { @@ -175,23 +139,6 @@ extern bool fHavePruned; extern bool fPruneMode; /** Number of MiB of block files that we're trying to stay below. */ extern uint64_t nPruneTarget; -/** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ::ChainActive().Tip() will not be pruned. */ -static const unsigned int MIN_BLOCKS_TO_KEEP = 288; -/** Minimum blocks required to signal NODE_NETWORK_LIMITED */ -static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; - -static const signed int DEFAULT_CHECKBLOCKS = 6; -static const unsigned int DEFAULT_CHECKLEVEL = 3; - -// Require that user allocate at least 550 MiB for block & undo files (blk???.dat and rev???.dat) -// At 1MB per block, 288 blocks = 288MB. -// Add 15% for Undo data = 331MB -// Add 20% for Orphan block rate = 397MB -// We want the low water mark after pruning to be at least 397 MB and since we prune in -// full block file chunks, we need the high water mark which triggers the prune to be -// one 128MB block file + added 15% undo data = 147MB greater for a total of 545MB -// Setting the target to >= 550 MiB will make it likely we can respect the target. -static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; /** * Process an incoming block. This only returns after the best known valid diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index ceb7a7d287..86e4e06673 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -724,9 +724,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request) UniValue dumpwallet(const JSONRPCRequest& request) { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - const CWallet* const pwallet = wallet.get(); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet.get(), request.fHelp)) { return NullUniValue; } @@ -750,12 +749,17 @@ UniValue dumpwallet(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet); + CWallet& wallet = *pwallet; + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(wallet); + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + wallet.BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(&wallet); fs::path filepath = request.params[0].get_str(); filepath = fs::absolute(filepath); @@ -791,9 +795,9 @@ UniValue dumpwallet(const JSONRPCRequest& request) // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); - file << strprintf("# * Best block at time of backup was %i (%s),\n", pwallet->GetLastBlockHeight(), pwallet->GetLastBlockHash().ToString()); + file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString()); int64_t block_time = 0; - CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(block_time))); + CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time))); file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time)); file << "\n"; @@ -817,8 +821,8 @@ UniValue dumpwallet(const JSONRPCRequest& request) CKey key; if (spk_man.GetKey(keyid, key)) { file << strprintf("%s %s ", EncodeSecret(key), strTime); - if (GetWalletAddressesForKey(&spk_man, pwallet, keyid, strAddr, strLabel)) { - file << strprintf("label=%s", strLabel); + if (GetWalletAddressesForKey(&spk_man, &wallet, keyid, strAddr, strLabel)) { + file << strprintf("label=%s", strLabel); } else if (keyid == seed_id) { file << "hdseed=1"; } else if (mapKeyPool.count(keyid)) { diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 9e1c1ce0be..42ae738207 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -217,16 +217,19 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Import key into wallet and call dumpwallet to create backup file. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); - auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); - LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); - spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; - spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + { + auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); + LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); + spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; + spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + AddWallet(wallet); + wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + } JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); - AddWallet(wallet); - wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + ::dumpwallet(request); RemoveWallet(wallet); } |