mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-15 11:36:00 -05:00
![Wladimir J. van der Laan](/assets/img/avatar_default.png)
0a4f1422cd
Further improve comments around recentRejects (Suhas Daftuar)0e20cfedb7
Disconnect peers sending wtxidrelay message after VERACK (Suhas Daftuar)cacd85209e
test: Use wtxid relay generally in functional tests (Fabian Jahr)8d8099e97a
test: Add tests for wtxid tx relay in segwit test (Fabian Jahr)9a5392fdf6
test: Update test framework p2p protocol version to 70016 (Fabian Jahr)dd78d1d641
Rename AddInventoryKnown() to AddKnownTx() (Suhas Daftuar)4eb515574e
Make TX_WITNESS_STRIPPED its own rejection reason (Suhas Daftuar)97141ca442
Delay getdata requests from peers using txid-based relay (Suhas Daftuar)46d78d47de
Add p2p message "wtxidrelay" (Suhas Daftuar)2d282e0cba
ignore non-wtxidrelay compliant invs (Anthony Towns)ac88e2eb61
Add support for tx-relay via wtxid (Suhas Daftuar)8e68fc246d
Add wtxids to recentRejects instead of txids (Suhas Daftuar)144c385820
Add wtxids of confirmed transactions to bloom filter (Suhas Daftuar)85c78d54af
Add wtxid-index to orphan map (Suhas Daftuar)08b39955ec
Add a wtxid-index to mapRelay (Suhas Daftuar)60f0acda71
Just pass a hash to AddInventoryKnown (Suhas Daftuar)c7eb6b4f1f
Add wtxid to mempool unbroadcast tracking (Amiti Uttarwar)2b4b90aa8f
Add a wtxid-index to the mempool (Suhas Daftuar) Pull request description: Using txids (a transaction's hash, without witness) for transaction relay is problematic, post-segwit -- if a peer gives us a segwit transaction that fails policy checks, it could be because the txid associated with the transaction is definitely unacceptable to our node (regardless of the witness), or it could be that the transaction was malleated and with a different witness, the txid could be accepted to our mempool. We have a bloom filter of recently rejected transactions, whose purpose is to help us avoid redownloading and revalidating transactions that fail to be accepted, but because of this potential for witness malleability to interfere with relay of valid transactions, we do not use the filter for segwit transactions. This issue is discussed at some length in #8279. The effect of this is that whenever a segwit transaction that fails policy checks is relayed, a node would download that transaction from every peer announcing it, because it has no way presently to cache failure. Historically this hasn't been a big problem, but if/when policy for accepting segwit transactions were to change (eg taproot, or any other change), we could expect older nodes talking to newer nodes to be wasting bandwidth because of this. As discussed in that issue, switching to wtxid-based relay solves this problem -- by using an identifier for a transaction that commits to all the data in our relay protocol, we can be certain if a transaction that a peer is announcing is one that we've already tried to process, or if it's something new. This PR introduces support for wtxid-based relay with peers that support it (and remains backwards compatible with peers that use txids for relay, of course). Apart from code correctness, one issue to be aware of is that by downloading from old and new peers alike, we should expect there to be some bandwidth wasted, because sometimes we might download the same transaction via txid-relay as well as wtxid-relay. The last commit in this PR implements a heuristic I want to analyze, which is to just delay relay from txid-relay peers by 2 seconds, if we have at least 1 wtxid-based peer. I've just started running a couple nodes with this heuristic so I can measure how well it works, but I'm open to other ideas for minimizing that issue. In the long run, I think this will be essentially a non-issue, so I don't think it's too big a concern, we just need to bite the bullet and deal with it during upgrade. Finally, this proposal would need a simple BIP describing the changes, which I haven't yet drafted. However, review and testing of this code in the interim would be welcome. To do items: - [x] Write BIP explaining the spec here (1 new p2p message for negotiating wtxid-based relay, along with a new INV type) - [ ] Measure and evaluate a heuristic for minimizing how often a node downloads the same transaction twice, when connected to old and new nodes. ACKs for top commit: naumenkogs: utACK0a4f1422cd
laanwj: utACK0a4f1422cd
Tree-SHA512: d8eb8f0688cf0cbe9507bf738e143edab1f595551fdfeddc2b6734686ea26e7f156b6bfde38bad8bbbe8bec1857c7223e1687f8f018de7463dde8ecaa8f450df
153 lines
6.6 KiB
C++
153 lines
6.6 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// 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.
|
|
|
|
#ifndef BITCOIN_CONSENSUS_VALIDATION_H
|
|
#define BITCOIN_CONSENSUS_VALIDATION_H
|
|
|
|
#include <string>
|
|
#include <version.h>
|
|
#include <consensus/consensus.h>
|
|
#include <primitives/transaction.h>
|
|
#include <primitives/block.h>
|
|
|
|
/** A "reason" why a transaction was invalid, suitable for determining whether the
|
|
* provider of the transaction should be banned/ignored/disconnected/etc.
|
|
*/
|
|
enum class TxValidationResult {
|
|
TX_RESULT_UNSET = 0, //!< initial value. Tx has not yet been rejected
|
|
TX_CONSENSUS, //!< invalid by consensus rules
|
|
/**
|
|
* 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.
|
|
*/
|
|
TX_RECENT_CONSENSUS_CHANGE,
|
|
TX_NOT_STANDARD, //!< didn't meet our local policy rules
|
|
TX_MISSING_INPUTS, //!< transaction was missing some of its inputs
|
|
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
|
|
/**
|
|
* Transaction might have a witness prior to SegWit
|
|
* activation, or witness may have been malleated (which includes
|
|
* non-standard witnesses).
|
|
*/
|
|
TX_WITNESS_MUTATED,
|
|
/**
|
|
* Transaction is missing a witness.
|
|
*/
|
|
TX_WITNESS_STRIPPED,
|
|
/**
|
|
* 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)
|
|
* Currently this is only used if the transaction already exists in the mempool or on chain.
|
|
*/
|
|
TX_CONFLICT,
|
|
TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits
|
|
};
|
|
|
|
/** A "reason" why a block was invalid, suitable for determining whether the
|
|
* provider of the block 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 BlockValidationResult {
|
|
BLOCK_RESULT_UNSET = 0, //!< initial value. Block has not yet been rejected
|
|
BLOCK_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.
|
|
*/
|
|
BLOCK_RECENT_CONSENSUS_CHANGE,
|
|
BLOCK_CACHED_INVALID, //!< this block was cached as being invalid and we didn't store the reason 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
|
|
};
|
|
|
|
|
|
|
|
/** Template for capturing information about block/transaction validation. This is instantiated
|
|
* by TxValidationState and BlockValidationState for validation information on transactions
|
|
* and blocks respectively. */
|
|
template <typename Result>
|
|
class ValidationState
|
|
{
|
|
private:
|
|
enum class ModeState {
|
|
M_VALID, //!< everything ok
|
|
M_INVALID, //!< network rule violation (DoS value may be set)
|
|
M_ERROR, //!< run-time error
|
|
} m_mode{ModeState::M_VALID};
|
|
Result m_result{};
|
|
std::string m_reject_reason;
|
|
std::string m_debug_message;
|
|
|
|
public:
|
|
bool Invalid(Result result,
|
|
const std::string& reject_reason = "",
|
|
const std::string& debug_message = "")
|
|
{
|
|
m_result = result;
|
|
m_reject_reason = reject_reason;
|
|
m_debug_message = debug_message;
|
|
if (m_mode != ModeState::M_ERROR) m_mode = ModeState::M_INVALID;
|
|
return false;
|
|
}
|
|
bool Error(const std::string& reject_reason)
|
|
{
|
|
if (m_mode == ModeState::M_VALID)
|
|
m_reject_reason = reject_reason;
|
|
m_mode = ModeState::M_ERROR;
|
|
return false;
|
|
}
|
|
bool IsValid() const { return m_mode == ModeState::M_VALID; }
|
|
bool IsInvalid() const { return m_mode == ModeState::M_INVALID; }
|
|
bool IsError() const { return m_mode == ModeState::M_ERROR; }
|
|
Result GetResult() const { return m_result; }
|
|
std::string GetRejectReason() const { return m_reject_reason; }
|
|
std::string GetDebugMessage() const { return m_debug_message; }
|
|
std::string ToString() const
|
|
{
|
|
if (IsValid()) {
|
|
return "Valid";
|
|
}
|
|
|
|
if (!m_debug_message.empty()) {
|
|
return m_reject_reason + ", " + m_debug_message;
|
|
}
|
|
|
|
return m_reject_reason;
|
|
}
|
|
};
|
|
|
|
class TxValidationState : public ValidationState<TxValidationResult> {};
|
|
class BlockValidationState : public ValidationState<BlockValidationResult> {};
|
|
|
|
// These implement the weight = (stripped_size * 4) + witness_size formula,
|
|
// using only serialization with and without witness data. As witness_size
|
|
// is equal to total_size - stripped_size, this formula is identical to:
|
|
// weight = (stripped_size * 3) + total_size.
|
|
static inline int64_t GetTransactionWeight(const CTransaction& tx)
|
|
{
|
|
return ::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(tx, PROTOCOL_VERSION);
|
|
}
|
|
static inline int64_t GetBlockWeight(const CBlock& block)
|
|
{
|
|
return ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(block, PROTOCOL_VERSION);
|
|
}
|
|
static inline int64_t GetTransactionInputWeight(const CTxIn& txin)
|
|
{
|
|
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
|
|
return ::GetSerializeSize(txin, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, PROTOCOL_VERSION);
|
|
}
|
|
|
|
#endif // BITCOIN_CONSENSUS_VALIDATION_H
|