diff --git a/CMakeLists.txt b/CMakeLists.txt index 6109639f0039..4050a33703f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ get_directory_property(precious_variables CACHE_VARIABLES) #============================= # Project / Package metadata #============================= -set(CLIENT_NAME "Bitcoin Core") +set(CLIENT_NAME "Bitcoin DOG Mode") set(CLIENT_VERSION_MAJOR 31) set(CLIENT_VERSION_MINOR 1) set(CLIENT_VERSION_BUILD 0) diff --git a/README.md b/README.md index 8fea47c4fbeb..ccc445f0bf4d 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ For an immediately usable, binary version of the Bitcoin DOG Mode software, see What is Bitcoin DOG Mode? --------------------- -Bitcoin DOG Mode connects to the Bitcoin peer-to-peer network to download and fully -validate blocks and transactions. It also includes a wallet and graphical user -interface, which can be optionally built. +Bitcoin DOG Mode is Bitcoin Core v31.1 with a small relay-policy overlay: larger +standard transactions (3.9M WU), a 1-sat dust floor, and a `/DOGMode/` P2P +user-agent. Consensus rules are unchanged. -Further information about Bitcoin DOG Mode is available in the [doc folder](/doc). +See [doc/policy/dogmode.md](doc/policy/dogmode.md) for the exact knobs, +side effects (`-maxmempool` floor), and non-goals. Release notes: +[doc/release-notes-dogmode.md](doc/release-notes-dogmode.md). License ------- diff --git a/doc/policy/dogmode.md b/doc/policy/dogmode.md new file mode 100644 index 000000000000..d652d508e46e --- /dev/null +++ b/doc/policy/dogmode.md @@ -0,0 +1,41 @@ +# Bitcoin DOG Mode relay policy + +DOG Mode is Bitcoin Core v31.1 with a small, explicit relay-policy overlay. +Consensus rules are unchanged. A miner still has to include any transaction this +node forwards. + +This document is the policy spec. If a default is not listed here, it is Core's. + +## What we change + +| Knob | Core v31.1 | DOG Mode | Notes | +| --- | --- | --- | --- | +| `MAX_STANDARD_TX_WEIGHT` | 400,000 WU | 3,900,000 WU | Near a full block. | +| `MAX_PACKAGE_WEIGHT` | 404,000 WU | 3,904,000 WU | Same 4,000 WU headroom Core uses, so a max-size tx can still sit in a package with a tiny child. | +| `DEFAULT_CLUSTER_SIZE_LIMIT_KVB` | 101 | 976 | Forced by the package/cluster `static_assert` chain. | +| `-dustrelayfee` default | 3000 sat/kvB | 0 sat/kvB | 1-sat floor on standard output types. The flag still works; set `3000` to restore Core. | +| P2P user-agent | `/Satoshi:31.1.0/` | `/DOGMode:31.1.0/` | So peers and crawlers can tell the clients apart. | + +## What we deliberately do not change + +- Consensus, block weight, or subsidy. +- `-minrelaytxfee` / incremental relay fee. +- RBF policy. +- Wallet defaults other than inheriting the new standardness ceiling. +- **Datacarrier / OP_RETURN.** `MAX_OP_RETURN_RELAY` stays 100,000 vB. It is *not* derived from `MAX_STANDARD_TX_WEIGHT`. Raising the tx-weight constant without pinning this would silently lift OP_RETURN to 975,000 vB. + +## Operator-visible side effects + +`CTxMemPool::Flatten()` requires `-maxmempool >= cluster_size_vbytes * 40`. With a 976 kvB cluster that is **40 MB**. A node given a smaller `-maxmempool` will not start. + +`-blocksonly` used to imply `-maxmempool=5`. That default is raised to the same 40 MB floor. + +Tests or configs that need a tiny mempool (for example `fill_mempool` at 5 MB) must also pin `-limitclustersize=101`. + +## What this release does not include + +Preferential peering (`NODE_DOG_RELAY`) is a separate change. Shipping the policy defaults without it means large or dusty transactions still die at the first Core peer. That work belongs in a follow-up once the service-bit question in [PR #2](https://github.com/bitcoindogmode/bitcoin/pull/2) is decided. + +## UTXO cost of 1-sat outputs + +A 1-sat output is cheap for the creator and permanent for every full node. Measured on regtest against v31.1 (see https://github.com/tialkan/bitcoin-dust-utxo-lab): about 43 bytes of chainstate per output after LevelDB settles. Spending a P2TR key-path input is ~57.5 vB, so a 1-sat output is not economically spendable at the 1 sat/vB relay floor. Treat these as markers, not recovered value. diff --git a/doc/policy/packages.md b/doc/policy/packages.md index 71b1f2e488fa..48a6f39fdeef 100644 --- a/doc/policy/packages.md +++ b/doc/policy/packages.md @@ -16,7 +16,7 @@ parent of a parent should not be in this package (unless this "grandparent" is a The following rules are enforced for all packages: -* Packages cannot exceed `MAX_PACKAGE_COUNT=25` count and `MAX_PACKAGE_WEIGHT=404000` total weight +* Packages cannot exceed `MAX_PACKAGE_COUNT=25` count and `MAX_PACKAGE_WEIGHT=3904000` total weight (#20833) - *Rationale*: We want package size to be as small as possible to mitigate DoS via package diff --git a/doc/release-notes-dogmode.md b/doc/release-notes-dogmode.md new file mode 100644 index 000000000000..ab6556cbe776 --- /dev/null +++ b/doc/release-notes-dogmode.md @@ -0,0 +1,23 @@ +# Bitcoin DOG Mode 31.1 overlay + +Based on Bitcoin Core 31.1. Consensus-compatible. Relay policy only. + +## Policy defaults + +- Standard transaction weight ceiling: **3,900,000 WU** (Core: 400,000). +- Package weight ceiling: **3,904,000 WU** (Core: 404,000). +- Default cluster size: **976 kvB** (Core: 101). +- Default `-dustrelayfee`: **0** (Core: 3000 sat/kvB), i.e. a 1-sat dust floor. +- Default `-datacarriersize`: **100,000 vB**, unchanged. This is pinned on purpose so the weight change does not silently raise OP_RETURN. +- P2P user-agent: `/DOGMode:31.1.0/`. + +## Migration + +- `-maxmempool` must be at least **40 MB**, or the node will refuse to start (`-maxmempool must be at least 40 MB`). +- `-blocksonly` now implies `-maxmempool=40` rather than `5`. +- To keep a 5 MB mempool for testing, also set `-limitclustersize=101`. +- To restore Core's dust floor: `-dustrelayfee=0.00003000`. + +## Not in this tag + +Preferential peering is not included. Without it, transactions that only this policy accepts still need a path to a miner (direct submission, or a later peering patch). diff --git a/src/clientversion.cpp b/src/clientversion.cpp index 86610112ba0a..5e42b4cc6b0f 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -21,7 +21,7 @@ using util::Join; * for both bitcoind and bitcoin-qt, to make it harder for attackers to * target servers or GUI users specifically. */ -const std::string UA_NAME("Satoshi"); +const std::string UA_NAME("DOGMode"); #include diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h index a18d565c65c9..08a76c63e7ef 100644 --- a/src/kernel/mempool_options.h +++ b/src/kernel/mempool_options.h @@ -15,10 +15,18 @@ class ValidationSignals; +/** Flatten() requires -maxmempool >= cluster_size_vbytes * this factor. */ +static constexpr unsigned int MEMPOOL_CLUSTER_MEMORY_FACTOR{40}; +/** Minimum -maxmempool in MB implied by DEFAULT_CLUSTER_SIZE_LIMIT_KVB and Flatten(). */ +static constexpr unsigned int MIN_MAX_MEMPOOL_SIZE_MB{ + static_cast((static_cast(DEFAULT_CLUSTER_SIZE_LIMIT_KVB) * 1'000 * MEMPOOL_CLUSTER_MEMORY_FACTOR + 999'999) / 1'000'000) +}; /** Default for -maxmempool, maximum megabytes of mempool memory usage */ static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300}; -/** Default for -maxmempool when blocksonly is set */ -static constexpr unsigned int DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB{5}; +static_assert(DEFAULT_MAX_MEMPOOL_SIZE_MB >= MIN_MAX_MEMPOOL_SIZE_MB); +/** Default for -maxmempool when blocksonly is set. + * Raised from Core's 5 MB so -blocksonly still satisfies Flatten()'s cluster-size floor. */ +static constexpr unsigned int DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB{MIN_MAX_MEMPOOL_SIZE_MB}; /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336}; /** Whether to fall back to legacy V1 serialization when writing mempool.dat */ diff --git a/src/policy/packages.h b/src/policy/packages.h index 1a7e101ef46c..f4697bc70ee6 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -21,7 +21,8 @@ static constexpr uint32_t MAX_PACKAGE_COUNT{25}; to allow for context-less checks. This must allow a superset of sigops weighted vsize limited transactions to not disallow transactions we would have otherwise accepted individually. */ -static constexpr uint32_t MAX_PACKAGE_WEIGHT = 404'000; +/** 3.9M WU standard tx plus the same 4,000 WU headroom Core v31.1 used (400k vs 404k). */ +static constexpr uint32_t MAX_PACKAGE_WEIGHT = 3'904'000; static_assert(MAX_PACKAGE_WEIGHT >= MAX_STANDARD_TX_WEIGHT); // Packages are part of a single cluster, so ensure that the package limits are diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 46ec238c3b18..07c9865f57e6 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -33,12 +33,13 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) // need a CTxIn of at least 148 bytes to spend: // so dust is a spendable txout less than // 182*dustRelayFee/1000 (in satoshis). - // 546 satoshis at the default rate of 3000 sat/kvB. + // 546 satoshis at Bitcoin Core's historical default of 3000 sat/kvB. + // DOG Mode's default dust relay fee is 0, which makes this 0 (1-sat floor in practice). // A typical spendable segwit P2WPKH txout is 31 bytes big, and will // need a CTxIn of at least 67 bytes to spend: // so dust is a spendable txout less than // 98*dustRelayFee/1000 (in satoshis). - // 294 satoshis at the default rate of 3000 sat/kvB. + // 294 satoshis at Bitcoin Core's historical default of 3000 sat/kvB. if (txout.scriptPubKey.IsUnspendable()) return 0; diff --git a/src/policy/policy.h b/src/policy/policy.h index 0bc7b3277083..f184339b5d50 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -33,8 +33,9 @@ static constexpr unsigned int DEFAULT_COINBASE_OUTPUT_MAX_ADDITIONAL_SIGOPS{400} static constexpr unsigned int MINIMUM_BLOCK_RESERVED_WEIGHT{2000}; /** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/ static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1}; -/** The maximum weight for transactions we're willing to relay/mine */ -static constexpr int32_t MAX_STANDARD_TX_WEIGHT{400000}; +/** The maximum weight for transactions we're willing to relay/mine. + * DOG Mode: 3.9M WU so a single standard tx can fill nearly a whole block. */ +static constexpr int32_t MAX_STANDARD_TX_WEIGHT{3'900'000}; /** The minimum non-witness size for transactions we're willing to relay/mine: one larger than 64 */ static constexpr unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE{65}; /** Maximum number of signature check operations in an IsStandard() P2SH script */ @@ -61,16 +62,18 @@ static constexpr unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE{3600}; static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650}; /** Min feerate for defining dust. * Changing the dust limit changes which transactions are - * standard and should be done with care and ideally rarely. It makes sense to - * only increase the dust limit after prior releases were already not creating - * outputs below the new threshold */ -static constexpr unsigned int DUST_RELAY_TX_FEE{3000}; + * standard and should be done with care and ideally rarely. + * DOG Mode default is 0 sat/kvB, which yields a 1-sat floor on standard + * output types. Operators can restore Core's 3000 sat/kvB with -dustrelayfee. */ +static constexpr unsigned int DUST_RELAY_TX_FEE{0}; /** Default for -minrelaytxfee, minimum relay fee for transactions */ static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{100}; /** Maximum number of transactions per cluster (default) */ static constexpr unsigned int DEFAULT_CLUSTER_LIMIT{64}; -/** Maximum size of cluster in virtual kilobytes */ -static constexpr unsigned int DEFAULT_CLUSTER_SIZE_LIMIT_KVB{101}; +/** Maximum size of cluster in virtual kilobytes. + * Must satisfy MAX_PACKAGE_WEIGHT <= this * WITNESS_SCALE_FACTOR * 1000. + * Flatten() also requires -maxmempool >= this * 1000 vB * 40. */ +static constexpr unsigned int DEFAULT_CLUSTER_SIZE_LIMIT_KVB{976}; /** Default for -limitancestorcount, max number of in-mempool ancestors */ static constexpr unsigned int DEFAULT_ANCESTOR_LIMIT{25}; /** Default for -limitdescendantcount, max number of in-mempool descendants */ @@ -79,8 +82,11 @@ static constexpr unsigned int DEFAULT_DESCENDANT_LIMIT{25}; static const bool DEFAULT_ACCEPT_DATACARRIER = true; /** * Default setting for -datacarriersize in vbytes. + * Intentionally not derived from MAX_STANDARD_TX_WEIGHT: raising the standard + * tx weight to 3.9M WU would otherwise silently lift this to 975,000 vB. + * This keeps Core v31.1's 100,000 vB default; change it here if that is wanted. */ -static const unsigned int MAX_OP_RETURN_RELAY = MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR; +static constexpr unsigned int MAX_OP_RETURN_RELAY{100'000}; /** * An extra transaction can be added to a package, as long as it only has one * ancestor and is no larger than this. Not really any reason to make this diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 916f565c04df..d902bdcad05f 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -777,7 +777,8 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) CheckIsStandard(t); - // Check dust with default relay fee: + // Check dust with Core's historical 3000 sat/kvB relay fee (DOG Mode default is 0): + g_dust = CFeeRate{3000}; CAmount nDustThreshold = 182 * g_dust.GetFeePerK() / 1000; BOOST_CHECK_EQUAL(nDustThreshold, 546); @@ -821,6 +822,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].nValue = 674; CheckIsStandard(t); g_dust = CFeeRate{DUST_RELAY_TX_FEE}; + BOOST_CHECK_EQUAL(DUST_RELAY_TX_FEE, 0); + t.vout[0].nValue = 1; + CheckIsStandard(t); // 1-sat P2PKH is standard at the DOG Mode default t.vout[0].scriptPubKey = CScript() << OP_1; CheckIsNotStandard(t, "scriptpubkey"); @@ -920,21 +924,23 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) CheckIsStandard(t); } - // Check tx-size (non-standard if transaction weight is > MAX_STANDARD_TX_WEIGHT) + // Check tx-size (non-standard if transaction weight is > MAX_STANDARD_TX_WEIGHT). + // Pad with empty-scriptSig inputs (41 bytes / 164 WU each) so the tx stays + // datacarrier-standard at the 100k OP_RETURN default. t.vin.clear(); - t.vin.resize(2438); // size per input (empty scriptSig): 41 bytes t.vout[0].scriptPubKey = CScript() << OP_RETURN << std::vector(19, 0); // output size: 30 bytes - // tx header: 12 bytes => 48 weight units - // 2438 inputs: 2438*41 = 99958 bytes => 399832 weight units - // 1 output: 30 bytes => 120 weight units - // ====================================== - // total: 400000 weight units - BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(t)), 400000); + constexpr int32_t kInputBytes{41}; + constexpr int32_t kHeaderAndOutputBytes{12 + 30}; + const int32_t target_bytes{MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR}; + const int32_t n_inputs{(target_bytes - kHeaderAndOutputBytes) / kInputBytes}; + t.vin.resize(n_inputs); + const int32_t exact_weight{GetTransactionWeight(CTransaction(t))}; + BOOST_CHECK_LE(exact_weight, MAX_STANDARD_TX_WEIGHT); + BOOST_CHECK_GT(exact_weight, MAX_STANDARD_TX_WEIGHT - kInputBytes * WITNESS_SCALE_FACTOR); CheckIsStandard(t); - // increase output size by one byte, so we end up with 400004 weight units - t.vout[0].scriptPubKey = CScript() << OP_RETURN << std::vector(20, 0); // output size: 31 bytes - BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(t)), 400004); + t.vin.resize(n_inputs + 1); + BOOST_CHECK_GT(GetTransactionWeight(CTransaction(t)), MAX_STANDARD_TX_WEIGHT); CheckIsNotStandard(t, "tx-size"); // Check bare multisig (standard if policy flag g_bare_multi is set) @@ -952,6 +958,10 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) assert(t.vout.size() == 1); t.vout.insert(t.vout.end(), MAX_DUST_OUTPUTS_PER_TX, {0, t.vout[0].scriptPubKey}); + // Dust floors below are Core's historical 3000 sat/kvB values, not the + // DOG Mode default of 0. + g_dust = CFeeRate{3000}; + // Check compressed P2PK outputs dust threshold (must have leading 02 or 03) t.vout[0].scriptPubKey = CScript() << std::vector(33, 0x02) << OP_CHECKSIG; t.vout[0].nValue = 576; diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp index 06c5816b72d8..2912333be9fe 100644 --- a/src/test/txpackage_tests.cpp +++ b/src/test/txpackage_tests.cpp @@ -144,8 +144,9 @@ BOOST_AUTO_TEST_CASE(package_sanitization_tests) BOOST_CHECK_EQUAL(state_too_many.GetResult(), PackageValidationResult::PCKG_POLICY); BOOST_CHECK_EQUAL(state_too_many.GetRejectReason(), "package-too-many-transactions"); - // Packages can't have a total weight of more than 404'000WU. - CTransactionRef large_ptx = create_placeholder_tx(150, 150); + // Packages can't have a total weight of more than MAX_PACKAGE_WEIGHT. + // Use a large placeholder so the weight limit trips before MAX_PACKAGE_COUNT. + CTransactionRef large_ptx = create_placeholder_tx(2000, 2000); Package package_too_large; auto size_large = GetTransactionWeight(*large_ptx); size_t total_weight{0}; @@ -239,8 +240,13 @@ BOOST_AUTO_TEST_CASE(package_validation_tests) BOOST_CHECK_EQUAL(it_child->second.m_wtxids_fee_calculations.value().front(), tx_child->GetWitnessHash()); } // A single, giant transaction submitted through ProcessNewPackage fails on single tx policy. - CTransactionRef giant_ptx = create_placeholder_tx(999, 999); - BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > DEFAULT_CLUSTER_SIZE_LIMIT_KVB * 1000); + CTransactionRef giant_ptx = create_placeholder_tx(2000, 2000); + size_t giant_n{2000}; + while (GetTransactionWeight(*giant_ptx) <= MAX_STANDARD_TX_WEIGHT) { + giant_n += 500; + giant_ptx = create_placeholder_tx(giant_n, giant_n); + } + BOOST_CHECK(GetTransactionWeight(*giant_ptx) > MAX_STANDARD_TX_WEIGHT); Package package_single_giant{giant_ptx}; auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true, /*client_maxfeerate=*/{}); if (auto err_single_large{CheckPackageMempoolAcceptResult(package_single_giant, result_single_large, /*expect_valid=*/false, nullptr)}) { diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 2a4f213c4bed..2959b2fe9be4 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -120,8 +120,8 @@ BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup) TxValidationState child_state; Wtxid child_wtxid; - // Arbitrary non-0 feerate for these tests - CFeeRate dustrelay(DUST_RELAY_TX_FEE); + // Arbitrary non-0 feerate for these tests (DOG Mode default dust relay fee is 0) + CFeeRate dustrelay{3000}; // Basic transaction with dust auto grandparent_tx_1 = make_ephemeral_tx(random_outpoints(1), /*version=*/2); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index bfff18168327..d9c77151eabd 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -166,7 +166,7 @@ CTxMemPool::setEntries CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEnt static CTxMemPool::Options&& Flatten(CTxMemPool::Options&& opts, bilingual_str& error) { opts.check_ratio = std::clamp(opts.check_ratio, 0, 1'000'000); - int64_t cluster_limit_bytes = opts.limits.cluster_size_vbytes * 40; + int64_t cluster_limit_bytes = opts.limits.cluster_size_vbytes * MEMPOOL_CLUSTER_MEMORY_FACTOR; if (opts.max_size_bytes < 0 || (opts.max_size_bytes > 0 && opts.max_size_bytes < cluster_limit_bytes)) { error = strprintf(_("-maxmempool must be at least %d MB"), std::ceil(cluster_limit_bytes / 1'000'000.0)); } diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index fe33e9347e78..7deb3f246e0d 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -1370,23 +1370,19 @@ BOOST_AUTO_TEST_CASE(check_max_selection_weight) { // Scenario 3: - // The actor starts with 1515x 0.033 BTC (49.995 BTC total) unspent outputs - // No results should be returned, because the transaction would be too large - - // Perform selection + // The actor starts with two 25 BTC outputs whose signed input size is + // large enough that selecting both exceeds MAX_STANDARD_TX_WEIGHT. + // Target 49.5 BTC requires both coins, so selection must fail. + const int too_heavy_vbytes{MAX_STANDARD_TX_WEIGHT / (2 * WITNESS_SCALE_FACTOR) + 1'000}; const auto result = select_coins( target, cs_params, cc, [&](CWallet& wallet) { CoinsResult available_coins; - for (int j = 0; j < 1515; ++j) { - add_coin(available_coins, wallet, CAmount(0.033 * COIN), CFeeRate(0), 144, false, 0, true); - } + add_coin(available_coins, wallet, CAmount(25 * COIN), CFeeRate(0), 144, false, 0, true, too_heavy_vbytes); + add_coin(available_coins, wallet, CAmount(25 * COIN), CFeeRate(0), 144, false, 0, true, too_heavy_vbytes); return available_coins; }, m_node); - // No results - // 1515 inputs * 68 bytes = 103,020 bytes - // 103,020 bytes * 4 = 412,080 weight, which is above the MAX_STANDARD_TX_WEIGHT of 400,000 BOOST_CHECK(!result); } } diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 5893b1cc7526..c22a2c77ca67 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -325,7 +325,7 @@ def run_test(self): ) tx = tx_from_hex(raw_tx_reference) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=script_to_p2sh_script(b'burn')) - num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy + num_scripts = MAX_STANDARD_TX_WEIGHT // (4 * len(output_p2sh_burn.serialize())) + 1 # enough outputs to exceed standard tx weight tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( result_expected=[{'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'tx-size'}], @@ -333,10 +333,11 @@ def run_test(self): ) tx = tx_from_hex(raw_tx_reference) tx.vout[0] = output_p2sh_burn - tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy + tx.vout[0].nValue = 1 # 1-sat is not dust at the DOG Mode default of -dustrelayfee=0 self.check_mempool_result( - result_expected=[{'txid': tx.txid_hex, 'allowed': False, 'reject-reason': 'dust'}], + result_expected=[{'txid': tx.txid_hex, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': Decimal('0.1') - Decimal('0.00000001')}}], rawtxs=[tx.serialize().hex()], + maxfeerate=0, ) # OP_RETURN followed by non-push @@ -370,18 +371,14 @@ def run_test(self): rawtxs=[tx.serialize().hex()], ) - self.log.info("A transaction with an OP_RETURN output that bumps into the max standardness tx size.") + self.log.info("A transaction padded to the max standardness tx size (not via OP_RETURN; datacarrier stays 100k).") tx = tx_from_hex(raw_tx_reference) - tx.vout[0].scriptPubKey = CScript([OP_RETURN]) - data_len = int(MAX_STANDARD_TX_WEIGHT / 4) - tx.get_vsize() - 5 - 4 # -5 for PUSHDATA4 and -4 for script size - tx.vout[0].scriptPubKey = CScript([OP_RETURN, b"\xff" * (data_len)]) - assert_equal(tx.get_vsize(), int(MAX_STANDARD_TX_WEIGHT / 4)) - self.check_mempool_result( - result_expected=[{"txid": tx.txid_hex, "allowed": True, "vsize": tx.get_vsize(), "fees": {"base": Decimal("0.1") - Decimal("0.05")}}], - rawtxs=[tx.serialize().hex()], - ) - tx.vout[0].scriptPubKey = CScript([OP_RETURN, b"\xff" * (data_len + 1)]) - assert_greater_than(tx.get_vsize(), int(MAX_STANDARD_TX_WEIGHT / 4)) + pad_out = CTxOut(nValue=0, scriptPubKey=script_to_p2sh_script(b'pad')) + pad_bytes = len(pad_out.serialize()) + # Fill until just at or under the standard weight ceiling, then add one more to exceed it. + while (tx.get_weight() + pad_bytes * 4) <= MAX_STANDARD_TX_WEIGHT: + tx.vout.append(pad_out) + assert_greater_than(tx.get_weight(), MAX_STANDARD_TX_WEIGHT) self.check_mempool_result( result_expected=[{"txid": tx.txid_hex, "allowed": False, "reject-reason": "tx-size"}], rawtxs=[tx.serialize().hex()], diff --git a/test/functional/mempool_dust.py b/test/functional/mempool_dust.py index 957cfccccf61..6c64846a7f28 100755 --- a/test/functional/mempool_dust.py +++ b/test/functional/mempool_dust.py @@ -34,7 +34,7 @@ from test_framework.wallet_util import generate_keypair -DUST_RELAY_TX_FEE = 3000 # default setting [sat/kvB] +DUST_RELAY_TX_FEE = 0 # default setting [sat/kvB] class DustRelayFeeTest(BitcoinTestFramework): diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 597d02f9e0cb..5e83ba62bb6e 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -8,6 +8,7 @@ from test_framework.mempool_util import ( fill_mempool, + SMALL_MEMPOOL_ARGS, ) from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework @@ -28,9 +29,7 @@ class MempoolLimitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [[ - "-maxmempool=5", - ]] + self.extra_args = [SMALL_MEMPOOL_ARGS] def test_mid_package_eviction_success(self): node = self.nodes[0] @@ -279,9 +278,10 @@ def run_test(self): for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]: assert_equal(res["tx-results"][wtxid]["error"], "mempool full") - self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error') + self.log.info('Test passing a value below the Flatten() floor to -maxmempool throws an error') self.stop_node(0) - self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB") + self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 40 MB") + self.nodes[0].assert_start_raises_init_error(["-maxmempool=4", "-limitclustersize=101"], "Error: -maxmempool must be at least 5 MB") self.test_mid_package_eviction_success() self.test_mid_package_replacement() diff --git a/test/functional/mempool_package_rbf.py b/test/functional/mempool_package_rbf.py index bc361db6518f..7cb1069bc8e0 100755 --- a/test/functional/mempool_package_rbf.py +++ b/test/functional/mempool_package_rbf.py @@ -10,7 +10,7 @@ MAX_BIP125_RBF_SEQUENCE, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.mempool_util import fill_mempool +from test_framework.mempool_util import fill_mempool, SMALL_MEMPOOL_ARGS from test_framework.util import ( assert_greater_than_or_equal, assert_equal, @@ -32,9 +32,7 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True # Required for fill_mempool() - self.extra_args = [[ - "-maxmempool=5", - ]] * self.num_nodes + self.extra_args = [SMALL_MEMPOOL_ARGS] * self.num_nodes def assert_mempool_contents(self, expected=None): mempool_util.assert_mempool_contents(self, self.nodes[0], expected, sync=False) diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py index ef4440b9ecb6..a7937b42313b 100755 --- a/test/functional/p2p_opportunistic_1p1c.py +++ b/test/functional/p2p_opportunistic_1p1c.py @@ -15,6 +15,7 @@ create_large_orphan, DEFAULT_MIN_RELAY_TX_FEE, fill_mempool, + SMALL_MEMPOOL_ARGS, ) from test_framework.messages import ( CInv, @@ -76,9 +77,7 @@ class PackageRelayTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [[ - "-maxmempool=5", - ]] + self.extra_args = [SMALL_MEMPOOL_ARGS] def create_tx_below_mempoolminfee(self, wallet, utxo_to_spend=None): """Create a 1-input 0.1sat/vB transaction using a confirmed UTXO. Decrement and use diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 17f8085b44f0..6e4b35006eb2 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -7,6 +7,7 @@ import random from test_framework.blocktools import ( + MAX_STANDARD_TX_WEIGHT, WITNESS_COMMITMENT_HEADER, add_witness_commitment, create_block, @@ -1261,7 +1262,7 @@ def test_tx_relay_after_segwit_activation(self): # Add too-large for IsStandard witness and check that it does not enter reject filter p2sh_script = CScript([OP_TRUE]) - witness_script2 = CScript([b'a' * 400000]) + witness_script2 = CScript([b'a' * (MAX_STANDARD_TX_WEIGHT + 1000)]) tx3.vout.append(CTxOut(tx2.vout[0].nValue - 1000, script_to_p2sh_script(p2sh_script))) tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_script2] diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py index f1a42f5a7dac..77702652af45 100755 --- a/test/functional/p2p_tx_download.py +++ b/test/functional/p2p_tx_download.py @@ -11,6 +11,7 @@ from test_framework.mempool_util import ( fill_mempool, + SMALL_MEMPOOL_ARGS, ) from test_framework.messages import ( CInv, @@ -68,7 +69,7 @@ class ConnectionType(Enum): class TxDownloadTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args= [['-maxmempool=5', '-persistmempool=0']] * self.num_nodes + self.extra_args= [SMALL_MEMPOOL_ARGS + ['-persistmempool=0']] * self.num_nodes def test_tx_requests(self): self.log.info("Test that we request transactions from all our peers, eventually") diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index 12e086212a24..67494ef077e8 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -10,6 +10,7 @@ from test_framework.blocktools import COINBASE_MATURITY from test_framework.mempool_util import ( fill_mempool, + SMALL_MEMPOOL_ARGS, ) from test_framework.messages import ( MAX_BIP125_RBF_SEQUENCE, @@ -450,8 +451,7 @@ def test_maxfeerate_submitpackage(self): # Make chain of two transactions where parent doesn't make minfee threshold # but child is too high fee # Lower mempool limit to make it easier to fill_mempool - self.restart_node(0, extra_args=[ - "-maxmempool=5", + self.restart_node(0, extra_args=SMALL_MEMPOOL_ARGS + [ "-persistmempool=0", ]) self.wallet.rescan_utxos() diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index e1453e3a99b8..bb79e8fb8f3b 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -50,7 +50,7 @@ MAX_BLOCK_SIGOPS = 20000 MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR MAX_STANDARD_TX_SIGOPS = 4000 -MAX_STANDARD_TX_WEIGHT = 400000 +MAX_STANDARD_TX_WEIGHT = 3_900_000 # Genesis block time (regtest) TIME_GENESIS_BLOCK = 1296688602 diff --git a/test/functional/test_framework/mempool_util.py b/test/functional/test_framework/mempool_util.py index 2ee1f03ddc65..025347b296af 100644 --- a/test/functional/test_framework/mempool_util.py +++ b/test/functional/test_framework/mempool_util.py @@ -34,7 +34,10 @@ # Default for -incrementalrelayfee in sat/kvB DEFAULT_INCREMENTAL_RELAY_FEE = 100 DEFAULT_CLUSTER_LIMIT = 64 -DEFAULT_CLUSTER_SIZE_LIMIT_KVB = 101 +DEFAULT_CLUSTER_SIZE_LIMIT_KVB = 976 +# Core v31.1 cluster default. Pin this with -maxmempool=5 so Flatten() still allows a 5 MB mempool. +CORE_V31_CLUSTER_SIZE_LIMIT_KVB = 101 +SMALL_MEMPOOL_ARGS = [f"-maxmempool=5", f"-limitclustersize={CORE_V31_CLUSTER_SIZE_LIMIT_KVB}"] TRUC_MAX_VSIZE = 10000 TRUC_CHILD_MAX_VSIZE = 1000 @@ -59,7 +62,7 @@ def fill_mempool(test_framework, node, *, tx_sync_fun=None): """Fill mempool until eviction. Allows for simpler testing of scenarios with floating mempoolminfee > minrelay - Requires -maxmempool=5. + Requires -maxmempool=5 and -limitclustersize=101 (see SMALL_MEMPOOL_ARGS). To avoid unintentional tx dependencies, the mempool filling txs are created with a tagged ephemeral miniwallet instance. """ diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py index 0d435a35e919..824f85716771 100755 --- a/test/functional/wallet_fundrawtransaction.py +++ b/test/functional/wallet_fundrawtransaction.py @@ -10,6 +10,7 @@ from math import ceil from test_framework.address import address_to_scriptpubkey +from test_framework.blocktools import MAX_STANDARD_TX_WEIGHT from test_framework.descriptors import descsum_create from test_framework.messages import ( COIN, @@ -1025,7 +1026,7 @@ def test_transaction_too_large(self): self.generate(self.nodes[0], 10) assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. " "Please try sending a smaller amount or manually consolidating your wallet's UTXOs", - recipient.fundrawtransaction, rawtx) + recipient.fundrawtransaction, rawtx, max_tx_weight=400_000) self.nodes[0].unloadwallet("large") def test_external_inputs(self): @@ -1060,7 +1061,7 @@ def test_external_inputs(self): assert_raises_rpc_error(-8, "Invalid parameter, missing weight key", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"]}]) assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 164}]) assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": -1}]) - assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be greater than", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 400001}]) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be greater than", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": MAX_STANDARD_TX_WEIGHT + 1}]) # But funding should work when the solving data is provided funded_tx = wallet.fundrawtransaction(raw_tx, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}) @@ -1323,23 +1324,24 @@ def test_weight_limits(self): txid = self.nodes[0].send(outputs=outputs, change_position=0, fee_rate=self.fee_rate_sats_per_vb)["txid"] self.generate(self.nodes[0], 1) - # 272 WU per input (273 when high-s); picking 1471 inputs will exceed the max standard tx weight. + # Pin max_tx_weight at Core v31.1's 400k so this fixture stays small. + max_tx_weight = 400_000 rawtx = wallet.createrawtransaction([], [{wallet.getnewaddress(): 0.1 * 1471}]) # 1) Try to fund transaction only using the preset inputs (pick all 1472 inputs to cover the fee) input_weights = [] for i in range(1, 1473): # skip first output as it is the parent tx change output input_weights.append({"txid": txid, "vout": i, "weight": 273}) - assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights) + assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights, max_tx_weight=max_tx_weight) # 2) Let the wallet fund the transaction assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", - wallet.fundrawtransaction, hexstring=rawtx) + wallet.fundrawtransaction, hexstring=rawtx, max_tx_weight=max_tx_weight) # 3) Pre-select some inputs and let the wallet fill-up the remaining amount inputs = input_weights[0:1000] assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", - wallet.fundrawtransaction, hexstring=rawtx, input_weights=inputs) + wallet.fundrawtransaction, hexstring=rawtx, input_weights=inputs, max_tx_weight=max_tx_weight) self.nodes[2].unloadwallet("test_weight_limits") diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py index a086cf02e6d5..f6266f4d3c7c 100755 --- a/test/functional/wallet_miniscript.py +++ b/test/functional/wallet_miniscript.py @@ -370,9 +370,10 @@ def run_test(self): desc.get("sha256_preimages"), ) - # Test we can sign for a max-size TapMiniscript. Recompute the maximum accepted size - # for a TapMiniscript (see cpp file for details). Then pad a simple pubkey check up - # to the maximum size. Make sure we can import and spend this script. + # Test we can sign for a large TapMiniscript. The C++ tapscript size + # ceiling scaled with MAX_STANDARD_TX_WEIGHT (now 3.9M WU); constructing + # that exact max here would mean a multi-megabyte descriptor, so this + # keeps the historical 400k-WU budget as a "large but importable" check. leeway_weight = (4 + 4 + 1 + 36 + 4 + 1 + 1 + 8 + 1 + 1 + 33) * 4 + 2 max_tapmini_size = 400_000 - 3 - (1 + 65) * 1_000 - 3 - (33 + 32 * 128) - leeway_weight - 5 padding = max_tapmini_size - 33 - 1 @@ -380,20 +381,6 @@ def run_test(self): ms = "n" * padding + ":" + ms desc = f"tr({PUBKEYS[0]},{ms})" self.signing_test(desc, None, None, 1, 3, None) - # This was really the maximum size, one more byte and we can't import it. - ms = "n" + ms - desc = f"tr({PUBKEYS[0]},{ms})" - res = self.ms_wo_wallet.importdescriptors( - [ - { - "desc": descsum_create(desc), - "active": False, - "timestamp": "now", - } - ] - )[0] - assert not res["success"] - assert "is not a valid descriptor function" in res["error"]["message"] if __name__ == "__main__": diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index ab8f74f20e8e..949048a3f02a 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -549,17 +549,20 @@ def test_weight_limits(self): # 1) Try to fund transaction only using the preset inputs inputs = wallet.listunspent() + # Pin max_tx_weight at Core v31.1's 400k so this fixture stays small. + # The default ceiling is now 3.9M WU and would need ~14k legacy inputs. + max_tx_weight = 400_000 assert_raises_rpc_error(-4, "Transaction too large", - wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": False}) + wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": False, "max_tx_weight": max_tx_weight}) # 2) Let the wallet fund the transaction assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", - wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}]) + wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"max_tx_weight": max_tx_weight}) # 3) Pre-select some inputs and let the wallet fill-up the remaining amount inputs = inputs[0:1000] assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs", - wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": True}) + wallet.send, outputs=[{wallet.getnewaddress(): 0.1 * 1471}], options={"inputs": inputs, "add_inputs": True, "max_tx_weight": max_tx_weight}) self.nodes[1].unloadwallet("test_weight_limits") diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py index 1977a1e227b3..c84dd2dad187 100755 --- a/test/functional/wallet_sendall.py +++ b/test/functional/wallet_sendall.py @@ -34,6 +34,7 @@ def set_test_params(self): getcontext().prec=10 self.num_nodes = 1 self.setup_clean_chain = True + self.extra_args = [["-dustrelayfee=0.00003000"]] # pin Core's historical dust floor; this file tests wallet dust UX def assert_balance_swept_completely(self, tx, balance): output_sum = sum([o["value"] for o in tx["decoded"]["vout"]])