-acceptnonstdtxn=1
so that it also skips the non-mandatory
script checks, allowing txs that (eg) use OP_NOPx or OP_SUCCESS into the mempool. This remains only available on test nets.
-acceptnonstdtxn=1
so that it also skips the non-mandatory
script checks, allowing txs that (eg) use OP_NOPx or OP_SUCCESS into the mempool. This remains only available on test nets.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
For detailed information about the code coverage, see the test coverage report.
See the guideline for information on the review process.
Type | Reviewers |
---|---|
Concept ACK | darosior, moonsettler, sipa, instagibbs |
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
GetBlockScriptFlags
that’s not in MANDATORY_SCRIPT_VERIFY_FLAGS
, and if there is such a rule, even with this patch, you could add a tx to the mempool prior to the rule becoming active, in which case it wouldn’t magically be removed from the mempool when it became active. There’s already a double check in the mining code (test_block_validity
) which would prevent PoW being wasted on a block that included an invalid tx because it was somehow made it into the mempool.
have -acceptnonstdtxn=1 skip the non-mandatory script checks,
Can you give a bit of motivation for this second part?
have -acceptnonstdtxn=1 skip the non-mandatory script checks,
Can you give a bit of motivation for this second part?
Mostly that STANDARD_SCRIPT_VERIFY_FLAGS
violations sounds like something that acceptnonstdtxn
should allow. But some particular examples:
-acceptnonstdtxn
seemed like a way to make this possible, except it doesn’t actually work. https://twitter.com/fiatjaf/status/16327101591631708212041@@ -2016,33 +2042,33 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch
2042 // mainnet.
2043 // For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two
2044 // violating blocks.
2045- uint32_t flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT};
2046+ MandatoryFlags flags{MandatoryFlag<SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT>};
Probably this commit should be in its own PR since it touches block consensus code. I just wanted to make sure that there is a path forward to ensuring we don’t introduce mempool bugs in future by updating consensus without also updating MANDATORY_SCRIPT_VERIFY_FLAGS
. This gives compile time errors:
0validation.cpp:2070:11: error: no viable overloaded '|='
1 flags |= SCRIPT_VERIFY_CLEANSTACK;
or
0validation.cpp:2013:5: error: static_assert failed due to requirement '(16384U & MANDATORY_SCRIPT_VERIFY_FLAGS) == 16384U' "MandatoryFlag not included in MANDATORY_SCRIPT_VERIFY_FLAGS"
1....
2validation.cpp:2071:14: note: in instantiation of variable template specialization '(anonymous namespace)::MandatoryFlag<16384>' requested here
3 flags |= MandatoryFlag<SCRIPT_VERIFY_NULLFAIL>;
2041@@ -2016,33 +2042,33 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch
2042 // mainnet.
2043 // For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two
2044 // violating blocks.
2045- uint32_t flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT};
2046+ MandatoryFlags flags{MandatoryFlag<SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT>};
2047 const auto it{consensusparams.script_flag_exceptions.find(*Assert(block_index.phashBlock))};
2048 if (it != consensusparams.script_flag_exceptions.end()) {
2049- flags = it->second;
2050+ flags &= it->second;
script_flag_exceptions
from adding rules; it can now only remove from the P2SH, SEGWIT, TAPROOT
set.
Mostly that STANDARD_SCRIPT_VERIFY_FLAGS violations sounds like something that acceptnonstdtxn should allow. But some particular examples:
I think this is valuable for experimentation to allow non-standard scripts with -accetnonstdtxn=1
, though I wonder if we wouldn’t wish more granularity e.g allow OP_SUCCESS / OP_NOP without bypassing IsStandardTx
checks. I wonder if we might wish partial policy loosening deployment, e.g if a OP_NOP is used as a new data carrier though all the other checks on sanity of spent inputs / transaction size / etc are conserved. Related discussions in #27926.
I’m not sure I understand what this brings to Bitcoin could you elaborate?
it’s a rationalization of node behavior. removes some logical inconsistencies related to standardness checks and relay policy and gives you better control over your own node.
sometimes you remove weirdness instead of adding new functionality.
2008+// Helpers to ensure that GetBlockScriptFlags only returns a superset of MANDATORY_SCRIPT_VERIFY_FLAGS
2009+// (otherwise unminable txs could end up in the mempool)
2010+template<uint32_t FLAG>
2011+struct MandatoryFlagChecked
2012+{
2013+ static_assert((FLAG & MANDATORY_SCRIPT_VERIFY_FLAGS) == FLAG, "MandatoryFlag not included in MANDATORY_SCRIPT_VERIFY_FLAGS");
Okay, I think there’s a reasonable way of doing this with C++20 consteval:
0class MandatoryFlags {
1private:
2 uint32_t value{0};
3 struct CheckFlag {
4 uint32_t flag;
5 consteval CheckFlag(uint32_t f) {
6 if ((f & MANDATORY_SCRIPT_VERIFY_FLAGS) != f) {
7 throw "Block script flag not included in MANDATORY_SCRIPT_VERIFY_FLAGS";
8 }
9 flag = f;
10 }
11 };
12public:
13 explicit MandatoryFlags(const CheckFlag& flags) { value |= flags.flag; }
14 void operator|=(const CheckFlag& flags) { value |= flags.flag; }
15 void operator&=(uint32_t flags) { value &= flags; }
16 operator uint32_t() const { return value; }
17};
then declare MandatoryFlags flags;
and you can just say flags |= SCRIPT_VERIFY_DERSIG;
as normal, but if you give a non-mandatory flag, you get:
0validation.cpp:2072:14: error: call to consteval function '(anonymous namespace)::MandatoryFlags::CheckFlag::CheckFlag' is not a constant expression
1 flags |= SCRIPT_VERIFY_MINIMALIF;
2 ^
3validation.cpp:2018:17: note: subexpression not valid in a constant expression
4 throw "Block script flag not included in MANDATORY_SCRIPT_VERIFY_FLAGS";
5 ^
and saying flags |= var
also gives you a decent error:
0validation.cpp:2049:18: error: call to consteval function '(anonymous namespace)::MandatoryFlags::CheckFlag::CheckFlag' is not a constant expression
1 flags |= var;
2 ^
3validation.cpp:2049:18: note: initializer of 'var' is not a constant expression
4validation.cpp:2046:20: note: declared here
5 const auto var = it->second;
6 ^
GetBlockScriptFlags
and MANDATORY_SCRIPT_VERIFY_FLAGS
with a comment.
I think this is valuable for experimentation to allow non-standard scripts with
-accetnonstdtxn=1
, though I wonder if we wouldn’t wish more granularity e.g allow OP_SUCCESS / OP_NOP without bypassingIsStandardTx
checks. I wonder if we might wish partial policy loosening deployment, e.g if a OP_NOP is used as a new data carrier though all the other checks on sanity of spent inputs / transaction size / etc are conserved.
I think any time you want more granularity, you should just define the new standardness rule and implement it. Examples:
-annexdatacarrier
https://github.com/bitcoin-inquisition/bitcoin/pull/22-ephemeralanchors
https://github.com/bitcoin-inquisition/bitcoin/pull/23Using -acceptnonstdtxn
is useful for experimenting before you get to that point (or while you’re not confident that the code implementing it is really safe to run, but you want to see the txs that use those rules anyway).
-acceptnonstdtxn=2
or something?
I think we should distinguish at least between simply non-standard transactions, and ones that use opcodes reserved for future functionality. Maybe only bypass the latter for
-acceptnonstdtxn=2
or something?
I guess the difference would be:
Not really seeing the point in differentiating between them though; all the examples I have are where you want to mess with the upgradable things.
I think any time you want more granularity, you should just define the new standardness rule and implement it.
Yes this is one policy rules design viewpoint. The pitfall I can think of is in term of DoS protection of full-nodes. Let’s say in the future we discover a new DoS vector on new deployed segwit versions (e.g grafroot spends) and max transaction size. Max transaction size (MAX_STANDARD_TX_WEIGHT
) is currently enforced IsStandardTx
, lowering this limit would a plausible fix to this DoS vector, however it would come at the ecosystem cost of breaking all the data carriage use-case relying on current MAX_STANDARD_TX_WEIGHT
. Those node operators if they have high-performance full-nodes would have an interest to keep those loose default, and we’ll start to have mempool partitioning in the ecosystem (sounds bad for decentralization), I think.
All of that hypothetical, though it sounds the most-granular policy loosening allowing the use-case can be a better policy rules design thumb rule. I wonder if here a new acceptnonstdscripts=1
would fit ? If it’s for experimentation-only, the current code changes could be also signet / testnet /regtest only.
Prepare for updating -acceptnonstdtxn to allow txns that violate
STANDARD_SCRIPT_VERIFY_FLAGS but not MANDATORY_SCRIPT_VERIFY_FLAGS by
checking the non-mandatory flags with node that enforces standardness.
Leaning on -0 now for this PR.
Unfortunately I think mining pools may start hacking things off with their modifications, including flipping standardness switch in mainnet. From what I can tell, Marathon was doing exactly this feedback was given to introduce back some limits, such as upgrade hooks.