Skip to content

Commit 07ed462

Browse files
committed
Merge branch 'refactor_isstandardtx_mpopts-28+knots' into permitbarepubkey-28+knots
2 parents 1248d0d + e61cfff commit 07ed462

31 files changed

+429
-189
lines changed

src/interfaces/node.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <stdint.h>
2121
#include <string>
2222
#include <tuple>
23+
#include <variant>
2324
#include <vector>
2425

2526
class BanMan;
@@ -214,7 +215,7 @@ class Node
214215
virtual std::optional<Coin> getUnspentOutput(const COutPoint& output) = 0;
215216

216217
//! Broadcast transaction.
217-
virtual node::TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) = 0;
218+
virtual node::TransactionError broadcastTransaction(CTransactionRef tx, const std::variant<CAmount, CFeeRate>& max_tx_fee, std::string& err_string) = 0;
218219

219220
//! Get wallet loader.
220221
virtual WalletLoader& walletLoader() = 0;

src/kernel/mempool_entry.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ struct NewMempoolTransactionInfo {
226226
* This boolean indicates whether the transaction was added
227227
* without enforcing mempool fee limits.
228228
*/
229-
const bool m_mempool_limit_bypassed;
229+
const ignore_rejects_type m_ignore_rejects;
230230
/* This boolean indicates whether the transaction is part of a package. */
231231
const bool m_submitted_in_package;
232232
/*
@@ -239,11 +239,11 @@ struct NewMempoolTransactionInfo {
239239

240240
explicit NewMempoolTransactionInfo(const CTransactionRef& tx, const CAmount& fee,
241241
const int64_t vsize, const unsigned int height,
242-
const bool mempool_limit_bypassed, const bool submitted_in_package,
242+
const ignore_rejects_type& ignore_rejects, const bool submitted_in_package,
243243
const bool chainstate_is_current,
244244
const bool has_no_mempool_parents)
245245
: info{tx, fee, vsize, height},
246-
m_mempool_limit_bypassed{mempool_limit_bypassed},
246+
m_ignore_rejects{ignore_rejects},
247247
m_submitted_in_package{submitted_in_package},
248248
m_chainstate_is_current{chainstate_is_current},
249249
m_has_no_mempool_parents{has_no_mempool_parents} {}

src/node/interfaces.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ class NodeImpl : public Node
355355
if (chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin)) return coin;
356356
return {};
357357
}
358-
TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override
358+
TransactionError broadcastTransaction(CTransactionRef tx, const std::variant<CAmount, CFeeRate>& max_tx_fee, std::string& err_string) override
359359
{
360360
return BroadcastTransaction(*m_context, std::move(tx), err_string, max_tx_fee, /*relay=*/ true, /*wait_callback=*/ false);
361361
}

src/node/mempool_persist.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
9898
}
9999
if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
100100
LOCK(cs_main);
101-
const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
101+
const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, empty_ignore_rejects, /*test_accept=*/false);
102102
if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
103103
++count;
104104
} else {

src/node/transaction.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ static TransactionError HandleATMPError(const TxValidationState& state, std::str
3131
}
3232
}
3333

