diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -823,9 +823,10 @@ DEFAULT_ACCEPT_DATACARRIER)); strUsage += HelpMessageOpt( "-datacarriersize", - strprintf(_("Maximum size of data in data carrier transactions we " - "relay and mine (default: %u)"), - MAX_OP_RETURN_RELAY)); + strprintf( + _("Maximum size of data in data carrier transactions we relay and " + "mine (pre-fork default: %u, post-fork default: %u)"), + DEFAULT_OP_RETURN_RELAY, DEFAULT_OP_RETURN_RELAY_LARGE)); strUsage += HelpMessageGroup(_("Block creation options:")); strUsage += HelpMessageOpt( diff --git a/src/policy/policy.h b/src/policy/policy.h --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -74,13 +74,17 @@ static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST; -bool IsStandard(const CScript &scriptPubKey, txnouttype &whichType); +bool IsStandard(const CScript &scriptPubKey, txnouttype &whichType, + bool allowLargeOpReturn = false); + /** * Check for standard transaction types * @return True if all outputs (scriptPubKeys) use only standard transaction * forms */ -bool IsStandardTx(const CTransaction &tx, std::string &reason); +bool IsStandardTx(const CTransaction &tx, std::string &reason, + bool allowLargeOpReturn = false); + /** * Check for standard transaction types * @param[in] mapInputs Map of previous transactions that have outputs we're diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -27,9 +27,12 @@ * expensive-to-check-upon-redemption script like: * DUP CHECKSIG DROP ... repeated 100 times... OP_1 */ -bool IsStandard(const CScript &scriptPubKey, txnouttype &whichType) { +bool IsStandard(const CScript &scriptPubKey, txnouttype &whichType, + bool allowLargeOpReturn) { std::vector> vSolutions; - if (!Solver(scriptPubKey, whichType, vSolutions)) return false; + if (!Solver(scriptPubKey, whichType, vSolutions)) { + return false; + } if (whichType == TX_MULTISIG) { uint8_t m = vSolutions.front()[0]; @@ -37,15 +40,29 @@ // Support up to x-of-3 multisig txns as standard if (n < 1 || n > 3) return false; if (m < 1 || m > n) return false; - } else if (whichType == TX_NULL_DATA && - (!fAcceptDatacarrier || - scriptPubKey.size() > nMaxDatacarrierBytes)) - return false; + } else if (whichType == TX_NULL_DATA) { + if (!fAcceptDatacarrier) { + return false; + } + + unsigned nMaxSize = allowLargeOpReturn ? DEFAULT_OP_RETURN_RELAY_LARGE + : DEFAULT_OP_RETURN_RELAY; + + // If the user set a specific value, use that instead. + if (nMaxDatacarrierBytes != DEFAULT_OP_RETURN_RELAY) { + nMaxSize = nMaxDatacarrierBytes; + } + + if (scriptPubKey.size() > nMaxSize) { + return false; + } + } return whichType != TX_NONSTANDARD; } -bool IsStandardTx(const CTransaction &tx, std::string &reason) { +bool IsStandardTx(const CTransaction &tx, std::string &reason, + bool allowLargeOpReturn) { if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) { reason = "version"; return false; @@ -81,14 +98,14 @@ unsigned int nDataOut = 0; txnouttype whichType; for (const CTxOut &txout : tx.vout) { - if (!::IsStandard(txout.scriptPubKey, whichType)) { + if (!::IsStandard(txout.scriptPubKey, whichType, allowLargeOpReturn)) { reason = "scriptpubkey"; return false; } - if (whichType == TX_NULL_DATA) + if (whichType == TX_NULL_DATA) { nDataOut++; - else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { + } else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { reason = "bare-multisig"; return false; } else if (txout.IsDust(dustRelayFee)) { diff --git a/src/script/standard.h b/src/script/standard.h --- a/src/script/standard.h +++ b/src/script/standard.h @@ -27,7 +27,8 @@ }; //!< bytes (+1 for OP_RETURN, +2 for the pushdata opcodes) -static const unsigned int MAX_OP_RETURN_RELAY = 83; +static const unsigned int DEFAULT_OP_RETURN_RELAY = 83; +static const unsigned int DEFAULT_OP_RETURN_RELAY_LARGE = 223; extern bool fAcceptDatacarrier; extern unsigned nMaxDatacarrierBytes; diff --git a/src/script/standard.cpp b/src/script/standard.cpp --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -13,7 +13,7 @@ typedef std::vector valtype; bool fAcceptDatacarrier = DEFAULT_ACCEPT_DATACARRIER; -unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY; +unsigned nMaxDatacarrierBytes = DEFAULT_OP_RETURN_RELAY; CScriptID::CScriptID(const CScript &in) : uint160(Hash160(in.begin(), in.end())) {} diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -587,6 +587,75 @@ BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size()); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + /** + * Check acceptance of larger op_return when asked to. + */ + + // New default size of 223 byte is standard + t.vout[0].scriptPubKey = + CScript() << OP_RETURN + << ParseHex("646578784062697477617463682e636f2092c558ed52c56d" + "8dd14ca76226bc936a84820d898443873eb03d8854b21fa3" + "952b99a2981873e74509281730d78a21786d34a38bd1ebab" + "822fad42278f7f4420db6ab1fd2b6826148d4f73bb41ec2d" + "40a6d5793d66e17074a0c56a8a7df21062308f483dd6e38d" + "53609d350038df0a1b2a9ac8332016e0b904f66880dd0108" + "81c4e8074cce8e4ad6c77cb3460e01bf0e7e811b5f945f83" + "732ba6677520a893d75d9a966cb8f85dc301656b1635c631" + "f5d00d4adf73f2dd112ca75cf19754651909becfbe65aed1" + "3afb2ab8"); + BOOST_CHECK_EQUAL(DEFAULT_OP_RETURN_RELAY_LARGE, + t.vout[0].scriptPubKey.size()); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason, true)); + + // Larger than default size of 223 byte is non-standard + t.vout[0].scriptPubKey = + CScript() << OP_RETURN + << ParseHex("646578784062697477617463682e636f2092c558ed52c56d" + "8dd14ca76226bc936a84820d898443873eb03d8854b21fa3" + "952b99a2981873e74509281730d78a21786d34a38bd1ebab" + "822fad42278f7f4420db6ab1fd2b6826148d4f73bb41ec2d" + "40a6d5793d66e17074a0c56a8a7df21062308f483dd6e38d" + "53609d350038df0a1b2a9ac8332016e0b904f66880dd0108" + "81c4e8074cce8e4ad6c77cb3460e01bf0e7e811b5f945f83" + "732ba6677520a893d75d9a966cb8f85dc301656b1635c631" + "f5d00d4adf73f2dd112ca75cf19754651909becfbe65aed1" + "3afb2ab800"); + BOOST_CHECK_EQUAL(DEFAULT_OP_RETURN_RELAY_LARGE + 1, + t.vout[0].scriptPubKey.size()); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason, true)); + + /** + * Check whena custom value is used for -datacarriersize . + */ + + unsigned nMaxDatacarrierBytesOriginal = nMaxDatacarrierBytes; + nMaxDatacarrierBytes = DEFAULT_OP_RETURN_RELAY + 7; + + // Max user provided payload size is standard + t.vout[0].scriptPubKey = + CScript() << OP_RETURN + << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909" + "a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548" + "271967f1a67130b7105cd6a828e03909a67962e0ea1f61de" + "b649f6bc3f4cef3877696e64657878"); + BOOST_CHECK_EQUAL(nMaxDatacarrierBytes, t.vout[0].scriptPubKey.size()); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + + // Max user provided payload size + 1 is non-standard + t.vout[0].scriptPubKey = + CScript() << OP_RETURN + << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909" + "a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548" + "271967f1a67130b7105cd6a828e03909a67962e0ea1f61de" + "b649f6bc3f4cef3877696e6465787800"); + BOOST_CHECK_EQUAL(nMaxDatacarrierBytes + 1, t.vout[0].scriptPubKey.size()); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + + // Restore defaults. This is not exception safe, but I guess that's what you + // got when using a bunch of globals. + nMaxDatacarrierBytes = nMaxDatacarrierBytesOriginal; + // Data payload can be encoded in any way... t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex(""); BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -754,9 +754,13 @@ return false; } + // After the May, 15 hard fork, we start accepting larger op_return. + const bool allowLargeOpReturn = + IsMonolithEnabled(config, chainActive.Tip()); + // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; - if (fRequireStandard && !IsStandardTx(tx, reason)) { + if (fRequireStandard && !IsStandardTx(tx, reason, allowLargeOpReturn)) { return state.DoS(0, false, REJECT_NONSTANDARD, reason); }