Skip to content

Commit 7989901

Browse files
committed
Add txids with non-standard inputs to reject filter
Our policy checks for non-standard inputs depend only on the non-witness portion of a transaction: we look up the scriptPubKey of the input being spent from our UTXO set (which is covered by the input txid), and the p2sh checks only rely on the scriptSig portion of the input. Consequently it's safe to add txids of transactions that fail these checks to the reject filter, as the witness is irrelevant to the failure. This is helpful for any situation where we might request the transaction again via txid (either from txid-relay peers, or if we might fetch the transaction via txid due to parent-fetching of orphans). Further, in preparation for future witness versions being deployed on the network, ensure that WITNESS_UNKNOWN transactions are rejected in AreInputsStandard(), so that transactions spending v1 (or greater) witness outputs will fall into this category of having their txid added to the reject filter.
1 parent a41ae68 commit 7989901

File tree

4 files changed

+45
-6
lines changed

4 files changed

+45
-6
lines changed

src/consensus/validation.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ enum class TxValidationResult {
2626
* is uninteresting.
2727
*/
2828
TX_RECENT_CONSENSUS_CHANGE,
29-
TX_NOT_STANDARD, //!< didn't meet our local policy rules
29+
TX_INPUTS_NOT_STANDARD, //!< inputs (covered by txid) failed policy rules
30+
TX_NOT_STANDARD, //!< otherwise didn't meet our local policy rules
3031
TX_MISSING_INPUTS, //!< transaction was missing some of its inputs
3132
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
3233
/**

src/net_processing.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ namespace {
187187
* million to make it highly unlikely for users to have issues with this
188188
* filter.
189189
*
190-
* We only need to add wtxids to this filter. For non-segwit
190+
* We typically only add wtxids to this filter. For non-segwit
191191
* transactions, the txid == wtxid, so this only prevents us from
192192
* re-downloading non-segwit transactions when communicating with
193193
* non-wtxidrelay peers -- which is important for avoiding malleation
@@ -196,6 +196,12 @@ namespace {
196196
* the reject filter store wtxids is exactly what we want to avoid
197197
* redownload of a rejected transaction.
198198
*
199+
* In cases where we can tell that a segwit transaction will fail
200+
* validation no matter the witness, we may add the txid of such
201+
* transaction to the filter as well. This can be helpful when
202+
* communicating with txid-relay peers or if we were to otherwise fetch a
203+
* transaction via txid (eg in our orphan handling).
204+
*
199205
* Memory used: 1.3 MB
200206
*/
201207
std::unique_ptr<CRollingBloomFilter> recentRejects GUARDED_BY(cs_main);
@@ -1161,6 +1167,7 @@ static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state,
11611167
}
11621168
// Conflicting (but not necessarily invalid) data or different policy:
11631169
case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE:
1170+
case TxValidationResult::TX_INPUTS_NOT_STANDARD:
11641171
case TxValidationResult::TX_NOT_STANDARD:
11651172
case TxValidationResult::TX_MISSING_INPUTS:
11661173
case TxValidationResult::TX_PREMATURE_SPEND:
@@ -2053,6 +2060,19 @@ void static ProcessOrphanTx(CConnman& connman, CTxMemPool& mempool, std::set<uin
20532060
// if we start doing this too early.
20542061
assert(recentRejects);
20552062
recentRejects->insert(orphanTx.GetWitnessHash());
2063+
// If the transaction failed for TX_INPUTS_NOT_STANDARD,
2064+
// then we know that the witness was irrelevant to the policy
2065+
// failure, since this check depends only on the txid
2066+
// (the scriptPubKey being spent is covered by the txid).
2067+
// Add the txid to the reject filter to prevent repeated
2068+
// processing of this transaction in the event that child
2069+
// transactions are later received (resulting in
2070+
// parent-fetching by txid via the orphan-handling logic).
2071+
if (orphan_state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && orphanTx.GetWitnessHash() != orphanTx.GetHash()) {
2072+
// We only add the txid if it differs from the wtxid, to
2073+
// avoid wasting entries in the rolling bloom filter.
2074+
recentRejects->insert(orphanTx.GetHash());
2075+
}
20562076
}
20572077
EraseOrphanTx(orphanHash);
20582078
done = true;
@@ -2940,7 +2960,7 @@ void ProcessMessage(
29402960

29412961
// We do the AlreadyHave() check using wtxid, rather than txid - in the
29422962
// absence of witness malleation, this is strictly better, because the
2943-
// recent rejects filter may contain the wtxid but will never contain
2963+
// recent rejects filter may contain the wtxid but rarely contains
29442964
// the txid of a segwit transaction that has been rejected.
29452965
// In the presence of witness malleation, it's possible that by only
29462966
// doing the check with wtxid, we could overlook a transaction which
@@ -3034,6 +3054,17 @@ void ProcessMessage(
30343054
// if we start doing this too early.
30353055
assert(recentRejects);
30363056
recentRejects->insert(tx.GetWitnessHash());
3057+
// If the transaction failed for TX_INPUTS_NOT_STANDARD,
3058+
// then we know that the witness was irrelevant to the policy
3059+
// failure, since this check depends only on the txid
3060+
// (the scriptPubKey being spent is covered by the txid).
3061+
// Add the txid to the reject filter to prevent repeated
3062+
// processing of this transaction in the event that child
3063+
// transactions are later received (resulting in
3064+
// parent-fetching by txid via the orphan-handling logic).
3065+
if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD && tx.GetWitnessHash() != tx.GetHash()) {
3066+
recentRejects->insert(tx.GetHash());
3067+
}
30373068
if (RecursiveDynamicUsage(*ptx) < 100000) {
30383069
AddToCompactExtraTransactions(ptx);
30393070
}

src/policy/policy.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeR
152152
* script can be anything; an attacker could use a very
153153
* expensive-to-check-upon-redemption script like:
154154
* DUP CHECKSIG DROP ... repeated 100 times... OP_1
155+
*
156+
* Note that only the non-witness portion of the transaction is checked here.
155157
*/
156158
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
157159
{
@@ -164,7 +166,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
164166

165167
std::vector<std::vector<unsigned char> > vSolutions;
166168
TxoutType whichType = Solver(prev.scriptPubKey, vSolutions);
167-
if (whichType == TxoutType::NONSTANDARD) {
169+
if (whichType == TxoutType::NONSTANDARD || whichType == TxoutType::WITNESS_UNKNOWN) {
170+
// WITNESS_UNKNOWN failures are typically also caught with a policy
171+
// flag in the script interpreter, but it can be helpful to catch
172+
// this type of NONSTANDARD transaction earlier in transaction
173+
// validation.
168174
return false;
169175
} else if (whichType == TxoutType::SCRIPTHASH) {
170176
std::vector<std::vector<unsigned char> > stack;

src/validation.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
689689
}
690690

691691
// Check for non-standard pay-to-script-hash in inputs
692-
if (fRequireStandard && !AreInputsStandard(tx, m_view))
693-
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-nonstandard-inputs");
692+
if (fRequireStandard && !AreInputsStandard(tx, m_view)) {
693+
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
694+
}
694695

695696
// Check for non-standard witness in P2WSH
696697
if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view))

0 commit comments

Comments
 (0)