Thursday, October 30, 2025
HomeBitcoinbitcoin core - Would a "Bulk Mud" relay/consensus rule (limiting 100+ sub-1,000-sat...

bitcoin core – Would a “Bulk Mud” relay/consensus rule (limiting 100+ sub-1,000-sat outputs, plus a ratio test) be efficient with out unfavourable results?

I’m exploring a possible resolution to discourage UTXO-bloat patterns with out touching Script or witness semantics. This rule merely flags “bulk mud” transactions that create many very low-value outputs:

  • Threshold: no less than 100 outputs with worth < 1,000 sats
  • Ratio: these “tiny” (sub-1,000 sat) outputs are ≥ 60% of all outputs within the Tx

The purpose isn’t to remove all arbitrary knowledge makes use of, however to lift prices for patterns most related to UTXO development (low cost, “bulk mud” Txs), whereas leaving typical funds, channel opens, and one-off inscriptions unaffected.

Patterns this is able to restrict (not essentially remove)

  • Pretend pubkeys hiding knowledge (UTXO fan-outs)
  • Bitcoin STAMPS / UTXO-art utilizing many mud UTXOs
  • BRC-20 batch mints that fan out tiny outputs
  • Some batched Ordinal inscriptions that unfold knowledge/state throughout many small UTXOs
  • Mud bombing (monitoring) / UTXO squatting / resource-exhaustion makes an attempt
  • Mass micro-airdrops of sub-1k-sat outputs (collateral impact)

Non-goals / Not coated

  • Single/few-output inscriptions with massive witness knowledge (these wouldn’t set off the “bulk mud” heuristic)
  • Any scheme that merely makes use of ≥1,000 sat per output (economically dearer however nonetheless legitimate)

Why a ratio + rely?

Requiring each (tiny_count ≥ 100) and (tiny_count / total_outputs ≥ 0.6) reduces false positives (e.g., huge custodial batch payouts with a mixture of values). It focuses on transactions which are largely fabricated from dust-like outputs.

Ask

  • Are there credible, non-spam use-cases that want ≥100 sub-1k-sat outputs with ≥60% tiny ratio in a single tx?
  • Are there coverage pitfalls I’m lacking (e.g., charge market dynamics, odd coinbase behaviors, privateness instruments)?
  • Any prior artwork or measurements you may level to in regards to the prevalence of such transactions?

(with syntax highlighting right here: https://pastebin.com/tYsvDh2R)

RELAY POLICY FILTER sketch

// Place in /coverage/coverage.cpp, and name from inside IsStandardTx() earlier than returning:
//     if (IsBulkDust(tx, purpose))
//         return false;   // reject as nonstandard


bool IsBulkDust(const CTransaction& tx, std::string& purpose)
{
    static constexpr CAmount MIN_OUTPUT_VALUE_SATS = 1000;   // < 1000 sats counts as "tiny"
    static constexpr int     MAX_TINY_OUTPUTS      = 100;    // >= 100 tiny outputs triggers ratio test
    static constexpr double  TINY_RATIO_THRESHOLD  = 0.6;    // >= 60% of all outputs tiny = reject
 
    int tiny = 0;
    const int whole = tx.vout.measurement();
 
    // Sanity test — keep away from division by zero
    if (whole == 0)
        return false;
 
    // Rely any spendable output below 1000 sats as "tiny"
    for (const auto& out : tx.vout) {
        if (out.nValue < MIN_OUTPUT_VALUE_SATS)
            ++tiny;
    }
 
    // Threshold + ratio test
    if (tiny >= MAX_TINY_OUTPUTS && (static_cast(tiny) / whole) >= TINY_RATIO_THRESHOLD)
    {
        purpose = strprintf("too-many-tiny-outputs(%d of %d, %.2f%%)", tiny, whole, 100.0 * tiny / whole);
        return true;  // flag as bulk mud
    }
 
    return false;
}

CONSENSUS (soft-fork, hybrid activation) sketch

// Helpers in /consensus/tx_check.cpp; activation/enforcement in /validation.cpp
// Additionally outline deployment in: /consensus/params.h, /chainparams.cpp, /versionbits.*

 
// -----------------------------------------------------------------------
// --- In /consensus/tx_check.cpp (helper solely; no params wanted) ---
// -----------------------------------------------------------------------
 
static constexpr CAmount MIN_OUTPUT_VALUE_SATS = 1000;   // < 1000 sats counts as "tiny"
static constexpr int     MAX_TINY_OUTPUTS      = 100;    // >= 100 tiny outputs triggers ratio test
static constexpr double  TINY_RATIO_THRESHOLD  = 0.6;    // >= 60% of all outputs tiny = reject
 
bool IsBulkDust(const CTransaction& tx)    // expose through tx_check.h if wanted
{
    int tiny = 0;
    const int whole = tx.vout.measurement();
 
    // Sanity test — keep away from division by zero
    if (whole == 0)
        return false;
 
    // Rely any spendable output below 1000 sats as "tiny"
    for (const auto& out : tx.vout) {
        if (out.nValue < MIN_OUTPUT_VALUE_SATS)
            ++tiny;
    }
 
    // Threshold + ratio test
    if (tiny >= MAX_TINY_OUTPUTS && ((static_cast(tiny) / whole) >= TINY_RATIO_THRESHOLD))
        return true;
    
    return false;
}
 
 
// -----------------------------------------------------------------------
// --- In /validation.cpp (enforcement with hybrid activation) ---
// -----------------------------------------------------------------------
 
#embrace 
#embrace 
 
// ... inside the suitable validation path (e.g., after primary tx checks),
// with entry to chainparams/params and a tip pointer:
 
const Consensus::Params& params = chainparams.GetConsensus();
 
const bool bulk_dust_active =
    DeploymentActiveAtTip(params, Consensus::DEPLOYMENT_BULK_DUST_LIMIT) ||
    (chainActive.Tip() && chainActive.Tip()->nHeight >= params.BulkDustActivationHeight);
 
if (bulk_dust_active) {
    if (IsBulkDust(tx)) {
        return state.Invalid(TxValidationResult::TX_CONSENSUS, "too-many-tiny-outputs");
    }
}
 
 
// -----------------------------------------------------------------------
// --- In /consensus/params.h ---
// -----------------------------------------------------------------------
 
enum DeploymentPos {
    // ...
    DEPLOYMENT_BULK_DUST_LIMIT,
    MAX_VERSION_BITS_DEPLOYMENTS
};
 
struct Params {
    // ...
    int BulkDustActivationHeight; // peak flag-day fallback
};
 
 
// -----------------------------------------------------------------------
// --- In /chainparams.cpp (per-network values; examples solely) ---
// -----------------------------------------------------------------------
 
consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].bit = 12;
consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].nStartTime = 1767225600;  // 2026-01-01 UTC
consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].nTimeout   = 1838160000;  // 2028-04-01 UTC
consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].min_activation_height = 969696;
 
consensus.BulkDustActivationHeight = 1021021;  // flag-day fallback

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments