diff options
27 files changed, 675 insertions, 411 deletions
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 9b36319e64..edbc0911a1 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -15,3 +15,9 @@ endif if BUILD_BITCOIN_TX dist_man1_MANS+=bitcoin-tx.1 endif + +if ENABLE_WALLET +if BUILD_BITCOIN_WALLET + dist_man1_MANS+=bitcoin-wallet.1 +endif +endif diff --git a/doc/release-notes-15730.md b/doc/release-notes-15730.md new file mode 100644 index 0000000000..7a4a60b1ee --- /dev/null +++ b/doc/release-notes-15730.md @@ -0,0 +1,5 @@ +RPC changes +----------- +The RPC `getwalletinfo` response now includes the `scanning` key with an object +if there is a scanning in progress or `false` otherwise. Currently the object +has the scanning duration and progress. diff --git a/doc/release-notes.md b/doc/release-notes.md index 0de0f563b1..834c9b36dc 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -61,6 +61,15 @@ platform. Notable changes =============== +New RPCs +-------- + +- `getbalances` returns an object with all balances (`mine`, + `untrusted_pending` and `immature`). Please refer to the RPC help of + `getbalances` for details. The new RPC is intended to replace + `getunconfirmedbalance` and the balance fields in `getwalletinfo`, as well as + `getbalance`. The old calls may be removed in a future version. + Updated RPCs ------------ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index ba6523d7c2..c4c08487f3 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -140,6 +140,7 @@ QT_MOC_CPP = \ qt/moc_overviewpage.cpp \ qt/moc_peertablemodel.cpp \ qt/moc_paymentserver.cpp \ + qt/moc_qrimagewidget.cpp \ qt/moc_qvalidatedlineedit.cpp \ qt/moc_qvaluecombobox.cpp \ qt/moc_receivecoinsdialog.cpp \ @@ -220,6 +221,7 @@ BITCOIN_QT_H = \ qt/paymentserver.h \ qt/peertablemodel.h \ qt/platformstyle.h \ + qt/qrimagewidget.h \ qt/qvalidatedlineedit.h \ qt/qvaluecombobox.h \ qt/receivecoinsdialog.h \ @@ -340,6 +342,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentserver.cpp \ + qt/qrimagewidget.cpp \ qt/receivecoinsdialog.cpp \ qt/receiverequestdialog.cpp \ qt/recentrequeststablemodel.cpp \ diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index 10f51931f0..f0fcf675eb 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -203,7 +203,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector< // but that is expensive, and CheckBlock caches a block's // "checked-status" (in the CBlock?). CBlock should be able to // check its own merkle root and cache that check. - if (state.CorruptionPossible()) + if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) return READ_STATUS_FAILED; // Possible Short ID collision return READ_STATUS_CHECKBLOCK_FAILED; } diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index 61a607ef7f..23ed3ecb53 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -11,24 +11,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe { // Basic checks that don't depend on any context if (tx.vin.empty()) - return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty"); if (tx.vout.empty()) - return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-empty"); // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize"); // Check for negative or overflow output values CAmount nValueOut = 0; for (const auto& txout : tx.vout) { if (txout.nValue < 0) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-negative"); if (txout.nValue > MAX_MONEY) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-toolarge"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); } // Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock @@ -37,20 +37,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe for (const auto& txin : tx.vin) { if (!vInOutPoints.insert(txin.prevout).second) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); } } if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) - return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length"); } else { for (const auto& txin : tx.vin) if (txin.prevout.IsNull()) - return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null"); } return true; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index fbbbcfd040..4b93cae848 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -160,7 +160,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", false, + return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", strprintf("%s: inputs missing/spent", __func__)); } @@ -172,28 +172,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c // If prev is coinbase, check that it's matured if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { - return state.Invalid(false, - REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", + return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } // Check for negative or overflow input values nValueIn += coin.out.nValue; if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); } } const CAmount value_out = tx.GetValueOut(); if (nValueIn < value_out) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-in-belowout", strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out))); } // Tally transaction fees const CAmount txfee_aux = nValueIn - value_out; if (!MoneyRange(txfee_aux)) { - return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-fee-outofrange"); } txfee = txfee_aux; diff --git a/src/consensus/validation.h b/src/consensus/validation.h index f2e2c3585a..2e23f4b3a4 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -22,6 +22,78 @@ static const unsigned char REJECT_NONSTANDARD = 0x40; static const unsigned char REJECT_INSUFFICIENTFEE = 0x42; static const unsigned char REJECT_CHECKPOINT = 0x43; +/** A "reason" why something was invalid, suitable for determining whether the + * provider of the object should be banned/ignored/disconnected/etc. + * These are much more granular than the rejection codes, which may be more + * useful for some other use-cases. + */ +enum class ValidationInvalidReason { + // txn and blocks: + NONE, //!< not actually invalid + CONSENSUS, //!< invalid by consensus rules (excluding any below reasons) + /** + * Invalid by a change to consensus rules more recent than SegWit. + * Currently unused as there are no such consensus rule changes, and any download + * sources realistically need to support SegWit in order to provide useful data, + * so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork + * is uninteresting. + */ + RECENT_CONSENSUS_CHANGE, + // Only blocks (or headers): + CACHED_INVALID, //!< this object was cached as being invalid, but we don't know why + BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old + BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW + BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on + BLOCK_INVALID_PREV, //!< A block this one builds on is invalid + BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) + BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints + // Only loose txn: + TX_NOT_STANDARD, //!< didn't meet our local policy rules + TX_MISSING_INPUTS, //!< a transaction was missing some of its inputs + TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks + /** + * Transaction might be missing a witness, have a witness prior to SegWit + * activation, or witness may have been malleated (which includes + * non-standard witnesses). + */ + TX_WITNESS_MUTATED, + /** + * Tx already in mempool or conflicts with a tx in the chain + * (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold) + * TODO: Currently this is only used if the transaction already exists in the mempool or on chain, + * TODO: ATMP's fMissingInputs and a valid CValidationState being used to indicate missing inputs + */ + TX_CONFLICT, + TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits +}; + +inline bool IsTransactionReason(ValidationInvalidReason r) +{ + return r == ValidationInvalidReason::NONE || + r == ValidationInvalidReason::CONSENSUS || + r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || + r == ValidationInvalidReason::TX_NOT_STANDARD || + r == ValidationInvalidReason::TX_PREMATURE_SPEND || + r == ValidationInvalidReason::TX_MISSING_INPUTS || + r == ValidationInvalidReason::TX_WITNESS_MUTATED || + r == ValidationInvalidReason::TX_CONFLICT || + r == ValidationInvalidReason::TX_MEMPOOL_POLICY; +} + +inline bool IsBlockReason(ValidationInvalidReason r) +{ + return r == ValidationInvalidReason::NONE || + r == ValidationInvalidReason::CONSENSUS || + r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || + r == ValidationInvalidReason::CACHED_INVALID || + r == ValidationInvalidReason::BLOCK_INVALID_HEADER || + r == ValidationInvalidReason::BLOCK_MUTATED || + r == ValidationInvalidReason::BLOCK_MISSING_PREV || + r == ValidationInvalidReason::BLOCK_INVALID_PREV || + r == ValidationInvalidReason::BLOCK_TIME_FUTURE || + r == ValidationInvalidReason::BLOCK_CHECKPOINT; +} + /** Capture information about block/transaction validation */ class CValidationState { private: @@ -30,32 +102,24 @@ private: MODE_INVALID, //!< network rule violation (DoS value may be set) MODE_ERROR, //!< run-time error } mode; - int nDoS; + ValidationInvalidReason m_reason; std::string strRejectReason; unsigned int chRejectCode; - bool corruptionPossible; std::string strDebugMessage; public: - CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {} - bool DoS(int level, bool ret = false, - unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="", - bool corruptionIn=false, - const std::string &strDebugMessageIn="") { + CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE), chRejectCode(0) {} + bool Invalid(ValidationInvalidReason reasonIn, bool ret = false, + unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="", + const std::string &strDebugMessageIn="") { + m_reason = reasonIn; chRejectCode = chRejectCodeIn; strRejectReason = strRejectReasonIn; - corruptionPossible = corruptionIn; strDebugMessage = strDebugMessageIn; if (mode == MODE_ERROR) return ret; - nDoS += level; mode = MODE_INVALID; return ret; } - bool Invalid(bool ret = false, - unsigned int _chRejectCode=0, const std::string &_strRejectReason="", - const std::string &_strDebugMessage="") { - return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage); - } bool Error(const std::string& strRejectReasonIn) { if (mode == MODE_VALID) strRejectReason = strRejectReasonIn; @@ -71,19 +135,7 @@ public: bool IsError() const { return mode == MODE_ERROR; } - bool IsInvalid(int &nDoSOut) const { - if (IsInvalid()) { - nDoSOut = nDoS; - return true; - } - return false; - } - bool CorruptionPossible() const { - return corruptionPossible; - } - void SetCorruptionPossible() { - corruptionPossible = true; - } + ValidationInvalidReason GetReason() const { return m_reason; } unsigned int GetRejectCode() const { return chRejectCode; } std::string GetRejectReason() const { return strRejectReason; } std::string GetDebugMessage() const { return strDebugMessage; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 9da8364ae4..b3facdcd3a 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -351,7 +351,16 @@ struct CNodeState { TxDownloadState m_tx_download; - CNodeState(CAddress addrIn, std::string addrNameIn) : address(addrIn), name(addrNameIn) { + //! Whether this peer is an inbound connection + bool m_is_inbound; + + //! Whether this peer is a manual connection + bool m_is_manual_connection; + + CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) : + address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound), + m_is_manual_connection (is_manual) + { fCurrentlyConnected = false; nMisbehavior = 0; fShouldBan = false; @@ -747,7 +756,7 @@ void PeerLogicValidation::InitializeNode(CNode *pnode) { NodeId nodeid = pnode->GetId(); { LOCK(cs_main); - mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName))); + mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName), pnode->fInbound, pnode->m_manual_connection)); } if(!pnode->fInbound) PushNodeVersion(pnode, connman, GetTime()); @@ -959,6 +968,90 @@ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIV LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d)%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed); } +/** + * Returns true if the given validation state result may result in a peer + * banning/disconnecting us. We use this to determine which unaccepted + * transactions from a whitelisted peer that we can safely relay. + */ +static bool TxRelayMayResultInDisconnect(const CValidationState& state) +{ + assert(IsTransactionReason(state.GetReason())); + return state.GetReason() == ValidationInvalidReason::CONSENSUS; +} + +/** + * Potentially ban a node based on the contents of a CValidationState object + * + * @param[in] via_compact_block: this bool is passed in because net_processing should + * punish peers differently depending on whether the data was provided in a compact + * block message or not. If the compact block had a valid header, but contained invalid + * txs, the peer should not be punished. See BIP 152. + * + * @return Returns true if the peer was punished (probably disconnected) + * + * Changes here may need to be reflected in TxRelayMayResultInDisconnect(). + */ +static bool MaybePunishNode(NodeId nodeid, const CValidationState& state, bool via_compact_block, const std::string& message = "") { + switch (state.GetReason()) { + case ValidationInvalidReason::NONE: + break; + // The node is providing invalid data: + case ValidationInvalidReason::CONSENSUS: + case ValidationInvalidReason::BLOCK_MUTATED: + if (!via_compact_block) { + LOCK(cs_main); + Misbehaving(nodeid, 100, message); + return true; + } + break; + case ValidationInvalidReason::CACHED_INVALID: + { + LOCK(cs_main); + CNodeState *node_state = State(nodeid); + if (node_state == nullptr) { + break; + } + + // Ban outbound (but not inbound) peers if on an invalid chain. + // Exempt HB compact block peers and manual connections. + if (!via_compact_block && !node_state->m_is_inbound && !node_state->m_is_manual_connection) { + Misbehaving(nodeid, 100, message); + return true; + } + break; + } + case ValidationInvalidReason::BLOCK_INVALID_HEADER: + case ValidationInvalidReason::BLOCK_CHECKPOINT: + case ValidationInvalidReason::BLOCK_INVALID_PREV: + { + LOCK(cs_main); + Misbehaving(nodeid, 100, message); + } + return true; + // Conflicting (but not necessarily invalid) data or different policy: + case ValidationInvalidReason::BLOCK_MISSING_PREV: + { + // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) + LOCK(cs_main); + Misbehaving(nodeid, 10, message); + } + return true; + case ValidationInvalidReason::RECENT_CONSENSUS_CHANGE: + case ValidationInvalidReason::BLOCK_TIME_FUTURE: + case ValidationInvalidReason::TX_NOT_STANDARD: + case ValidationInvalidReason::TX_MISSING_INPUTS: + case ValidationInvalidReason::TX_PREMATURE_SPEND: + case ValidationInvalidReason::TX_WITNESS_MUTATED: + case ValidationInvalidReason::TX_CONFLICT: + case ValidationInvalidReason::TX_MEMPOOL_POLICY: + break; + } + if (message != "") { + LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message); + } + return false; +} + @@ -1132,14 +1225,12 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationSta const uint256 hash(block.GetHash()); std::map<uint256, std::pair<NodeId, bool>>::iterator it = mapBlockSource.find(hash); - int nDoS = 0; - if (state.IsInvalid(nDoS)) { + if (state.IsInvalid()) { // Don't send reject message with code 0 or an internal reject code. if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) { CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), hash}; State(it->second.first)->rejects.push_back(reject); - if (nDoS > 0 && it->second.second) - Misbehaving(it->second.first, nDoS); + MaybePunishNode(/*nodeid=*/ it->second.first, state, /*via_compact_block=*/ !it->second.second); } } // Check that: @@ -1489,7 +1580,7 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } -bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool punish_duplicate_invalid) +bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block) { const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); size_t nCount = headers.size(); @@ -1551,48 +1642,8 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve CValidationState state; CBlockHeader first_invalid_header; if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) { - int nDoS; - if (state.IsInvalid(nDoS)) { - LOCK(cs_main); - if (nDoS > 0) { - Misbehaving(pfrom->GetId(), nDoS, "invalid header received"); - } else { - LogPrint(BCLog::NET, "peer=%d: invalid header received\n", pfrom->GetId()); - } - if (punish_duplicate_invalid && LookupBlockIndex(first_invalid_header.GetHash())) { - // Goal: don't allow outbound peers to use up our outbound - // connection slots if they are on incompatible chains. - // - // We ask the caller to set punish_invalid appropriately based - // on the peer and the method of header delivery (compact - // blocks are allowed to be invalid in some circumstances, - // under BIP 152). - // Here, we try to detect the narrow situation that we have a - // valid block header (ie it was valid at the time the header - // was received, and hence stored in mapBlockIndex) but know the - // block is invalid, and that a peer has announced that same - // block as being on its active chain. - // Disconnect the peer in such a situation. - // - // Note: if the header that is invalid was not accepted to our - // mapBlockIndex at all, that may also be grounds for - // disconnecting the peer, as the chain they are on is likely - // to be incompatible. However, there is a circumstance where - // that does not hold: if the header's timestamp is more than - // 2 hours ahead of our current time. In that case, the header - // may become valid in the future, and we don't want to - // disconnect a peer merely for serving us one too-far-ahead - // block header, to prevent an attacker from splitting the - // network by mining a block right at the 2 hour boundary. - // - // TODO: update the DoS logic (or, rather, rewrite the - // DoS-interface between validation and net_processing) so that - // the interface is cleaner, and so that we disconnect on all the - // reasons that a peer's headers chain is incompatible - // with ours (eg block->nVersion softforks, MTP violations, - // etc), and not just the duplicate-invalid case. - pfrom->fDisconnect = true; - } + if (state.IsInvalid()) { + MaybePunishNode(pfrom->GetId(), state, via_compact_block, "invalid header received"); return false; } } @@ -1727,13 +1778,13 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se const CTransaction& orphanTx = *porphanTx; NodeId fromPeer = orphan_it->second.fromPeer; bool fMissingInputs2 = false; - // Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan - // resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get - // anyone relaying LegitTxX banned) - CValidationState stateDummy; + // Use a new CValidationState because orphans come from different peers (and we call + // MaybePunishNode based on the source peer from the orphan map, not based on the peer + // that relayed the previous transaction). + CValidationState orphan_state; if (setMisbehaving.count(fromPeer)) continue; - if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { + if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); RelayTransaction(orphanTx, connman); for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { @@ -1747,17 +1798,18 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se EraseOrphanTx(orphanHash); done = true; } else if (!fMissingInputs2) { - int nDos = 0; - if (stateDummy.IsInvalid(nDos) && nDos > 0) { + if (orphan_state.IsInvalid()) { // Punish peer that gave us an invalid orphan tx - Misbehaving(fromPeer, nDos); - setMisbehaving.insert(fromPeer); + if (MaybePunishNode(fromPeer, orphan_state, /*via_compact_block*/ false)) { + setMisbehaving.insert(fromPeer); + } LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString()); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString()); - if (!orphanTx.HasWitness() && !stateDummy.CorruptionPossible()) { + assert(IsTransactionReason(orphan_state.GetReason())); + if (!orphanTx.HasWitness() && orphan_state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. @@ -2474,7 +2526,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr recentRejects->insert(tx.GetHash()); } } else { - if (!tx.HasWitness() && !state.CorruptionPossible()) { + assert(IsTransactionReason(state.GetReason())); + if (!tx.HasWitness() && state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) { // Do not use rejection cache for witness transactions or // witness-stripped transactions, as they can have been malleated. // See https://github.com/bitcoin/bitcoin/issues/8279 for details. @@ -2493,15 +2546,13 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // to policy, allowing the node to function as a gateway for // nodes hidden behind it. // - // Never relay transactions that we would assign a non-zero DoS - // score for, as we expect peers to do the same with us in that - // case. - int nDoS = 0; - if (!state.IsInvalid(nDoS) || nDoS == 0) { + // Never relay transactions that might result in being + // disconnected (or banned). + if (state.IsInvalid() && TxRelayMayResultInDisconnect(state)) { + LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); + } else { LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); RelayTransaction(tx, connman); - } else { - LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); } } } @@ -2526,8 +2577,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // peer simply for relaying a tx that our recentRejects has caught, // regardless of false positives. - int nDoS = 0; - if (state.IsInvalid(nDoS)) + if (state.IsInvalid()) { LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), pfrom->GetId(), @@ -2536,9 +2586,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash)); } - if (nDoS > 0) { - Misbehaving(pfrom->GetId(), nDoS); - } + MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ false); } return true; } @@ -2574,14 +2622,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr const CBlockIndex *pindex = nullptr; CValidationState state; if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) { - int nDoS; - if (state.IsInvalid(nDoS)) { - if (nDoS > 0) { - LOCK(cs_main); - Misbehaving(pfrom->GetId(), nDoS, strprintf("Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId())); - } else { - LogPrint(BCLog::NET, "Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId()); - } + if (state.IsInvalid()) { + MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock"); return true; } } @@ -2731,7 +2773,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // the peer if the header turns out to be for an invalid block. // Note that if a peer tries to build on an invalid chain, that // will be detected and the peer will be banned. - return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_duplicate_invalid=*/false); + return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*via_compact_block=*/true); } if (fBlockReconstructed) { @@ -2874,12 +2916,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr ReadCompactSize(vRecv); // ignore tx count; assume it is 0. } - // Headers received via a HEADERS message should be valid, and reflect - // the chain the peer is on. If we receive a known-invalid header, - // disconnect the peer if it is using one of our outbound connection - // slots. - bool should_punish = !pfrom->fInbound && !pfrom->m_manual_connection; - return ProcessHeadersMessage(pfrom, connman, headers, chainparams, should_punish); + return ProcessHeadersMessage(pfrom, connman, headers, chainparams, /*via_compact_block=*/false); } if (strCommand == NetMsgType::BLOCK) diff --git a/src/prevector.h b/src/prevector.h index 99e5751634..ea8707389a 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -147,14 +147,14 @@ public: }; private: - size_type _size; + size_type _size = 0; union direct_or_indirect { char direct[sizeof(T) * N]; struct { size_type capacity; char* indirect; }; - } _union; + } _union = {}; T* direct_ptr(difference_type pos) { return reinterpret_cast<T*>(_union.direct) + pos; } const T* direct_ptr(difference_type pos) const { return reinterpret_cast<const T*>(_union.direct) + pos; } @@ -230,34 +230,34 @@ public: fill(item_ptr(0), first, last); } - prevector() : _size(0), _union{{}} {} + prevector() {} - explicit prevector(size_type n) : prevector() { + explicit prevector(size_type n) { resize(n); } - explicit prevector(size_type n, const T& val) : prevector() { + explicit prevector(size_type n, const T& val) { change_capacity(n); _size += n; fill(item_ptr(0), n, val); } template<typename InputIterator> - prevector(InputIterator first, InputIterator last) : prevector() { + prevector(InputIterator first, InputIterator last) { size_type n = last - first; change_capacity(n); _size += n; fill(item_ptr(0), first, last); } - prevector(const prevector<N, T, Size, Diff>& other) : prevector() { + prevector(const prevector<N, T, Size, Diff>& other) { size_type n = other.size(); change_capacity(n); _size += n; fill(item_ptr(0), other.begin(), other.end()); } - prevector(prevector<N, T, Size, Diff>&& other) : prevector() { + prevector(prevector<N, T, Size, Diff>&& other) { swap(other); } diff --git a/src/qt/forms/receiverequestdialog.ui b/src/qt/forms/receiverequestdialog.ui index dbe966b241..9f896ee3b1 100644 --- a/src/qt/forms/receiverequestdialog.ui +++ b/src/qt/forms/receiverequestdialog.ui @@ -127,7 +127,7 @@ <customwidget> <class>QRImageWidget</class> <extends>QLabel</extends> - <header>qt/receiverequestdialog.h</header> + <header>qt/qrimagewidget.h</header> </customwidget> </customwidgets> <resources/> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 736ff13a4a..d8f5594983 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -37,12 +37,6 @@ static const bool DEFAULT_SPLASHSCREEN = true; */ static const int TOOLTIP_WRAP_THRESHOLD = 80; -/* Maximum allowed URI length */ -static const int MAX_URI_LENGTH = 255; - -/* QRCodeDialog -- size of exported QR Code image */ -#define QR_IMAGE_SIZE 300 - /* Number of frames in spinner animation */ #define SPINNER_FRAMES 36 diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp new file mode 100644 index 0000000000..bf1baf5470 --- /dev/null +++ b/src/qt/qrimagewidget.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2011-2018 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 <qt/qrimagewidget.h> + +#include <qt/guiutil.h> + +#include <QApplication> +#include <QClipboard> +#include <QDrag> +#include <QMenu> +#include <QMimeData> +#include <QMouseEvent> +#include <QPainter> + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> /* for USE_QRCODE */ +#endif + +#ifdef USE_QRCODE +#include <qrencode.h> +#endif + +QRImageWidget::QRImageWidget(QWidget *parent): + QLabel(parent), contextMenu(nullptr) +{ + contextMenu = new QMenu(this); + QAction *saveImageAction = new QAction(tr("&Save Image..."), this); + connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); + contextMenu->addAction(saveImageAction); + QAction *copyImageAction = new QAction(tr("&Copy Image"), this); + connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); + contextMenu->addAction(copyImageAction); +} + +bool QRImageWidget::setQR(const QString& data, const QString& text) +{ +#ifdef USE_QRCODE + setText(""); + if (data.isEmpty()) return false; + + // limit length + if (data.length() > MAX_URI_LENGTH) { + setText(tr("Resulting URI too long, try to reduce the text for label / message.")); + return false; + } + + QRcode *code = QRcode_encodeString(data.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + + if (!code) { + setText(tr("Error encoding URI into QR Code.")); + return false; + } + + QImage qrImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); + qrImage.fill(0xffffff); + unsigned char *p = code->data; + for (int y = 0; y < code->width; ++y) { + for (int x = 0; x < code->width; ++x) { + qrImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); + ++p; + } + } + QRcode_free(code); + + QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE + (text.isEmpty() ? 0 : 20), QImage::Format_RGB32); + qrAddrImage.fill(0xffffff); + QPainter painter(&qrAddrImage); + painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); + + if (!text.isEmpty()) { + QFont font = GUIUtil::fixedPitchFont(); + QRect paddedRect = qrAddrImage.rect(); + + // calculate ideal font size + qreal font_size = GUIUtil::calculateIdealFontSize(paddedRect.width() - 20, text, font); + font.setPointSizeF(font_size); + + painter.setFont(font); + paddedRect.setHeight(QR_IMAGE_SIZE+12); + painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, text); + } + + painter.end(); + setPixmap(QPixmap::fromImage(qrAddrImage)); + + return true; +#else + setText(tr("QR code support not available.")); + return false; +#endif +} + +QImage QRImageWidget::exportImage() +{ + if(!pixmap()) + return QImage(); + return pixmap()->toImage(); +} + +void QRImageWidget::mousePressEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton && pixmap()) + { + event->accept(); + QMimeData *mimeData = new QMimeData; + mimeData->setImageData(exportImage()); + + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->exec(); + } else { + QLabel::mousePressEvent(event); + } +} + +void QRImageWidget::saveImage() +{ + if(!pixmap()) + return; + QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); + if (!fn.isEmpty()) + { + exportImage().save(fn); + } +} + +void QRImageWidget::copyImage() +{ + if(!pixmap()) + return; + QApplication::clipboard()->setImage(exportImage()); +} + +void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) +{ + if(!pixmap()) + return; + contextMenu->exec(event->globalPos()); +} diff --git a/src/qt/qrimagewidget.h b/src/qt/qrimagewidget.h new file mode 100644 index 0000000000..2a219ac101 --- /dev/null +++ b/src/qt/qrimagewidget.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011-2018 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_QT_QRIMAGEWIDGET_H +#define BITCOIN_QT_QRIMAGEWIDGET_H + +#include <QImage> +#include <QLabel> + +/* Maximum allowed URI length */ +static const int MAX_URI_LENGTH = 255; + +/* Size of exported QR Code image */ +static const int QR_IMAGE_SIZE = 300; + +QT_BEGIN_NAMESPACE +class QMenu; +QT_END_NAMESPACE + +/* Label widget for QR code. This image can be dragged, dropped, copied and saved + * to disk. + */ +class QRImageWidget : public QLabel +{ + Q_OBJECT + +public: + explicit QRImageWidget(QWidget *parent = nullptr); + bool setQR(const QString& data, const QString& text = ""); + QImage exportImage(); + +public Q_SLOTS: + void saveImage(); + void copyImage(); + +protected: + virtual void mousePressEvent(QMouseEvent *event); + virtual void contextMenuEvent(QContextMenuEvent *event); + +private: + QMenu *contextMenu; +}; + +#endif // BITCOIN_QT_QRIMAGEWIDGET_H diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index f5b30cf6d2..20b29145a0 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -6,85 +6,17 @@ #include <qt/forms/ui_receiverequestdialog.h> #include <qt/bitcoinunits.h> -#include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> +#include <qt/qrimagewidget.h> #include <QClipboard> -#include <QDrag> -#include <QMenu> -#include <QMimeData> -#include <QMouseEvent> #include <QPixmap> #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> /* for USE_QRCODE */ #endif -#ifdef USE_QRCODE -#include <qrencode.h> -#endif - -QRImageWidget::QRImageWidget(QWidget *parent): - QLabel(parent), contextMenu(nullptr) -{ - contextMenu = new QMenu(this); - QAction *saveImageAction = new QAction(tr("&Save Image..."), this); - connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); - contextMenu->addAction(saveImageAction); - QAction *copyImageAction = new QAction(tr("&Copy Image"), this); - connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); - contextMenu->addAction(copyImageAction); -} - -QImage QRImageWidget::exportImage() -{ - if(!pixmap()) - return QImage(); - return pixmap()->toImage(); -} - -void QRImageWidget::mousePressEvent(QMouseEvent *event) -{ - if(event->button() == Qt::LeftButton && pixmap()) - { - event->accept(); - QMimeData *mimeData = new QMimeData; - mimeData->setImageData(exportImage()); - - QDrag *drag = new QDrag(this); - drag->setMimeData(mimeData); - drag->exec(); - } else { - QLabel::mousePressEvent(event); - } -} - -void QRImageWidget::saveImage() -{ - if(!pixmap()) - return; - QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); - if (!fn.isEmpty()) - { - exportImage().save(fn); - } -} - -void QRImageWidget::copyImage() -{ - if(!pixmap()) - return; - QApplication::clipboard()->setImage(exportImage()); -} - -void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) -{ - if(!pixmap()) - return; - contextMenu->exec(event->globalPos()); -} - ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), @@ -150,55 +82,9 @@ void ReceiveRequestDialog::update() } ui->outUri->setText(html); -#ifdef USE_QRCODE - ui->lblQRCode->setText(""); - if(!uri.isEmpty()) - { - // limit URI length - if (uri.length() > MAX_URI_LENGTH) - { - ui->lblQRCode->setText(tr("Resulting URI too long, try to reduce the text for label / message.")); - } else { - QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); - if (!code) - { - ui->lblQRCode->setText(tr("Error encoding URI into QR Code.")); - return; - } - QImage qrImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); - qrImage.fill(0xffffff); - unsigned char *p = code->data; - for (int y = 0; y < code->width; y++) - { - for (int x = 0; x < code->width; x++) - { - qrImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); - p++; - } - } - QRcode_free(code); - - QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE+20, QImage::Format_RGB32); - qrAddrImage.fill(0xffffff); - QPainter painter(&qrAddrImage); - painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); - QFont font = GUIUtil::fixedPitchFont(); - QRect paddedRect = qrAddrImage.rect(); - - // calculate ideal font size - qreal font_size = GUIUtil::calculateIdealFontSize(paddedRect.width() - 20, info.address, font); - font.setPointSizeF(font_size); - - painter.setFont(font); - paddedRect.setHeight(QR_IMAGE_SIZE+12); - painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, info.address); - painter.end(); - - ui->lblQRCode->setPixmap(QPixmap::fromImage(qrAddrImage)); - ui->btnSaveAs->setEnabled(true); - } + if (ui->lblQRCode->setQR(uri, info.address)) { + ui->btnSaveAs->setEnabled(true); } -#endif } void ReceiveRequestDialog::on_btnCopyURI_clicked() diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index dd28fd73c8..a6e1a2af16 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -8,41 +8,11 @@ #include <qt/walletmodel.h> #include <QDialog> -#include <QImage> -#include <QLabel> -#include <QPainter> namespace Ui { class ReceiveRequestDialog; } -QT_BEGIN_NAMESPACE -class QMenu; -QT_END_NAMESPACE - -/* Label widget for QR code. This image can be dragged, dropped, copied and saved - * to disk. - */ -class QRImageWidget : public QLabel -{ - Q_OBJECT - -public: - explicit QRImageWidget(QWidget *parent = nullptr); - QImage exportImage(); - -public Q_SLOTS: - void saveImage(); - void copyImage(); - -protected: - virtual void mousePressEvent(QMouseEvent *event); - virtual void contextMenuEvent(QContextMenuEvent *event); - -private: - QMenu *contextMenu; -}; - class ReceiveRequestDialog : public QDialog { Q_OBJECT diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 331c340b74..26ae7be202 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -52,10 +52,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) // Check that the validation state reflects the unsuccessful attempt. BOOST_CHECK(state.IsInvalid()); BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase"); - - int nDoS; - BOOST_CHECK_EQUAL(state.IsInvalid(nDoS), true); - BOOST_CHECK_EQUAL(nDoS, 100); + BOOST_CHECK(state.GetReason() == ValidationInvalidReason::CONSENSUS); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index e94574b7ba..ea6c36a3fb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -588,28 +588,28 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) - return state.DoS(100, false, REJECT_INVALID, "coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "coinbase"); // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; if (fRequireStandard && !IsStandardTx(tx, reason)) - return state.DoS(0, false, REJECT_NONSTANDARD, reason); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, reason); // Do not work on transactions that are too small. // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. // Transactions smaller than this are not relayed to reduce unnecessary malloc overhead. if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE) - return state.DoS(0, false, REJECT_NONSTANDARD, "tx-size-small"); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "tx-size-small"); // 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(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) - return state.DoS(0, false, REJECT_NONSTANDARD, "non-final"); + return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-final"); // is it already in the memory pool? if (pool.exists(hash)) { - return state.Invalid(false, REJECT_DUPLICATE, "txn-already-in-mempool"); + return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-in-mempool"); } // Check for conflicts with in-memory transactions @@ -645,7 +645,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } } if (fReplacementOptOut) { - return state.Invalid(false, REJECT_DUPLICATE, "txn-mempool-conflict"); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_DUPLICATE, "txn-mempool-conflict"); } setConflicts.insert(ptxConflicting->GetHash()); @@ -675,7 +675,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool for (size_t out = 0; out < tx.vout.size(); out++) { // Optimistically just do efficient check of cache for outputs if (pcoinsTip->HaveCoinInCache(COutPoint(hash, out))) { - return state.Invalid(false, REJECT_DUPLICATE, "txn-already-known"); + return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known"); } } // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet @@ -698,7 +698,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Must keep pool.cs for this unless we change CheckSequenceLocks to take a // CoinsViewCache instead of create its own if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) - return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); + return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final"); CAmount nFees = 0; if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) { @@ -707,11 +707,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Check for non-standard pay-to-script-hash in inputs if (fRequireStandard && !AreInputsStandard(tx, view)) - return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); // Check for non-standard witness in P2WSH if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view)) - return state.DoS(0, false, REJECT_NONSTANDARD, "bad-witness-nonstandard", true); + return state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, REJECT_NONSTANDARD, "bad-witness-nonstandard"); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS); @@ -734,27 +734,22 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool fSpendsCoinbase, nSigOpsCost, lp); unsigned int nSize = entry.GetTxSize(); - // Check that the transaction doesn't have an excessive number of - // sigops, making it impossible to mine. Since the coinbase transaction - // itself can contain sigops MAX_STANDARD_TX_SIGOPS is less than - // MAX_BLOCK_SIGOPS; we still consider this an invalid rather than - // merely non-standard transaction. if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) - return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", strprintf("%d", nSigOpsCost)); CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize); if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) { - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nModifiedFees, mempoolRejectFee)); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", nModifiedFees, mempoolRejectFee)); } // No transactions are allowed below minRelayTxFee except from disconnected blocks if (!bypass_limits && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) { - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", false, strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize))); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize))); } if (nAbsurdFee && nFees > nAbsurdFee) - return state.Invalid(false, + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_HIGHFEE, "absurdly-high-fee", strprintf("%d > %d", nFees, nAbsurdFee)); @@ -766,7 +761,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { - return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); } // A transaction that spends outputs that would be replaced by it is invalid. Now @@ -778,8 +773,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool const uint256 &hashAncestor = ancestorIt->GetTx().GetHash(); if (setConflicts.count(hashAncestor)) { - return state.DoS(10, false, - REJECT_INVALID, "bad-txns-spends-conflicting-tx", false, + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-spends-conflicting-tx", strprintf("%s spends conflicting transaction %s", hash.ToString(), hashAncestor.ToString())); @@ -821,8 +815,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); if (newFeeRate <= oldFeeRate) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee", strprintf("rejecting replacement %s; new feerate %s <= old feerate %s", hash.ToString(), newFeeRate.ToString(), @@ -850,8 +843,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool nConflictingSize += it->GetTxSize(); } } else { - return state.DoS(0, false, - REJECT_NONSTANDARD, "too many potential replacements", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too many potential replacements", strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n", hash.ToString(), nConflictingCount, @@ -870,8 +862,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // it's cheaper to just check if the new input refers to a // tx that's in the mempool. if (pool.exists(tx.vin[j].prevout.hash)) { - return state.DoS(0, false, - REJECT_NONSTANDARD, "replacement-adds-unconfirmed", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "replacement-adds-unconfirmed", strprintf("replacement %s adds unconfirmed input, idx %d", hash.ToString(), j)); } @@ -883,8 +874,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // transactions would not be paid for. if (nModifiedFees < nConflictingFees) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee", strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s", hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees))); } @@ -894,8 +884,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool CAmount nDeltaFees = nModifiedFees - nConflictingFees; if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize)) { - return state.DoS(0, false, - REJECT_INSUFFICIENTFEE, "insufficient fee", false, + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee", strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s", hash.ToString(), FormatMoney(nDeltaFees), @@ -916,8 +905,10 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { // Only the witness is missing, so the transaction itself may be fine. - state.SetCorruptionPossible(); + state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, + state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); } + assert(IsTransactionReason(state.GetReason())); return false; // state filled in by CheckInputs } @@ -974,7 +965,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!bypass_limits) { LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60); if (!pool.exists(hash)) - return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool full"); + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full"); } } @@ -1312,7 +1303,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c } void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { - if (!state.CorruptionPossible()) { + if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_failed_blocks.insert(pindex); setDirtyBlockIndex.insert(pindex); @@ -1380,6 +1371,9 @@ void InitScriptExecutionCache() { * which are matched. This is useful for checking blocks where we will likely never need the cache * entry again. * + * Note that we may set state.reason to NOT_STANDARD for extra soft-fork flags in flags, block-checking + * callers should probably reset it to CONSENSUS in such cases. + * * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp */ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -1435,22 +1429,26 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi // Check whether the failure was caused by a // non-mandatory script verification check, such as // non-standard DER encodings or non-null dummy - // arguments; if so, don't trigger DoS protection to - // avoid splitting the network between upgraded and - // non-upgraded nodes. + // arguments; if so, ensure we return NOT_STANDARD + // instead of CONSENSUS to avoid downstream users + // splitting the network between upgraded and + // non-upgraded nodes by banning CONSENSUS-failing + // data providers. CScriptCheck check2(coin.out, tx, i, flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); if (check2()) - return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); + return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } - // Failures of other flags indicate a transaction that is - // invalid in new blocks, e.g. an invalid P2SH. We DoS ban - // such nodes as they are not following the protocol. That - // said during an upgrade careful thought should be taken - // as to the correct behavior - we may want to continue - // peering with non-upgraded nodes even after soft-fork - // super-majority signaling has occurred. - return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); + // MANDATORY flag failures correspond to + // ValidationInvalidReason::CONSENSUS. Because CONSENSUS + // failures are the most serious case of validation + // failures, we may need to consider using + // RECENT_CONSENSUS_CHANGE for any script failure that + // could be due to non-upgraded nodes which we may want to + // support, to avoid splitting the network (but this + // depends on the details of how net_processing handles + // such errors). + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); } } @@ -1810,7 +1808,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // re-enforce that rule here (at least until we make it impossible for // GetAdjustedTime() to go backward). if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) { - if (state.CorruptionPossible()) { + if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been // corrupted, so this should be impossible unless we're having hardware // problems. @@ -1945,7 +1943,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl for (const auto& tx : block.vtx) { for (size_t o = 0; o < tx->vout.size(); o++) { if (view.HaveCoin(COutPoint(tx->GetHash(), o))) { - return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): tried to overwrite transaction"), REJECT_INVALID, "bad-txns-BIP30"); } } @@ -1985,11 +1983,19 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { CAmount txfee = 0; if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) { + if (!IsBlockReason(state.GetReason())) { + // CheckTxInputs may return MISSING_INPUTS or + // PREMATURE_SPEND but we can't return that, as it's not + // defined for a block, so we reset the reason flag to + // CONSENSUS here. + state.Invalid(ValidationInvalidReason::CONSENSUS, false, + state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); + } return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); } nFees += txfee; if (!MoneyRange(nFees)) { - return state.DoS(100, error("%s: accumulated fee in the block out of range.", __func__), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: accumulated fee in the block out of range.", __func__), REJECT_INVALID, "bad-txns-accumulated-fee-outofrange"); } @@ -2002,7 +2008,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) { - return state.DoS(100, error("%s: contains a non-BIP68-final transaction", __func__), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: contains a non-BIP68-final transaction", __func__), REJECT_INVALID, "bad-txns-nonfinal"); } } @@ -2013,7 +2019,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // * witness (when witness enabled in flags and excludes coinbase) nSigOpsCost += GetTransactionSigOpCost(tx, view, flags); if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) - return state.DoS(100, error("ConnectBlock(): too many sigops"), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): too many sigops"), REJECT_INVALID, "bad-blk-sigops"); txdata.emplace_back(tx); @@ -2021,9 +2027,20 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ - if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) + if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { + if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) { + // CheckInputs may return NOT_STANDARD for extra flags we passed, + // but we can't return that, as it's not defined for a block, so + // we reset the reason flag to CONSENSUS here. + // In the event of a future soft-fork, we may need to + // consider whether rewriting to CONSENSUS or + // RECENT_CONSENSUS_CHANGE would be more appropriate. + state.Invalid(ValidationInvalidReason::CONSENSUS, false, + state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); + } return error("ConnectBlock(): CheckInputs on %s failed with %s", tx.GetHash().ToString(), FormatStateMessage(state)); + } control.Add(vChecks); } @@ -2038,13 +2055,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()); if (block.vtx[0]->GetValueOut() > blockReward) - return state.DoS(100, + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)", block.vtx[0]->GetValueOut(), blockReward), REJECT_INVALID, "bad-cb-amount"); if (!control.Wait()) - return state.DoS(100, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed"); int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2; LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); @@ -2572,7 +2589,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { if (state.IsInvalid()) { // The block violates a consensus rule. - if (!state.CorruptionPossible()) { + if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { InvalidChainFound(vpindexToConnect.front()); } state = CValidationState(); @@ -3070,7 +3087,7 @@ static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, { // Check proof of work matches claimed amount if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) - return state.DoS(50, false, REJECT_INVALID, "high-hash", false, "proof of work failed"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "high-hash", "proof of work failed"); return true; } @@ -3092,13 +3109,13 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P bool mutated; uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); if (block.hashMerkleRoot != hashMerkleRoot2) - return state.DoS(100, false, REJECT_INVALID, "bad-txnmrklroot", true, "hashMerkleRoot mismatch"); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txnmrklroot", "hashMerkleRoot mismatch"); // Check for merkle tree malleability (CVE-2012-2459): repeating sequences // of transactions in a block without affecting the merkle root of a block, // while still invalidating it. if (mutated) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-duplicate", true, "duplicate transaction"); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txns-duplicate", "duplicate transaction"); } // All potential-corruption validation must be done before we do any @@ -3109,19 +3126,19 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P // Size limits if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false, "size limits failed"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-length", "size limits failed"); // First transaction must be coinbase, the rest must not be if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) - return state.DoS(100, false, REJECT_INVALID, "bad-cb-missing", false, "first tx is not coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-missing", "first tx is not coinbase"); for (unsigned int i = 1; i < block.vtx.size(); i++) if (block.vtx[i]->IsCoinBase()) - return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase"); // Check transactions for (const auto& tx : block.vtx) if (!CheckTransaction(*tx, state, true)) - return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(), + return state.Invalid(state.GetReason(), false, state.GetRejectCode(), state.GetRejectReason(), strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); unsigned int nSigOps = 0; @@ -3130,7 +3147,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P nSigOps += GetLegacySigOpCount(*tx); } if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) - return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", false, "out-of-bounds SigOpCount"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-sigops", "out-of-bounds SigOpCount"); if (fCheckPOW && fCheckMerkleRoot) block.fChecked = true; @@ -3239,7 +3256,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta // Check proof of work const Consensus::Params& consensusParams = params.GetConsensus(); if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) - return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "bad-diffbits", "incorrect proof of work"); // Check against checkpoints if (fCheckpointsEnabled) { @@ -3248,23 +3265,23 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta // MapBlockIndex. CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) - return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); + return state.Invalid(ValidationInvalidReason::BLOCK_CHECKPOINT, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); } // Check timestamp against prev if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) - return state.Invalid(false, REJECT_INVALID, "time-too-old", "block's timestamp is too early"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "time-too-old", "block's timestamp is too early"); // Check timestamp if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) - return state.Invalid(false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); + return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); // Reject outdated version blocks when 95% (75% on testnet) of the network has upgraded: // check for version 2, 3 and 4 upgrades if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) || (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) || (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) - return state.Invalid(false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion), + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); return true; @@ -3294,7 +3311,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // Check that all transactions are finalized for (const auto& tx : block.vtx) { if (!IsFinalTx(*tx, nHeight, nLockTimeCutoff)) { - return state.DoS(10, false, REJECT_INVALID, "bad-txns-nonfinal", false, "non-final transaction"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-nonfinal", "non-final transaction"); } } @@ -3304,7 +3321,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || !std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) { - return state.DoS(100, false, REJECT_INVALID, "bad-cb-height", false, "block height mismatch in coinbase"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-height", "block height mismatch in coinbase"); } } @@ -3326,11 +3343,11 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // already does not permit it, it is impossible to trigger in the // witness tree. if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) { - return state.DoS(100, false, REJECT_INVALID, "bad-witness-nonce-size", true, strprintf("%s : invalid witness reserved value size", __func__)); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__)); } CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin()); if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { - return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__)); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__)); } fHaveWitness = true; } @@ -3340,7 +3357,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c if (!fHaveWitness) { for (const auto& tx : block.vtx) { if (tx->HasWitness()) { - return state.DoS(100, false, REJECT_INVALID, "unexpected-witness", true, strprintf("%s : unexpected witness data found", __func__)); + return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__)); } } } @@ -3352,7 +3369,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // the block hash, so we couldn't mark the block as permanently // failed). if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) { - return state.DoS(100, false, REJECT_INVALID, "bad-blk-weight", false, strprintf("%s : weight limit failed", __func__)); + return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-weight", strprintf("%s : weight limit failed", __func__)); } return true; @@ -3372,7 +3389,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (ppindex) *ppindex = pindex; if (pindex->nStatus & BLOCK_FAILED_MASK) - return state.Invalid(error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate"); + return state.Invalid(ValidationInvalidReason::CACHED_INVALID, error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate"); return true; } @@ -3383,10 +3400,10 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& CBlockIndex* pindexPrev = nullptr; BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); if (mi == mapBlockIndex.end()) - return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); + return state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); pindexPrev = (*mi).second; if (pindexPrev->nStatus & BLOCK_FAILED_MASK) - return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); @@ -3423,7 +3440,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& setDirtyBlockIndex.insert(invalid_walk); invalid_walk = invalid_walk->pprev; } - return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); + return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); } } } @@ -3527,7 +3544,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali if (!CheckBlock(block, state, chainparams.GetConsensus()) || !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { - if (state.IsInvalid() && !state.CorruptionPossible()) { + assert(IsBlockReason(state.GetReason())); + if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(pindex); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5a22508c4b..626cdccfee 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,7 +9,6 @@ #include <core_io.h> #include <init.h> #include <interfaces/chain.h> -#include <validation.h> #include <key_io.h> #include <net.h> #include <node/transaction.h> @@ -27,10 +26,11 @@ #include <timedata.h> #include <util/bip32.h> #include <util/fees.h> -#include <util/system.h> #include <util/moneystr.h> +#include <util/system.h> #include <util/url.h> #include <util/validation.h> +#include <validation.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/psbtwallet.h> @@ -70,14 +70,14 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques return wallets.size() == 1 || (request.fHelp && wallets.size() > 0) ? wallets[0] : nullptr; } -std::string HelpRequiringPassphrase(CWallet * const pwallet) +std::string HelpRequiringPassphrase(const CWallet* pwallet) { return pwallet && pwallet->IsCrypted() ? "\nRequires wallet passphrase to be set with walletpassphrase call." : ""; } -bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) +bool EnsureWalletIsAvailable(const CWallet* pwallet, bool avoidException) { if (pwallet) return true; if (avoidException) return false; @@ -89,7 +89,7 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); } -void EnsureWalletIsUnlocked(CWallet * const pwallet) +void EnsureWalletIsUnlocked(const CWallet* pwallet) { if (pwallet->IsLocked()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); @@ -785,7 +785,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( RPCHelpMan{"getunconfirmedbalance", - "Returns the server's total unconfirmed balance\n", + "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n", {}, RPCResults{}, RPCExamples{""}, @@ -2373,6 +2373,68 @@ static UniValue settxfee(const JSONRPCRequest& request) return true; } +static UniValue getbalances(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const rpc_wallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(rpc_wallet.get(), request.fHelp)) { + return NullUniValue; + } + CWallet& wallet = *rpc_wallet; + + const RPCHelpMan help{ + "getbalances", + "Returns an object with all balances in " + CURRENCY_UNIT + ".\n", + {}, + RPCResult{ + "{\n" + " \"mine\": { (object) balances from outputs that the wallet can sign\n" + " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n" + " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n" + " \"immature\": xxx (numeric) balance from immature coinbase outputs\n" + " },\n" + " \"watchonly\": { (object) watchonly balances (not present if wallet does not watch anything)\n" + " \"trusted\": xxx (numeric) trusted balance (outputs created by the wallet or confirmed outputs)\n" + " \"untrusted_pending\": xxx (numeric) untrusted pending balance (outputs created by others that are in the mempool)\n" + " \"immature\": xxx (numeric) balance from immature coinbase outputs\n" + " },\n" + "}\n"}, + RPCExamples{ + HelpExampleCli("getbalances", "") + + HelpExampleRpc("getbalances", "")}, + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } + + // 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 = wallet.chain().lock(); + LOCK(wallet.cs_wallet); + + UniValue obj(UniValue::VOBJ); + + const auto bal = wallet.GetBalance(); + UniValue balances{UniValue::VOBJ}; + { + UniValue balances_mine{UniValue::VOBJ}; + balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted)); + balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending)); + balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature)); + balances.pushKV("mine", balances_mine); + } + if (wallet.HaveWatchOnly()) { + UniValue balances_watchonly{UniValue::VOBJ}; + balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted)); + balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending)); + balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature)); + balances.pushKV("watchonly", balances_watchonly); + } + return balances; +} + static UniValue getwalletinfo(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -2382,18 +2444,16 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - RPCHelpMan{"getwalletinfo", + const RPCHelpMan help{"getwalletinfo", "Returns an object containing various wallet state info.\n", {}, RPCResult{ "{\n" " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" - " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" + " \"balance\": xxxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.trusted\n" + " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n" + " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" @@ -2402,13 +2462,22 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" + " \"scanning\": (json object) current scanning details, or false if no scan is in progress\n" + " {\n" + " \"duration\" : xxxx (numeric) elapsed seconds since scan start\n" + " \"progress\" : x.xxxx, (numeric) scanning progress percentage [0.0, 1.0]\n" + " }\n" "}\n" }, RPCExamples{ HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") }, - }.ToString()); + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } // 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 @@ -2441,6 +2510,14 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("hdseedid", seed_id.GetHex()); } obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + if (pwallet->IsScanning()) { + UniValue scanning(UniValue::VOBJ); + scanning.pushKV("duration", pwallet->ScanningDuration() / 1000); + scanning.pushKV("progress", pwallet->ScanningProgress()); + obj.pushKV("scanning", scanning); + } else { + obj.pushKV("scanning", false); + } return obj; } @@ -4073,6 +4150,7 @@ static const CRPCCommand commands[] = { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} }, { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} }, + { "wallet", "getbalances", &getbalances, {} }, { "wallet", "getwalletinfo", &getwalletinfo, {} }, { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} }, { "wallet", "importmulti", &importmulti, {"requests","options"} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 7cf607ccc7..90617472cc 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -31,9 +31,9 @@ void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique */ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request); -std::string HelpRequiringPassphrase(CWallet *); -void EnsureWalletIsUnlocked(CWallet *); -bool EnsureWalletIsAvailable(CWallet *, bool avoidException); +std::string HelpRequiringPassphrase(const CWallet*); +void EnsureWalletIsUnlocked(const CWallet*); +bool EnsureWalletIsAvailable(const CWallet*, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5e32f2154b..ac33d07ec0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1786,8 +1786,9 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc } double progress_current = progress_begin; while (block_height && !fAbortRescan && !chain().shutdownRequested()) { + m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 510f4b8c04..30ea51c8a2 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -596,6 +596,8 @@ class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notific private: std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver + std::atomic<int64_t> m_scanning_start{0}; + std::atomic<double> m_scanning_progress{0}; std::mutex mutexScanning; friend class WalletRescanReserver; @@ -820,6 +822,8 @@ public: void AbortRescan() { fAbortRescan = true; } bool IsAbortingRescan() { return fAbortRescan; } bool IsScanning() { return fScanningWallet; } + int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; } + double ScanningProgress() const { return fScanningWallet ? (double) m_scanning_progress : 0; } /** * keystore implementation @@ -1241,6 +1245,8 @@ public: if (m_wallet->fScanningWallet) { return false; } + m_wallet->m_scanning_start = GetTimeMillis(); + m_wallet->m_scanning_progress = 0; m_wallet->fScanningWallet = true; m_could_reserve = true; return true; diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index d262dae5aa..454eb583f7 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -58,7 +58,7 @@ class BadTxTemplate: class OutputMissing(BadTxTemplate): reject_reason = "bad-txns-vout-empty" - expect_disconnect = False + expect_disconnect = True def get_tx(self): tx = CTransaction() @@ -69,7 +69,7 @@ class OutputMissing(BadTxTemplate): class InputMissing(BadTxTemplate): reject_reason = "bad-txns-vin-empty" - expect_disconnect = False + expect_disconnect = True # We use a blank transaction here to make sure # it is interpreted as a non-witness transaction. diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 72eb4f804f..3ad83cd2b3 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -281,7 +281,7 @@ class FullBlockTest(BitcoinTestFramework): self.log.info("Reject a block spending an immature coinbase.") self.move_tip(15) b20 = self.next_block(20, spend=out[7]) - self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') + self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase', reconnect=True) # Attempt to spend a coinbase at depth too low (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) @@ -294,7 +294,7 @@ class FullBlockTest(BitcoinTestFramework): self.send_blocks([b21], False) b22 = self.next_block(22, spend=out[5]) - self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') + self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase', reconnect=True) # Create a block on either side of MAX_BLOCK_BASE_SIZE and make sure its accepted/rejected # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) @@ -616,7 +616,7 @@ class FullBlockTest(BitcoinTestFramework): while b47.sha256 < target: b47.nNonce += 1 b47.rehash() - self.send_blocks([b47], False, force_send=True, reject_reason='high-hash') + self.send_blocks([b47], False, force_send=True, reject_reason='high-hash', reconnect=True) self.log.info("Reject a block with a timestamp >2 hours in the future") self.move_tip(44) @@ -667,7 +667,7 @@ class FullBlockTest(BitcoinTestFramework): b54 = self.next_block(54, spend=out[15]) b54.nTime = b35.nTime - 1 b54.solve() - self.send_blocks([b54], False, force_send=True, reject_reason='time-too-old') + self.send_blocks([b54], False, force_send=True, reject_reason='time-too-old', reconnect=True) # valid timestamp self.move_tip(53) @@ -813,7 +813,7 @@ class FullBlockTest(BitcoinTestFramework): assert tx.vin[0].nSequence < 0xffffffff tx.calc_sha256() b62 = self.update_block(62, [tx]) - self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal') + self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal', reconnect=True) # Test a non-final coinbase is also rejected # @@ -827,7 +827,7 @@ class FullBlockTest(BitcoinTestFramework): b63.vtx[0].vin[0].nSequence = 0xDEADBEEF b63.vtx[0].rehash() b63 = self.update_block(63, []) - self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal') + self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal', reconnect=True) # This checks that a block with a bloated VARINT between the block_header and the array of tx such that # the block is > MAX_BLOCK_BASE_SIZE with the bloated varint, but <= MAX_BLOCK_BASE_SIZE without the bloated varint, @@ -1241,7 +1241,7 @@ class FullBlockTest(BitcoinTestFramework): self.log.info("Reject a block with an invalid block header version") b_v1 = self.next_block('b_v1', version=1) - self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)') + self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)', reconnect=True) self.move_tip(chain1_tip + 2) b_cb34 = self.next_block('b_cb34', version=4) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 8b2006a05c..b9d1082ddc 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -80,10 +80,14 @@ class TestNode(): # For those callers that need more flexibility, they can just set the args property directly. # Note that common args are set in the config file (see initialize_datadir) self.extra_args = extra_args + # Configuration for logging is set as command-line args rather than in the bitcoin.conf file. + # This means that starting a bitcoind using the temp dir to debug a failed test won't + # spam debug.log. self.args = [ self.binary, "-datadir=" + self.datadir, "-logtimemicros", + "-logthreadnames", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d406ee3229..ec5d6f1715 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -66,10 +66,16 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 +EXTENDED_SCRIPTS = [ + # These tests are not run by the travis build process. + # Longest test should go first, to favor running tests in parallel + 'feature_pruning.py', + 'feature_dbcrash.py', +] + BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel - 'feature_pruning.py', 'feature_fee_estimation.py', 'wallet_hd.py', 'wallet_backup.py', @@ -197,12 +203,6 @@ BASE_SCRIPTS = [ # Put them in a random line within the section that fits their approximate run-time ] -EXTENDED_SCRIPTS = [ - # These tests are not run by the travis build process. - # Longest test should go first, to favor running tests in parallel - 'feature_dbcrash.py', -] - # Place EXTENDED_SCRIPTS first since it has the 3 longest running tests ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index c02d7422b9..4d1f1ccdc1 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -59,14 +59,24 @@ class WalletTest(BitcoinTestFramework): assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0) - self.log.info("Mining blocks ...") + self.log.info("Check that only node 0 is watching an address") + assert 'watchonly' in self.nodes[0].getbalances() + assert 'watchonly' not in self.nodes[1].getbalances() + self.log.info("Mining blocks ...") self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(1) self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) self.sync_all() + assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50) + assert_equal(self.nodes[0].getwalletinfo()['balance'], 50) + assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50) + + assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000) + assert 'watchonly' not in self.nodes[1].getbalances() + assert_equal(self.nodes[0].getbalance(), 50) assert_equal(self.nodes[1].getbalance(), 50) @@ -107,8 +117,11 @@ class WalletTest(BitcoinTestFramework): assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) # getunconfirmedbalance assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend + assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('60')) assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) + assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + assert_equal(self.nodes[1].getbalances()['mine']['untrusted_pending'], Decimal('0')) assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) test_balances(fee_node_1=Decimal('0.01')) |