From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Wed, 29 Oct 2025 17:47:15 -0700 Received: from mail-oa1-f58.google.com ([209.85.160.58]) by mail.fairlystable.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1vEGos-0002Vn-GT for bitcoindev@gnusha.org; Wed, 29 Oct 2025 17:47:15 -0700 Received: by mail-oa1-f58.google.com with SMTP id 586e51a60fabf-3c9bfdade9csf2831944fac.0 for ; Wed, 29 Oct 2025 17:47:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20230601; t=1761785228; x=1762390028; darn=gnusha.org; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:message-id:to:from:date:sender:from:to:cc:subject:date :message-id:reply-to; bh=pTv34SdNINoNb1P4j8o1ZZe17MTA8ccJsBcciehHZO8=; b=A7WEiyIxB0r6u8gEcthSZafZn0q8aloBr8QuCJSsa3IGf5OdX13sRKjn0pnuopmfnO T6ggycwlvxBO7IWc16Y2CuG8HneHUV5kYCB4E4sWyBrTEC0vXT/pm2UU8bgx2TpEQLxm yawsQlsC8dXF2V7t3gkqZ710aRlk61Pub8w1Ax8g3w6m07qZcNuqDCPwC2kTl8NZY7s1 93tXTuPadu23EgnSVfzeS8+nYoE4i7bY4R1jHEoNJwshY+enklgpF1g8U2nTgp5i+EeF ytmg/xSr3EcnXoQ+bsWPmO0Ax1vIFBwhTzBxOtlU5zDv4J9gmcTspDNjwGr3fHj1GRpb fU9Q== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1761785228; x=1762390028; darn=gnusha.org; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:message-id:to:from:date:from:to:cc:subject:date:message-id :reply-to; bh=pTv34SdNINoNb1P4j8o1ZZe17MTA8ccJsBcciehHZO8=; b=MxTMuiG0bzVljbYLXChRTMHNev4HGOMyKYk0lsaK9kjXV/NpfaK7hG/jmghyk5y1AL dWf27CoOQ/CRVbjL31uPzjcpGt07NIlO5VDnYXAbuS52FQpyyUxTHsUk7rki35eOtNhJ zz3FVVQZiSLL4Y8HxsS/98sZGoxuRu4nbRubR2qPY8cB3KlTeBxbPet/i7icTkJ9MH9G vlPZXb3UBqBPzbR5zfIVzjUSMQF4R7pHMZBEtxmp2IntrqtrFCRJ7qnipNxTdjIxzjXg JIFQFIGHEKMWco594QLWovyX3zDMWB7nEEiRC5GmWnYk677gAJhE//eK0KKMpdQDQasB +zww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1761785228; x=1762390028; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:message-id:to:from:date:x-beenthere:x-gm-message-state :sender:from:to:cc:subject:date:message-id:reply-to; bh=pTv34SdNINoNb1P4j8o1ZZe17MTA8ccJsBcciehHZO8=; b=ifr0sqmJqB7vMghpo4FHa5wpdtQY762wcHGbJWCq0w8OeKBEKnb0+vcTwiAG9D2qDs Y4q4GT2m9T+JkTah2L8KrYqFwSwjkLSfnpSpQZ2joMXViq3aaKBnT2xE1F4sSvt/vjNn 6jqTS7ckVLSaLRTtUyc1B1K80LAR6+R+pg/FDyN3axq2NLY8mvkWy252fbqj88tzieyE ewk2tN8ZcRrIffmkrW0GcT378BP8IWWi3wR6ydqz5C6a5c8zHN11FCYpmGZmuF8MRHf5 W6sLOtnid/RmHxi+X3wiTr8K4mobckLa0q9UuKWxhF5meObtF0UDuwFHKwOkUsqIH3zr tsPg== Sender: bitcoindev@googlegroups.com X-Forwarded-Encrypted: i=1; AJvYcCVLufzcOc+ZTwB1aGdm60a/Uyq8ECm2hSla/Aoem+Q10Tyy8L/l3ZqAlGr9LN+beRM848c4KZ12Du/Z@gnusha.org X-Gm-Message-State: AOJu0YwnrBz10WbFVeJALIscsGXF1eoELJuT58QXm/j3JWxxjpJ9mOjx pS4HEDYah1/oHQnAZ+Ir8Luvc+yyPBBmBsO3lvIXQThbCHwAKun9bhpm X-Google-Smtp-Source: AGHT+IEWyB0n25eya6MwZDBTyKkGmu7flB0Lc277oCKwrJnLgF0Da4F32K+yEbh+SicOKgt2ioCRpQ== X-Received: by 2002:a05:6871:e2d6:b0:34b:9331:a9d5 with SMTP id 586e51a60fabf-3d8a3b335eamr745048fac.16.1761785228267; Wed, 29 Oct 2025 17:47:08 -0700 (PDT) X-BeenThere: bitcoindev@googlegroups.com; h="Ae8XA+aT0osmjH91+NfmyLeOn4ckmnAWoK1vMtSpTeP4oQlJDw==" Received: by 2002:a05:6871:80b:b0:3d5:92b8:657b with SMTP id 586e51a60fabf-3d8b64a950cls172790fac.0.-pod-prod-09-us; Wed, 29 Oct 2025 17:47:04 -0700 (PDT) X-Received: by 2002:a05:6808:23d3:b0:441:8f74:fbb with SMTP id 5614622812f47-44f89eaf3cbmr646480b6e.56.1761785224179; Wed, 29 Oct 2025 17:47:04 -0700 (PDT) Received: by 2002:a05:690c:a5c1:b0:74f:1486:e2a9 with SMTP id 00721157ae682-78629398ee3ms7b3; Wed, 29 Oct 2025 17:03:46 -0700 (PDT) X-Received: by 2002:a53:b3c1:0:b0:63c:f5a7:401 with SMTP id 956f58d0204a3-63f83af75c3mr734495d50.69.1761782625765; Wed, 29 Oct 2025 17:03:45 -0700 (PDT) Date: Wed, 29 Oct 2025 17:03:45 -0700 (PDT) From: Frenchanfry To: Bitcoin Development Mailing List Message-Id: Subject: [bitcoindev] By: Doctorbuzz1 {GitHub} Limit "Bulk Dust" with a default filter or consensus. MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_81006_322300299.1761782625421" X-Original-Sender: AndujoN18@gmail.com Precedence: list Mailing-list: list bitcoindev@googlegroups.com; contact bitcoindev+owners@googlegroups.com List-ID: X-Google-Group-Id: 786775582512 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , X-Spam-Score: -0.5 (/) ------=_Part_81006_322300299.1761782625421 Content-Type: multipart/alternative; boundary="----=_Part_81007_1892648872.1761782625421" ------=_Part_81007_1892648872.1761782625421 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable A proposal on GitHub I found Highly interesting and a better improvement,= =20 dealing with spammers/congestion. I=E2=80=99m exploring a potential default filter or consensus-level rule (s= ince a=20 large number of people believe that default filters don't work) to=20 discourage UTXO-bloat patterns without touching Script, witness data, or=20 the block size limit. The idea is to target =E2=80=9Cbulk dust=E2=80=9D transactions =E2=80=94 th= ose that create large=20 numbers of extremely small outputs =E2=80=94 which are the main cause of lo= ng-term=20 UTXO set growth. These types of "bulk dust" transactions have been the No. 1 reason cited=20 for wanting to expand the default OP_RETURN limit... and removing that=20 limit obviously influenced BIP 444. So it appears to me that there is=20 overwhelming majority support for limiting these types of "bulk dust"=20 transactions, as they do present a legitimate concern for node runners. Concept Flag a transaction as =E2=80=9Cbulk dust=E2=80=9D if: - It has >=3D100 outputs each below a dynamically defined TinyTx=20 threshold, and - Those tiny outputs make up >=3D60% of all outputs in the transaction. When flagged, it would be considered nonstandard (relay policy) or invalid= =20 (if soft-forked into consensus). TinyTx threshold (dynamic halving schedule) I originally considered a constant definition of what was a "tiny" Tx to be= =20 1,000 sats... but some might still just use 1,001 sats, right? Plus there= =20 very likely will be a time where there is a valid use-case of >100 outputs= =20 under 1,000 sats. Rather than fixing the =E2=80=9Ctiny=E2=80=9D threshold to a constant like = 1,000 sats, the=20 rule defines it as a decreasing function of block height, starting high and= =20 gradually tightening over time. - Starts at 4096 sats when activated (target ~2028). - Halves every 210,000 blocks (~4 years). - Never falls below 1 sat (hard floor). Year ---- Block Height -- TinyTx Threshold 2028 --- ~activation ---- 4096 sats 2032 --- ~1,260,000 ---- 2048 sats 2036 --- ~1,470,000 ---- 1024 sats 2040 --- ~1,680,000 ---- 512 sats =E2=80=A6 -- every 210,000 blocks -- =E2=80=A6 until 1 sat floor This gradual halving ensures the definition of "tiny" stays relevant as=20 Bitcoin=E2=80=99s value rises. For example, if 1 sat =3D $1 someday, having 100 outputs worth <1,000 sats= =20 each would no longer represent spam =E2=80=94 but rather normal payments. By then, the TinyTx limit would already have adjusted down automatically. Patterns this would limit - Fake pubkeys or scripts used to embed data via many UTXOs - Bitcoin STAMPS / UTXO-art spreading payloads across thousands of dust= =20 outputs - BRC-20 batch mints with 100s of "tiny" sat fan-outs - Some Ordinal or state inscription schemes that distribute data across= =20 many tiny outputs - Dust bombing (UTXO tracking or chain spam) - Mass micro-airdrops below the "tiny" sat range These use cases rely on cheap, numerous outputs =E2=80=94 making them sever= al times=20 more costly under this rule. Non-goals / unaffected - Normal user transactions, LN channel opens, and multisig spends - Batched exchange payouts (they typically have > 40% large-value=20 outputs) - Single/few-output inscriptions using witness data (not affected) - Any legitimate pattern where most outputs are above the threshold Why a ratio and a count? Requiring both (tiny_count >=3D 100) and (tiny_ratio >=3D 60%) helps avoid= =20 false positives, such as legitimate custodial payouts or consolidation=20 transactions with mixed values. It specifically filters transactions that are mostly dust, rather than=20 merely containing some. Inquiry - Are there credible, non-spam use cases that truly require >=3D100=20 sub-4k-sat outputs (or equivalent at later eras) and a >=3D60% tiny rati= o? - Could this affect fee market behavior or any privacy tools in=20 unintended ways? - Any concern with the 100 tiny_count limit or 60% tiny_ratio? - Any other unintended consequences? - Any objections in general?? What are they? Intent This proposal doesn=E2=80=99t censor any monetary transaction or prevent=20 inscriptions; it simply prices storage according to resource cost. It keeps the chain =E2=80=9Clight and nimble=E2=80=9D for everyday payments= while allowing=20 future flexibility =E2=80=94 because the TinyTx definition decreases automa= tically=20 in line with halvings and Bitcoin=E2=80=99s long-term value growth. CODE SKETCHES (with minimal syntax highlighting here: https://pastebin.com/9qdQCH83) RELAY POLICY FILTER sketch =E2=80=94 // Place in src/policy/policy.cpp, and call from within IsStandardTx()=20 before returning: // if (IsBulkDust(tx, reason)) // return false; // reject= =20 as nonstandard //=20 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=20 bool IsBulkDust(const CTransaction& tx, std::string& reason) { static=20 constexpr int MAX_TINY_OUTPUTS =3D 100; // >=3D100 tiny outputs triggers ra= tio=20 check static constexpr double TINY_RATIO_THRESHOLD =3D 0.6; // >=3D60% of a= ll=20 outputs tiny =E2=86=92 reject static constexpr CAmount BASE_TINY_THRESHOLD = =3D 4096;=20 // starting tiny threshold (sats) static constexpr int64_t=20 FIRST_TINY_HALVING_H =3D 1260000; // first halving of tiny threshold static= =20 constexpr int64_t HALVING_INTERVAL =3D 210000; // blocks per subsequent=20 halving static constexpr CAmount MIN_TINY_FLOOR =3D 1; // never below 1 sat= =20 const int total =3D tx.vout.size(); if (total =3D=3D 0) return false; int= =20 currentHeight =3D chainActive.Tip() ? chainActive.Tip()->nHeight : 0; // Er= a=20 index for TinyTx threshold, anchored at FIRST_TINY_HALVING_H (not subsidy= =20 eras) int era =3D 0; if (currentHeight >=3D FIRST_TINY_HALVING_H) { era =3D= 1 +=20 static_cast((currentHeight - FIRST_TINY_HALVING_H) /=20 HALVING_INTERVAL); } CAmount tinyThresh =3D BASE_TINY_THRESHOLD >> era; //= =20 halve per era if (tinyThresh < MIN_TINY_FLOOR) tinyThresh =3D MIN_TINY_FLOO= R;=20 int tiny =3D 0; for (const auto& out : tx.vout) { if (out.nValue <=20 tinyThresh) ++tiny; } if (tiny >=3D MAX_TINY_OUTPUTS &&=20 (static_cast(tiny) / total) >=3D TINY_RATIO_THRESHOLD) { reason =3D= =20 strprintf("too-many-tiny-outputs(%d of %d, %.2f%%, tiny<%d)", tiny, total,= =20 100.0 * tiny / total, tinyThresh); return true; // flag as bulk dust=20 (nonstandard) } return false; }=20 CONSENSUS (soft-fork, hybrid activation) sketch =E2=80=94 // Helpers in src/consensus/tx_check.cpp; activation/enforcement in=20 src/validation.cpp // Also define deployment in: src/consensus/params.h,=20 src/chainparams.cpp, src/versionbits.* //=20 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=20 // -----------------------------------------------------------------------= =20 // --- In src/consensus/tx_check.cpp (helper only; no params needed) --- //= =20 -----------------------------------------------------------------------=20 static constexpr CAmount BASE_TINY_THRESHOLD =3D 4096; static constexpr=20 int64_t FIRST_TINY_HALVING_H =3D 1260000; static constexpr int64_t=20 HALVING_INTERVAL =3D 210000; static constexpr int MAX_TINY_OUTPUTS =3D 100;= =20 static constexpr double TINY_RATIO_THRESHOLD =3D 0.6; static constexpr=20 CAmount MIN_TINY_FLOOR =3D 1; bool IsBulkDust(const CTransaction& tx, int= =20 currentHeight) // expose via tx_check.h if needed { const int total =3D=20 tx.vout.size(); if (total =3D=3D 0) return false; int era =3D 0; if=20 (currentHeight >=3D FIRST_TINY_HALVING_H) { era =3D 1 +=20 static_cast((currentHeight - FIRST_TINY_HALVING_H) /=20 HALVING_INTERVAL); } CAmount tinyThresh =3D BASE_TINY_THRESHOLD >> era; if= =20 (tinyThresh < MIN_TINY_FLOOR) tinyThresh =3D MIN_TINY_FLOOR; int tiny =3D 0= ;=20 for (const auto& out : tx.vout) { if (out.nValue < tinyThresh) ++tiny; } if= =20 (tiny >=3D MAX_TINY_OUTPUTS && (static_cast(tiny) / total) >=3D=20 TINY_RATIO_THRESHOLD) return true; return false; } //=20 ----------------------------------------------------------------------- //= =20 --- In src/validation.cpp (enforcement with hybrid activation) --- //=20 -----------------------------------------------------------------------=20 #include #include const=20 Consensus::Params& params =3D chainparams.GetConsensus(); int currentHeight= =3D=20 chainActive.Tip() ? chainActive.Tip()->nHeight : 0; const bool=20 bulk_dust_active =3D DeploymentActiveAtTip(params,=20 Consensus::DEPLOYMENT_BULK_DUST_LIMIT) || (currentHeight >=3D=20 params.BulkDustActivationHeight); if (bulk_dust_active) { if=20 (IsBulkDust(tx, currentHeight)) { return=20 state.Invalid(TxValidationResult::TX_CONSENSUS, "too-many-tiny-outputs"); }= =20 } //=20 ----------------------------------------------------------------------- //= =20 --- In src/consensus/params.h --- //=20 -----------------------------------------------------------------------=20 enum DeploymentPos { // ... DEPLOYMENT_BULK_DUST_LIMIT,=20 MAX_VERSION_BITS_DEPLOYMENTS }; struct Params { // ... int=20 BulkDustActivationHeight; // height flag-day fallback }; //=20 ----------------------------------------------------------------------- //= =20 --- In src/chainparams.cpp (per-network values; examples only) --- //=20 -----------------------------------------------------------------------=20 consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].bit =3D 12;= =20 consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].nStartTime = =3D=20 1767225600; // 2026-01-01 UTC=20 consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].nTimeout =3D= =20 1838160000; // 2028-04-01 UTC=20 consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].min_activatio= n_height=20 =3D 969696; consensus.BulkDustActivationHeight =3D 1021021; // flag-day fal= lback --=20 You received this message because you are subscribed to the Google Groups "= Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an e= mail to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/= b0dee827-c2fe-4a68-9b41-f1447ba3c1d3n%40googlegroups.com. ------=_Part_81007_1892648872.1761782625421 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable A proposal on GitHub I found Highly interesting and a better improvement, d= ealing with spammers/congestion.