34-
TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback)
34+
TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const std::variant<CAmount, CFeeRate>& max_tx_fee, bool relay, bool wait_callback, const ignore_rejects_type& ignore_rejects)
3535
{
3636
// BroadcastTransaction can be called by RPC or by the wallet.
3737
// chainman, mempool and peerman are initialized before the RPC server and wallet are started
@@ -69,18 +69,30 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
6969
wtxid = mempool_tx->GetWitnessHash();
7070
} else {
7171
// Transaction is not already in the mempool.
72-
if (max_tx_fee > 0) {
72+
bool max_tx_fee_set{(std::holds_alternative<CAmount>(max_tx_fee) ? std::get<CAmount>(max_tx_fee) : std::get<CFeeRate>(max_tx_fee).GetFeePerK()) > 0};
73+
if (ignore_rejects.count("absurdly-high-fee") || ignore_rejects.count("max-fee-exceeded")) {
74+
max_tx_fee_set = false;
75+
}
76+
if (max_tx_fee_set) {
7377
// First, call ATMP with test_accept and check the fee. If ATMP
7478
// fails here, return error immediately.
75-
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ true);
79+
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ true, ignore_rejects);
7680
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
7781
return HandleATMPError(result.m_state, err_string);
78-
} else if (result.m_base_fees.value() > max_tx_fee) {
79-
return TransactionError::MAX_FEE_EXCEEDED;
82+
} else {
83+
CAmount max_tx_fee_abs;
84+
if (std::holds_alternative<CFeeRate>(max_tx_fee)) {
85+
max_tx_fee_abs = std::get<CFeeRate>(max_tx_fee).GetFee(*Assert(result.m_vsize));
86+
} else {
87+
max_tx_fee_abs = std::get<CAmount>(max_tx_fee);
88+
}
89+
if (result.m_base_fees.value() > max_tx_fee_abs) {
90+
return TransactionError::MAX_FEE_EXCEEDED;
91+
}
8092
}
8193
}
8294
// Try to submit the transaction to the mempool.
83-
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ false);
95+
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ false, ignore_rejects);
8496
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
8597
return HandleATMPError(result.m_state, err_string);
8698
}

src/node/transaction.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include <common/messages.h>
99
#include <policy/feerate.h>
1010
#include <primitives/transaction.h>
11+
#include <policy/policy.h>
12+
13+
#include <variant>
1114

1215
class CBlockIndex;
1316
class CTxMemPool;
@@ -49,7 +52,7 @@ static const CAmount DEFAULT_MAX_BURN_AMOUNT{0};
4952
* @param[in] wait_callback wait until callbacks have been processed to avoid stale result due to a sequentially RPC.
5053
* return error
5154
*/
52-
[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback);
55+
[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const std::variant<CAmount, CFeeRate>& max_tx_fee, bool relay, bool wait_callback, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
5356

5457
/**
5558
* Return transaction with a given hash.

src/policy/fees.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ void CBlockPolicyEstimator::processTransaction(const NewMempoolTransactionInfo&
612612
// - the node is not behind
613613
// - the transaction is not dependent on any other transactions in the mempool
614614
// - it's not part of a package.
615-
const bool validForFeeEstimation = !tx.m_mempool_limit_bypassed && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;
615+
const bool validForFeeEstimation = tx.m_ignore_rejects.empty() && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;
616616

617617
// Only want to be updating estimates when our blockchain is synced,
618618
// otherwise we'll miscalculate how many blocks its taking to get included.

src/policy/policy.cpp

Lines changed: 91 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <consensus/amount.h>
1212
#include <consensus/consensus.h>
1313
#include <consensus/validation.h>
14+
#include <kernel/mempool_options.h>
1415
#include <policy/feerate.h>
1516
#include <primitives/transaction.h>
1617
#include <script/interpreter.h>
@@ -67,6 +68,10 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
6768
return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
6869
}
6970

71+
/**
72+
* Note this must assign whichType even if returning false, in case
73+
* IsStandardTx ignores the "scriptpubkey" rejection.
74+
*/
7075
bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
7176
{
7277
std::vector<std::vector<unsigned char> > vSolutions;
@@ -91,11 +96,27 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
9196
return true;
9297
}
9398

94-
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason)
99+
static inline bool MaybeReject_(std::string& out_reason, const std::string& reason, const std::string& reason_prefix, const ignore_rejects_type& ignore_rejects) {
100+
if (ignore_rejects.count(reason_prefix + reason)) {
101+
return false;
102+
}
103+
104+
out_reason = reason_prefix + reason;
105+
return true;
106+
}
107+
108+
#define MaybeReject(reason) do { \
109+
if (MaybeReject_(out_reason, reason, reason_prefix, ignore_rejects)) { \
110+
return false; \
111+
} \
112+
} while(0)
113+
114+
bool IsStandardTx(const CTransaction& tx, const kernel::MemPoolOptions& opts, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
95115
{
116+
const std::string reason_prefix;
117+
96118
if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < 1) {
97-
reason = "version";
98-
return false;
119+
MaybeReject("version");
99120
}
100121

101122
// Extremely large transactions with lots of inputs can cost the network
@@ -104,8 +125,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
104125
// to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks.
105126
unsigned int sz = GetTransactionWeight(tx);
106127
if (sz > MAX_STANDARD_TX_WEIGHT) {
107-
reason = "tx-size";
108-
return false;
128+
MaybeReject("tx-size");
109129
}
110130

111131
for (const CTxIn& txin : tx.vin)
@@ -119,38 +139,39 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
119139
// 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey
120140
// is not considered standard.
121141
if (txin.scriptSig.size() > MAX_STANDARD_SCRIPTSIG_SIZE) {
122-
reason = "scriptsig-size";
123-
return false;
142+
MaybeReject("scriptsig-size");
124143
}
125144
if (!txin.scriptSig.IsPushOnly()) {
126-
reason = "scriptsig-not-pushonly";
127-
return false;
145+
MaybeReject("scriptsig-not-pushonly");
128146
}
129147
}
130148

