mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge 7c35312964
into 51d76634fb
This commit is contained in:
commit
18723bc7e4
9 changed files with 304 additions and 0 deletions
|
@ -641,6 +641,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
|
|||
"is of this size or less (default: %u)",
|
||||
MAX_OP_RETURN_RELAY),
|
||||
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-limitdummyscriptdatasize", strprintf("Maximum size of dummy script data we relay and mine (default: %u)", MAX_DUMMY_SCRIPT_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-permitbaremultisig", strprintf("Relay transactions creating non-P2SH multisig outputs (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY,
|
||||
OptionsCategory::NODE_RELAY);
|
||||
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
|
||||
|
|
|
@ -51,6 +51,7 @@ struct MemPoolOptions {
|
|||
* If nullopt, any size is nonstandard.
|
||||
*/
|
||||
std::optional<unsigned> max_datacarrier_bytes{DEFAULT_ACCEPT_DATACARRIER ? std::optional{MAX_OP_RETURN_RELAY} : std::nullopt};
|
||||
unsigned int max_dummy_script_bytes{MAX_DUMMY_SCRIPT_RELAY};
|
||||
bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG};
|
||||
bool require_standard{true};
|
||||
bool persist_v1_dat{DEFAULT_PERSIST_V1_DAT};
|
||||
|
|
|
@ -87,6 +87,8 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
|
|||
mempool_opts.max_datacarrier_bytes = std::nullopt;
|
||||
}
|
||||
|
||||
mempool_opts.max_dummy_script_bytes = argsman.GetIntArg("-limitdummyscriptdatasize", MAX_DUMMY_SCRIPT_RELAY);
|
||||
|
||||
mempool_opts.require_standard = !argsman.GetBoolArg("-acceptnonstdtxn", DEFAULT_ACCEPT_NON_STD_TXN);
|
||||
if (!chainparams.IsTestChain() && !mempool_opts.require_standard) {
|
||||
return util::Error{Untranslated(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.GetChainTypeString()))};
|
||||
|
|
|
@ -321,3 +321,66 @@ int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost, un
|
|||
{
|
||||
return GetVirtualTransactionSize(GetTransactionInputWeight(txin), nSigOpCost, bytes_per_sigop);
|
||||
}
|
||||
|
||||
std::pair<CScript, unsigned int> GetScriptForTransactionInput(CScript prev_script, const CTxIn& txin)
|
||||
{
|
||||
bool p2sh = false;
|
||||
if (prev_script.IsPayToScriptHash()) {
|
||||
std::vector <std::vector<unsigned char> > stack;
|
||||
if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE)) {
|
||||
return std::make_pair(CScript(), 0);
|
||||
}
|
||||
if (stack.empty()) {
|
||||
return std::make_pair(CScript(), 0);
|
||||
}
|
||||
prev_script = CScript(stack.back().begin(), stack.back().end());
|
||||
p2sh = true;
|
||||
}
|
||||
|
||||
int witnessversion = 0;
|
||||
std::vector<unsigned char> witnessprogram;
|
||||
|
||||
if (!prev_script.IsWitnessProgram(witnessversion, witnessprogram)) {
|
||||
// For P2SH, scriptSig is always push-only, so the actual script is only the last stack item
|
||||
// For non-P2SH, prevScript is likely the real script, but not part of this transaction, and scriptSig could very well be executable, so return the latter instead
|
||||
return std::make_pair(p2sh ? prev_script : txin.scriptSig, WITNESS_SCALE_FACTOR);
|
||||
}
|
||||
|
||||
auto stack = std::span{txin.scriptWitness.stack};
|
||||
|
||||
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
|
||||
if (stack.empty()) return std::make_pair(CScript(), 0); // invalid
|
||||
auto& script_data = stack.back();
|
||||
prev_script = CScript(script_data.begin(), script_data.end());
|
||||
return std::make_pair(prev_script, 1);
|
||||
}
|
||||
|
||||
if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) {
|
||||
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
|
||||
SpanPopBack(stack);
|
||||
}
|
||||
if (stack.size() >= 2) {
|
||||
SpanPopBack(stack); // Ignore control block
|
||||
prev_script = CScript(stack.back().begin(), stack.back().end());
|
||||
return std::make_pair(prev_script, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(CScript(), 0);
|
||||
}
|
||||
|
||||
size_t DummyScriptBytes(const CTransaction& tx, const CCoinsViewCache& view)
|
||||
{
|
||||
size_t ret{0};
|
||||
|
||||
for (const CTxIn& txin : tx.vin) {
|
||||
const CTxOut &utxo = view.AccessCoin(txin.prevout).out;
|
||||
auto[script, consensus_weight_per_byte] = GetScriptForTransactionInput(utxo.scriptPubKey, txin);
|
||||
ret += script.DummyScriptBytes();
|
||||
}
|
||||
for (const CTxOut& txout : tx.vout) {
|
||||
ret += txout.scriptPubKey.DummyScriptBytes();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class CCoinsViewCache;
|
||||
class CFeeRate;
|
||||
|
@ -72,6 +73,8 @@ static constexpr unsigned int DEFAULT_DESCENDANT_LIMIT{25};
|
|||
static constexpr unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT_KVB{101};
|
||||
/** Default for -datacarrier */
|
||||
static const bool DEFAULT_ACCEPT_DATACARRIER = true;
|
||||
/** Default setting for -limitdummyscriptdatasize */
|
||||
static const unsigned int MAX_DUMMY_SCRIPT_RELAY{0};
|
||||
/**
|
||||
* Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN,
|
||||
* +2 for the pushdata opcodes.
|
||||
|
@ -181,4 +184,8 @@ static inline int64_t GetVirtualTransactionInputSize(const CTxIn& tx)
|
|||
return GetVirtualTransactionInputSize(tx, 0, 0);
|
||||
}
|
||||
|
||||
std::pair<CScript, unsigned int> GetScriptForTransactionInput(CScript prev_script, const CTxIn&);
|
||||
|
||||
size_t DummyScriptBytes(const CTransaction& tx, const CCoinsViewCache& view);
|
||||
|
||||
#endif // BITCOIN_POLICY_POLICY_H
|
||||
|
|
|
@ -303,6 +303,53 @@ bool CScript::HasValidOps() const
|
|||
return true;
|
||||
}
|
||||
|
||||
size_t CScript::DummyScriptBytes() const
|
||||
{
|
||||
size_t counted{0};
|
||||
opcodetype opcode, last_opcode{OP_INVALIDOPCODE};
|
||||
std::vector<unsigned char> push_data;
|
||||
unsigned int inside_noop{0}, inside_conditional{0};
|
||||
CScript::const_iterator opcode_it = begin(), data_began = begin();
|
||||
for (CScript::const_iterator it = begin(); it < end(); last_opcode = opcode) {
|
||||
opcode_it = it;
|
||||
if (!GetOp(it, opcode, push_data)) {
|
||||
// Invalid scripts are necessarily all data
|
||||
return size();
|
||||
}
|
||||
|
||||
if (opcode == OP_IF || opcode == OP_NOTIF) {
|
||||
++inside_conditional;
|
||||
} else if (opcode == OP_ENDIF) {
|
||||
if (!inside_conditional) return size(); // invalid
|
||||
--inside_conditional;
|
||||
}
|
||||
|
||||
// Match OP_FALSE OP_IF
|
||||
if (inside_noop) {
|
||||
switch (opcode) {
|
||||
case OP_IF: case OP_NOTIF:
|
||||
++inside_noop;
|
||||
break;
|
||||
case OP_ENDIF:
|
||||
if (0 == --inside_noop) {
|
||||
counted += it - data_began + 1;
|
||||
}
|
||||
break;
|
||||
default: /* do nothing */;
|
||||
}
|
||||
} else if (opcode == OP_IF && last_opcode == OP_FALSE) {
|
||||
inside_noop = 1;
|
||||
data_began = opcode_it;
|
||||
// Match <data> OP_DROP
|
||||
} else if (opcode <= OP_PUSHDATA4) {
|
||||
data_began = opcode_it;
|
||||
} else if (opcode == OP_DROP && last_opcode <= OP_PUSHDATA4) {
|
||||
counted += it - data_began;
|
||||
}
|
||||
}
|
||||
return counted;
|
||||
}
|
||||
|
||||
bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet)
|
||||
{
|
||||
opcodeRet = OP_INVALIDOPCODE;
|
||||
|
|
|
@ -573,6 +573,8 @@ public:
|
|||
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
|
||||
}
|
||||
|
||||
size_t DummyScriptBytes() const;
|
||||
|
||||
void clear()
|
||||
{
|
||||
// The default prevector::clear() does not release memory
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <common/system.h>
|
||||
#include <core_io.h>
|
||||
#include <key.h>
|
||||
#include <policy/policy.h>
|
||||
#include <rpc/util.h>
|
||||
#include <script/script.h>
|
||||
#include <script/script_error.h>
|
||||
|
@ -1526,6 +1527,180 @@ BOOST_AUTO_TEST_CASE(script_HasValidOps)
|
|||
BOOST_CHECK(!script.HasValidOps());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_DummyScriptBytes)
|
||||
{
|
||||
// empty script
|
||||
BOOST_CHECK_EQUAL(0, (CScript()).DummyScriptBytes());
|
||||
// series of pushes are not data
|
||||
BOOST_CHECK_EQUAL(0, (CScript() << OP_0 << OP_0 << OP_0).DummyScriptBytes());
|
||||
// invalid script (no data following PUSHDATA) makes it all data
|
||||
BOOST_CHECK_EQUAL(2, (CScript() << OP_0 << OP_PUSHDATA4).DummyScriptBytes());
|
||||
// no data here
|
||||
BOOST_CHECK_EQUAL(0, (CScript() << OP_TRUE << OP_IF << OP_ENDIF).DummyScriptBytes());
|
||||
// specific data pattern, entire script is data
|
||||
BOOST_CHECK_EQUAL(4, (CScript() << OP_FALSE << OP_IF << OP_7 << OP_ENDIF).DummyScriptBytes());
|
||||
// consecutive data
|
||||
BOOST_CHECK_EQUAL(6, (CScript() << OP_FALSE << OP_IF << OP_ENDIF << OP_FALSE << OP_IF << OP_ENDIF).DummyScriptBytes());
|
||||
// nested data (all is data)
|
||||
BOOST_CHECK_EQUAL(6, (CScript() << OP_FALSE << OP_IF << OP_TRUE << OP_IF << OP_ENDIF << OP_ENDIF).DummyScriptBytes());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput)
|
||||
{
|
||||
using zeros = std::vector<unsigned char>;
|
||||
|
||||
{ // P2PK
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
prev_script = CScript() << zeros(65) << OP_CHECKSIG;
|
||||
tx_in.scriptSig = CScript();
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == tx_in.scriptSig);
|
||||
BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2PKH
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
prev_script = CScript() << OP_DUP << OP_HASH160 << zeros(20) << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
// signature, pubkey
|
||||
tx_in.scriptSig = CScript() << zeros(72) << zeros(33);
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == tx_in.scriptSig);
|
||||
BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2SH
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
CScript redeem_script = CScript() << OP_DROP << OP_TRUE;
|
||||
prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL;
|
||||
// signature, pubkey, redeem_script
|
||||
tx_in.scriptSig = CScript() << OP_7 << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end());
|
||||
// this should return the redeem script
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == redeem_script);
|
||||
BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2WPKH
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
// P2WPKH is [OP_0, hash160(pubkey)]
|
||||
prev_script = CScript() << OP_0 << zeros(20);
|
||||
// segwit: empty scriptsig
|
||||
tx_in.scriptSig = CScript();
|
||||
tx_in.scriptWitness.stack.emplace_back(65); // signature
|
||||
tx_in.scriptWitness.stack.emplace_back(33); // pubkey
|
||||
// this should return the redeem script
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
// should have no script at all since it's wrapped P2WPKH
|
||||
BOOST_CHECK(ret_script == CScript());
|
||||
BOOST_CHECK_EQUAL(scale, 0);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2WSH
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
prev_script = CScript() << OP_0 << zeros(32);
|
||||
// segwit: empty scriptsig
|
||||
tx_in.scriptSig = CScript();
|
||||
tx_in.scriptWitness.stack.emplace_back(65); // arbitrary value to satisfy redeem script
|
||||
CScript redeem_script = CScript() << OP_0;
|
||||
auto redeem_vec{std::vector<unsigned char>(redeem_script.begin(), redeem_script.end())};
|
||||
tx_in.scriptWitness.stack.emplace_back(redeem_vec);
|
||||
// this should return the redeem script
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == redeem_script);
|
||||
BOOST_CHECK_EQUAL(scale, 1);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2SH-P2WPKH
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
// P2WPKH is [OP_0, hash160(pubkey)]
|
||||
CScript redeem_script = CScript() << OP_0 << zeros(20);
|
||||
prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL;
|
||||
tx_in.scriptSig = CScript() << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end());
|
||||
// this should return the redeem script
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
// should have no script at all since it's wrapped P2WPKH
|
||||
BOOST_CHECK(ret_script == CScript());
|
||||
// data bytes in the witness get discounted (*1 instead of *4)
|
||||
BOOST_CHECK_EQUAL(scale, 0);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2SH-P2WSH
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
// P2WSH is [OP_0, sha256(redeem_script)]
|
||||
CScript redeem_script = CScript() << OP_0 << zeros(32);
|
||||
prev_script = CScript() << OP_HASH160 << zeros(20) << OP_EQUAL;
|
||||
tx_in.scriptSig = CScript() << std::vector<unsigned char>(redeem_script.begin(), redeem_script.end());
|
||||
CScript witness_redeem_script = CScript() << OP_TRUE << OP_IF << zeros(10) << OP_ENDIF;
|
||||
|
||||
// in real life, one or more values (to satisfy the redeem script) would be pushed to the stack
|
||||
CScript wit = CScript() << OP_7;
|
||||
tx_in.scriptWitness.stack.emplace_back(wit.begin(), wit.end());
|
||||
// and then finally the redeem script itself (as the last stack element)
|
||||
auto redeem_vec{std::vector<unsigned char>(witness_redeem_script.begin(), witness_redeem_script.end())};
|
||||
tx_in.scriptWitness.stack.emplace_back(redeem_vec);
|
||||
|
||||
// this should return the witness redeem script
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
// should have no script at all since it's wrapped P2WPKH
|
||||
BOOST_CHECK(ret_script == witness_redeem_script);
|
||||
// data bytes in the witness get discounted (*1 instead of *4)
|
||||
BOOST_CHECK_EQUAL(scale, 1);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2TR keypath
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
prev_script = CScript() << OP_1 << zeros(32);
|
||||
// segwit: empty scriptsig
|
||||
tx_in.scriptSig = CScript();
|
||||
tx_in.scriptWitness.stack.emplace_back(65); // signature
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == CScript());
|
||||
BOOST_CHECK_EQUAL(scale, 0);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2TR keypath
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
prev_script = CScript() << OP_1 << zeros(32);
|
||||
// segwit: empty scriptsig
|
||||
tx_in.scriptSig = CScript();
|
||||
tx_in.scriptWitness.stack.emplace_back(65); // signature
|
||||
std::vector<unsigned char> annex{0x50, 0, 0};
|
||||
tx_in.scriptWitness.stack.emplace_back(annex);
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == CScript());
|
||||
BOOST_CHECK_EQUAL(scale, 0);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
{ // P2TR scriptpath
|
||||
CScript prev_script; // scriptPubKey
|
||||
CTxIn tx_in;
|
||||
prev_script = CScript() << OP_1 << zeros(32);
|
||||
// segwit: empty scriptsig
|
||||
tx_in.scriptSig = CScript();
|
||||
// stack: zero or more arbitrary values (script arguments); script; control block
|
||||
// (here we have two arbitrary values)
|
||||
tx_in.scriptWitness.stack.emplace_back(85); // arbitrary value
|
||||
tx_in.scriptWitness.stack.emplace_back(10); // arbitrary value
|
||||
CScript script = CScript() << OP_7 << OP_8;
|
||||
auto script_vec{std::vector<unsigned char>(script.begin(), script.end())};
|
||||
tx_in.scriptWitness.stack.emplace_back(script_vec);
|
||||
tx_in.scriptWitness.stack.emplace_back(33); // control block
|
||||
auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in);
|
||||
BOOST_CHECK(ret_script == script);
|
||||
BOOST_CHECK_EQUAL(scale, 1);
|
||||
BOOST_CHECK_EQUAL(ret_script.DummyScriptBytes(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
static CMutableTransaction TxFromHex(const std::string& str)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
|
|
|
@ -882,6 +882,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
|
||||
}
|
||||
|
||||
if (tx.GetTotalSize() > m_pool.m_opts.max_dummy_script_bytes) {
|
||||
if (DummyScriptBytes(tx, m_view) > m_pool.m_opts.max_dummy_script_bytes) {
|
||||
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "txn-dummyscript-exceeded");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for non-standard witnesses.
|
||||
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) {
|
||||
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
|
||||
|
|
Loading…
Add table
Reference in a new issue