I=E2=80=99m exploring a potential default filter or consensus-le= vel rule (since a large number of people believe that default filters don't= work) to discourage UTXO-bloat patterns without touching Script, witness d= ata, or the block size limit.

The idea i= s to target =E2=80=9Cbulk dust=E2=80=9D transactions =E2=80=94 those that c= reate large numbers of extremely small outputs =E2=80=94 which are the main= cause of long-term UTXO set growth.

The= se types of "bulk dust" transactions have been the No. 1 reason cited for w= anting to expand the default OP_RETURN limit... and removing that limit obv= iously influenced BIP 444. So it appears to me that there is overwhelming m= ajority support for limiting these types of "bulk dust" transactions, as th= ey do present a legitimate concern for node runners.

Conc= ept

Flag a transaction as =E2=80= =9Cbulk dust=E2=80=9D if:

  • It has >=3D100 outputs each = below a dynamically defined TinyTx threshold, and
  • Those tiny outputs make up >=3D60% = of all outputs in the transaction.

When flagged, it would be considered nonstandard (relay policy) or invalid= (if soft-forked into consensus).

TinyTx threshold (dynam= ic halving schedule)

I originally= considered a constant definition of what was a "tiny" Tx to be 1,000 sats.= .. but some might still just use 1,001 sats, right? Plus there very likely = will be a time where there is a valid use-case of >100 outputs under 1,0= 00 sats.

Rather than fixing the =E2=80= =9Ctiny=E2=80=9D threshold to a constant like 1,000 sats, the rule defines = it as a decreasing function of block height, starting high and gradually ti= ghtening over time.

    =
  • Starts at 4096 sats when activated (t= arget ~2028).
  • Halves every 210,000 blocks (~4 years).
  • Never falls below 1 sat (hard floor).
=

Year ---- Block Height -- TinyTx Threshold<= br style=3D"box-sizing: border-box;" />2028 --- ~activation ---- 4096 sats<= br style=3D"box-sizing: border-box;" />2032 --- ~1,260,000 ---- 2048 sats2036 --- ~1,470,000 ---- 1024 sats2040 --- ~1,680,000 ---- 512 sats
=E2=80=A6 -- every 210,000 blocks -- =E2= =80=A6 until 1 sat floor

This gradual ha= lving ensures the definition of "tiny" stays relevant as Bitcoin=E2=80=99s = value rises.
For example, if 1 sat = =3D $1 someday, having 100 outputs worth <1,000 sats each would no longe= r represent spam =E2=80=94 but rather normal payments.
By then, the TinyTx limit would already have adjusted do= wn automatically.

Patterns this would limit

  • Fake pubkeys or scripts used to embed data via many UTXOs
  • <= li style=3D"box-sizing: border-box; margin-top: 0.25em;">Bitcoin STAMPS / U= TXO-art spreading payloads across thousands of dust outputs
  • BRC-20 batch mints with 10= 0s of "tiny" sat fan-outs
  • Some Ordinal or state inscription schemes that distribute data= across many tiny outputs
  • Dust bombing (UTXO tracking or chain spam)
  • Mass micro-airdrops below the "t= iny" sat range
  • These use cases rel= y on cheap, numerous outputs =E2=80=94 making them several times more costl= y under this rule.

    Non-goals / unaffected

    • Normal user transactions, LN channel opens, and multisig spends
    • Batched exchan= ge payouts (they typically have > 40% large-value outputs)
    • Single/few-output inscript= ions using witness data (not affected)
    • Any legitimate pattern where most outputs are abo= ve the threshold

    Why a ratio and a count?

    Requiring both (tiny_count >=3D 100) an= d (tiny_ratio >=3D 60%) helps avoid false positives, such as legitimate = custodial payouts or consolidation transactions with mixed values.
    It specifically filters transactions that ar= e mostly dust, rather than merely containing some.

    Inquir= y

    • Are there credible, non-spam use cases that tr= uly require >=3D100 sub-4k-sat outputs (or equivalent at later eras) and= a >=3D60% tiny ratio?
    • Could this affect fee market behavior or any privacy tools in = unintended ways?
    • Any concern with the 100 tiny_count limit or 60% tiny_ratio?
    • Any other unintended co= nsequences?
    • A= ny objections in general?? What are they?

    Intent

    This proposal doesn=E2=80=99t censo= r any monetary transaction or prevent inscriptions; it simply prices storag= e according to resource cost.
    It kee= ps the chain =E2=80=9Clight and nimble=E2=80=9D for everyday payments while= allowing future flexibility =E2=80=94 because the TinyTx definition decrea= ses automatically in line with halvings and Bitcoin=E2=80=99s long-term val= ue growth.

    CODE SKETCHES
    (with minimal syntax highlighting here:=C2=A0https://pastebin.com/9= qdQCH83)

    RELAY POLICY FILTER sketch =E2=80=94<= /p>

    // Place in src/polic= y/policy.cpp, and call from within IsStandardTx() before returning: // if (IsBulkDust(tx, reason)) // return false; // reject as nonstandard // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D bool IsBulkDust(const CTransaction& tx, std::string& reason) { static constexpr int MAX_TINY_OUTPUTS =3D 100; // >= =3D100 tiny outputs triggers ratio check static constexpr double TINY_RATIO_THRESHOLD =3D 0.6; // >= =3D60% of all outputs tiny =E2=86=92 reject static constexpr CAmount BASE_TINY_THRESHOLD =3D 4096; // starti= ng tiny threshold (sats) static constexpr int64_t FIRST_TINY_HALVING_H =3D 1260000; // first = halving of tiny threshold static constexpr int64_t HALVING_INTERVAL =3D 210000; // blocks= per subsequent halving static constexpr CAmount MIN_TINY_FLOOR =3D 1; // never = below 1 sat const int total =3D tx.vout.size(); if (total =3D=3D 0) return false; int currentHeight =3D chainActive.Tip() ? chainActive.Tip()->nHeight= : 0; // Era index for TinyTx threshold, anchored at FIRST_TINY_HALVING_H (no= t subsidy eras) int era =3D 0; if (currentHeight >=3D FIRST_TINY_HALVING_H) { era =3D 1 + static_cast<int>((currentHeight - FIRST_TINY_HALV= ING_H) / HALVING_INTERVAL); } CAmount tinyThresh =3D BASE_TINY_THRESHOLD >> era; // halve = per era if (tinyThresh < MIN_TINY_FLOOR) tinyThresh =3D MIN_TINY_FLOOR; int tiny =3D 0; for (const auto& out : tx.vout) { if (out.nValue < tinyThresh) ++tiny; } if (tiny >=3D MAX_TINY_OUTPUTS && (static_cast<double>= (tiny) / total) >=3D TINY_RATIO_THRESHOLD) { reason =3D strprintf("too-many-tiny-outputs(%d of %d, %.2f%%, tiny&= lt;%d)", tiny, total, 100.0 * tiny / total, tinyThresh); return true; // flag as bulk dust (nonstandard) } return false; }

    CONSENSUS (soft-fork, hybrid activation) sketch =E2=80=94

    // Help= ers in src/consensus/tx_check.cpp; activation/enforcement in src/validation= .cpp // Also define deployment in: src/consensus/params.h, src/chainparams.cpp, = src/versionbits.* // =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D // ----------------------------------------------------------------------- // --- In src/consensus/tx_check.cpp (helper only; no params needed) --- // ----------------------------------------------------------------------- static constexpr CAmount BASE_TINY_THRESHOLD =3D 4096; static constexpr int64_t FIRST_TINY_HALVING_H =3D 1260000; static constexpr int64_t HALVING_INTERVAL =3D 210000; static constexpr int MAX_TINY_OUTPUTS =3D 100; static constexpr double TINY_RATIO_THRESHOLD =3D 0.6; static constexpr CAmount MIN_TINY_FLOOR =3D 1; bool IsBulkDust(const CTransaction& tx, int currentHeight) // expose vi= a tx_check.h if needed { const int total =3D tx.vout.size(); if (total =3D=3D 0) return false; int era =3D 0; if (currentHeight >=3D FIRST_TINY_HALVING_H) { era =3D 1 + static_cast<int>((currentHeight - FIRST_TINY_HALV= ING_H) / HALVING_INTERVAL); } CAmount tinyThresh =3D BASE_TINY_THRESHOLD >> era; if (tinyThresh < MIN_TINY_FLOOR) tinyThresh =3D MIN_TINY_FLOOR; int tiny =3D 0; for (const auto& out : tx.vout) { if (out.nValue < tinyThresh) ++tiny; } if (tiny >=3D MAX_TINY_OUTPUTS && (static_cast<double>= (tiny) / total) >=3D TINY_RATIO_THRESHOLD) return true; return false; } // ----------------------------------------------------------------------- // --- In src/validation.cpp (enforcement with hybrid activation) --- // ----------------------------------------------------------------------- #include <consensus/tx_check.h> #include <versionbits.h> const Consensus::Params& params =3D chainparams.GetConsensus(); int currentHeight =3D chainActive.Tip() ? chainActive.Tip()->nHeight : 0= ; const bool bulk_dust_active =3D DeploymentActiveAtTip(params, Consensus::DEPLOYMENT_BULK_DUST_LIMIT) || (currentHeight >=3D params.BulkDustActivationHeight); if (bulk_dust_active) { if (IsBulkDust(tx, currentHeight)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "too-many-ti= ny-outputs"); } } // ----------------------------------------------------------------------- // --- In src/consensus/params.h --- // ----------------------------------------------------------------------- enum DeploymentPos { // ... DEPLOYMENT_BULK_DUST_LIMIT, MAX_VERSION_BITS_DEPLOYMENTS }; struct Params { // ... int BulkDustActivationHeight; // height flag-day fallback }; // ----------------------------------------------------------------------- // --- In src/chainparams.cpp (per-network values; examples only) --- // ----------------------------------------------------------------------- consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].bit =3D 12; consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].nStartTime = =3D 1767225600; // 2026-01-01 UTC consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].nTimeout = =3D 1838160000; // 2028-04-01 UTC consensus.vDeployments[Consensus::DEPLOYMENT_BULK_DUST_LIMIT].min_activatio= n_height =3D 969696; consensus.BulkDustActivationHeight =3D 1021021; // flag-day fallback=

    --
    You received this message because you are subscribed to the Google Groups &= quot;Bitcoin Development Mailing List" group.
    To unsubscribe from this group and stop receiving emails from it, send an e= mail to bitcoind= ev+unsubscribe@googlegroups.com.
    To view this discussion visit https://groups.google.com/d/msgid/bitcoind= ev/b0dee827-c2fe-4a68-9b41-f1447ba3c1d3n%40googlegroups.com.
    ------=_Part_81007_1892648872.1761782625421-- ------=_Part_81006_322300299.1761782625421--