131149
unsigned int nDataOut = 0;
132150
TxoutType whichType;
133151
for (const CTxOut& txout : tx.vout) {
134-
if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
135-
reason = "scriptpubkey";
136-
return false;
152+
if (!::IsStandard(txout.scriptPubKey, opts.max_datacarrier_bytes, whichType)) {
153+
if (whichType == TxoutType::WITNESS_UNKNOWN) {
154+
MaybeReject("scriptpubkey-unknown-witnessversion");
155+
} else {
156+
MaybeReject("scriptpubkey");
157+
}
137158
}
138159

139-
if (whichType == TxoutType::NULL_DATA)
160+
if (whichType == TxoutType::NULL_DATA) {
140161
nDataOut++;
141-
else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
142-
reason = "bare-multisig";
143-
return false;
144-
} else if (IsDust(txout, dust_relay_fee)) {
145-
reason = "dust";
146-
return false;
162+
continue;
163+
}
164+
else if ((whichType == TxoutType::MULTISIG) && (!opts.permit_bare_multisig)) {
165+
MaybeReject("bare-multisig");
166+
}
167+
if (IsDust(txout, opts.dust_relay_feerate)) {
168+
MaybeReject("dust");
147169
}
148170
}
149171

150172
// only one OP_RETURN txout is permitted
151173
if (nDataOut > 1) {
152-
reason = "multi-op-return";
153-
return false;
174+
MaybeReject("multi-op-return");
154175
}
155176

156177
return true;
@@ -174,7 +195,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
174195
*
175196
* Note that only the non-witness portion of the transaction is checked here.
176197
*/
177-
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
198+
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
178199
{
179200
if (tx.IsCoinBase()) {
180201
return true; // Coinbases don't use vin normally
@@ -185,30 +206,46 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
185206

186207
std::vector<std::vector<unsigned char> > vSolutions;
187208
TxoutType whichType = Solver(prev.scriptPubKey, vSolutions);
188-
if (whichType == TxoutType::NONSTANDARD || whichType == TxoutType::WITNESS_UNKNOWN) {
209+
if (whichType == TxoutType::NONSTANDARD) {
210+
MaybeReject("script-unknown");
211+
} else if (whichType == TxoutType::WITNESS_UNKNOWN) {
189212
// WITNESS_UNKNOWN failures are typically also caught with a policy
190213
// flag in the script interpreter, but it can be helpful to catch
191214
// this type of NONSTANDARD transaction earlier in transaction
192215
// validation.
193-
return false;
216+
MaybeReject("witness-unknown");
194217
} else if (whichType == TxoutType::SCRIPTHASH) {
218+
if (!tx.vin[i].scriptSig.IsPushOnly()) {
219+
// The only way we got this far, is if the user ignored scriptsig-not-pushonly.
220+
// However, this case is invalid, and will be caught later on.
221+
// But for now, we don't want to run the [possibly expensive] script here.
222+
continue;
223+
}
195224
std::vector<std::vector<unsigned char> > stack;
196225
// convert the scriptSig into a stack, so we can inspect the redeemScript
197226
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE))
227+
{
228+
// This case is also invalid or a bug
229+
out_reason = reason_prefix + "scriptsig-failure";
198230
return false;
231+
}
199232
if (stack.empty())
233+
{
234+
// Also invalid
235+
out_reason = reason_prefix + "scriptcheck-missing";
200236
return false;
237+
}
201238
CScript subscript(stack.back().begin(), stack.back().end());
202239
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
203-
return false;
240+
MaybeReject("scriptcheck-sigops");
204241
}
205242
}
206243
}
207244

208245
return true;
209246
}
210247

211-
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
248+
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
212249
{
213250
if (tx.IsCoinBase())
214251
return true; // Coinbases are skipped
@@ -227,7 +264,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
227264

228265
// witness stuffing detected
229266
if (prevScript.IsPayToAnchor()) {
230-
return false;
267+
MaybeReject("anchor-not-empty");
231268
}
232269

233270
bool p2sh = false;
@@ -237,9 +274,15 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
237274
// into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway.
238275
// If the check fails at this stage, we know that this txid must be a bad one.
239276
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE))
277+
{
278+
out_reason = reason_prefix + "scriptsig-failure";
240279
return false;
280+
}
241281
if (stack.empty())
282+
{
283+
out_reason = reason_prefix + "scriptcheck-missing";
242284
return false;
285+
}
243286
prevScript = CScript(stack.back().begin(), stack.back().end());
244287
p2sh = true;
245288
}
@@ -249,18 +292,21 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
249292

250293
// Non-witness program must not be associated with any witness
251294
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
295+
{
296+
out_reason = reason_prefix + "nonwitness-input";
252297
return false;
298+
}
253299

254300
// Check P2WSH standard limits
255301
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
256302
if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE)
257-
return false;
303+
MaybeReject("script-size");
258304
size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1;
259305
if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS)
260-
return false;
306+
MaybeReject("stackitem-count");
261307
for (unsigned int j = 0; j < sizeWitnessStack; j++) {
262308
if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE)
263-
return false;
309+
MaybeReject("stackitem-size");
264310
}
265311
}
266312

@@ -272,24 +318,36 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
272318
Span stack{tx.vin[i].scriptWitness.stack};
273319
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
274320
// Annexes are nonstandard as long as no semantics are defined for them.
275-
return false;
321+
MaybeReject("taproot-annex");
322+
// If reject reason is ignored, continue as if the annex wasn't there.
323+
SpanPopBack(stack);
276324
}
277325
if (stack.size() >= 2) {
278326
// Script path spend (2 or more stack elements after removing optional annex)
279327
const auto& control_block = SpanPopBack(stack);
280328
SpanPopBack(stack); // Ignore script
281-
if (control_block.empty()) return false; // Empty control block is invalid
329+
if (control_block.empty()) {
330+
// Empty control block is invalid
331+
out_reason = reason_prefix + "taproot-control-missing";
332+
return false;
333+
}
282334
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
283335
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
336+
if (!ignore_rejects.count(reason_prefix + "taproot-stackitem-size")) {
284337
for (const auto& item : stack) {
285-
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) return false;
338+
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) {
339+
out_reason = reason_prefix + "taproot-stackitem-size";
340+
return false;
341+
}
342+
}
286343
}
287344
}
288345
} else if (stack.size() == 1) {
289346
// Key path spend (1 stack element after removing optional annex)
290347
// (no policy rules apply)
291348
} else {
292349
// 0 stack elements; this is already invalid by consensus rules
350+
out_reason = reason_prefix + "taproot-witness-missing";
293351
return false;
294352
}
295353
}

0 commit comments

Comments
 (0)