From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Wed, 29 Oct 2025 18:15:16 -0700 Received: from mail-oo1-f60.google.com ([209.85.161.60]) by mail.fairlystable.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1vEHFy-000388-U8 for bitcoindev@gnusha.org; Wed, 29 Oct 2025 18:15:16 -0700 Received: by mail-oo1-f60.google.com with SMTP id 006d021491bc7-62a98ee688csf157034eaf.1 for ; Wed, 29 Oct 2025 18:15:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20230601; t=1761786909; x=1762391709; 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:references:in-reply-to:message-id:to:from:date:sender:from :to:cc:subject:date:message-id:reply-to; bh=ngmvDwy+RuKUN1YzcMyaQiPCkfGa9FNWIpUbVj8+Y+M=; b=pGu0wYYipIXp9nAeczlqXEh+II22G2kA0LBz6GND1QsLZt1kY2em2czUEQ+j1OPtDq fpCJjBoOvjclwbqKRM8O3e0aN+5gkNb2Oie/1WCG+9cRLSyPqg1WxmHuQXpnFJwQK8PL lkmlrqad9o5snhiEK2UDZl8m3GB2pSF8UZyQVkGk/jgQwsj86bv5t+PrVxrJxEbXDjvi fVQ/3Hc5TY0ZprZEI/j7vlbEmaM/+DMcHZBGgQwse+OZAQpRkjUw+SvNk9o+w0WPr9yX U0f0exPtO9VF0aIbDQngnWRHOqaZGooAQkL1JZdbFJALqPTDEl9KCxIKcwgk5r7qQYdn mmAg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1761786909; x=1762391709; 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:references:in-reply-to:message-id:to:from:date:from:to:cc :subject:date:message-id:reply-to; bh=ngmvDwy+RuKUN1YzcMyaQiPCkfGa9FNWIpUbVj8+Y+M=; b=OlqtRPfA7FHug5hlaX2kF2PnTJHGPnrBp7RWz3LCnS3FzGRULirmnWcWdyT500+SoC WfOA1KDYJji8g74QTBeA2R31Ev6n4WDIby++OcvefLUUAnj1hFUWnn1MdoSvBRVZ+xJv ZfMb4fkPasOLB+4tvwal2OI52B5yjjkcnoiGTNynypQTmCi1GZzJBzR9N7so4YtKSjWA TGgEHbc/pQAzLMl2SRJPPhm5QD1oRlDR2YTTmLKRUagxn/ICUkh3RyhYCEV4BblqO1x/ cHocg17W9EV8je5mqClPkFF5J/qLU+eVOnBf8ArcRy9W34wwsQ8ex3aFVg3puPJhkm9a kR5Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1761786909; x=1762391709; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:references:in-reply-to:message-id:to:from:date:x-beenthere :x-gm-message-state:sender:from:to:cc:subject:date:message-id :reply-to; bh=ngmvDwy+RuKUN1YzcMyaQiPCkfGa9FNWIpUbVj8+Y+M=; b=ilT3tuUgfrKG2XXuP+klEpt7ZvTVX79rPzzs1rpfmnECA5f8cU5xkCeBcaOnwlQgOa C/ScxnX/OYkUWNdlCDUGEWn+08F9h73X/VueRqVKWcvftyUZ5hO5N7enZOSGqC4Y+mbc q0YSG50pWNR1SF8zjuYwjtjOkQPSCeITd0SdUgFj3xKbsnnl7DXSyY5rNxtGvuoErsul 33gUT3YnZTI8R9K9gJq6rty6OqLQTTw7YCMLgtJXZMuwEM/3/7yPxVOXndvrvPKBhWnz 87MTjQsmn2mdDPx5NrnGPbMTxoHeWZaypN2Y5bYoZVmld271GCxKlByEhOHH8VibvbY1 DQog== Sender: bitcoindev@googlegroups.com X-Forwarded-Encrypted: i=1; AJvYcCUBZqJiGx1E5hBrV0PQMI3q+NI1lAsERctAFypAmEw4Uq2wuGG05zK0pcqSYjMyX3SgAdL9uJn8atO9@gnusha.org X-Gm-Message-State: AOJu0YzUoqQMfkrEwFuyXMU0kKJCgdHSsmLwaRKdHllXraiEK3T2tE1E +Wo3TICtj84XxQZlQaqAaKy+sNdsO2Tha9FR9M2CMEb4gX+kxdMt6RpT X-Google-Smtp-Source: AGHT+IGeL4fls9WvXflOSGZik7hqdsVOdEDs81uyQo+WBIxEgyQ6Ii5+toynhZuWEcYmbM2Dt11q+Q== X-Received: by 2002:a05:6820:201b:b0:654:f9a7:76ee with SMTP id 006d021491bc7-65677f012a3mr2078388eaf.8.1761786908312; Wed, 29 Oct 2025 18:15:08 -0700 (PDT) X-BeenThere: bitcoindev@googlegroups.com; h="Ae8XA+Y7Akduxth7kcS5fJGK7017FnJXpxzc5Ls+h+3Tbb4gAA==" Received: by 2002:a05:6820:2d05:b0:61b:5157:c2ee with SMTP id 006d021491bc7-65682485a67ls65819eaf.1.-pod-prod-00-us; Wed, 29 Oct 2025 18:15:04 -0700 (PDT) X-Received: by 2002:a05:6808:6a96:b0:44d:b56a:d64e with SMTP id 5614622812f47-44f88a39b9emr738141b6e.15.1761786903985; Wed, 29 Oct 2025 18:15:03 -0700 (PDT) Received: by 2002:a05:690c:a5c1:b0:74f:1486:e2a9 with SMTP id 00721157ae682-78629398ee3ms7b3; Wed, 29 Oct 2025 18:02:26 -0700 (PDT) X-Received: by 2002:a05:690c:368d:b0:781:64f:2b72 with SMTP id 00721157ae682-7863901ab70mr15805217b3.35.1761786145424; Wed, 29 Oct 2025 18:02:25 -0700 (PDT) Date: Wed, 29 Oct 2025 18:02:24 -0700 (PDT) From: Doctor Buzz To: Bitcoin Development Mailing List Message-Id: <71b4d969-8c0d-4ff2-a177-244aa86ca57an@googlegroups.com> In-Reply-To: References: Subject: [bitcoindev] Re: By: Doctorbuzz1 {GitHub} Limit "Bulk Dust" with a default filter or consensus. MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_133496_2002457633.1761786144832" X-Original-Sender: BuzzHeavy@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_133496_2002457633.1761786144832 Content-Type: multipart/alternative; boundary="----=_Part_133497_759746740.1761786144832" ------=_Part_133497_759746740.1761786144832 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Thanks! I came here to post it myself. I just want to point out that it's= =20 awfully discouraging for a GitHub mod to "close" my 90% developed code,=20 asking me to post it elsewhere... but anyway! Original GitHub post here: https://github.com/bitcoin/bitcoin/issues/33737#issuecomment-3465288829 The first concept of this with static definition of a "tiny" Tx was posted= =20 here (with no responses): =20 https://bitcoin.stackexchange.com/questions/129139/would-a-bulk-dust-relay-= consensus-rule-limiting-100-sub-1-000-sat-outputs-p Pastebin code probably looks better here than what I can see in the OP of= =20 this thread: https://pastebin.com/9qdQCH83 On Wednesday, October 29, 2025 at 7:47:08=E2=80=AFPM UTC-5 Frenchanfry wrot= e: > 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 = (since 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 = those that create large=20 > numbers of extremely small outputs =E2=80=94 which are the main cause of = long-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 invali= d=20 > (if soft-forked into consensus). > > TinyTx threshold (dynamic halving schedule) > > I originally considered a constant definition of what was a "tiny" Tx to= =20 > be 1,000 sats... but some might still just use 1,001 sats, right? Plus=20 > there very likely will be a time where there is a valid use-case of >100= =20 > outputs under 1,000 sats. > > Rather than fixing the =E2=80=9Ctiny=E2=80=9D threshold to a constant lik= e 1,000 sats, the=20 > rule defines it as a decreasing function of block height, starting high a= nd=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 sat= s=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=20 > dust outputs > - BRC-20 batch mints with 100s of "tiny" sat fan-outs > - Some Ordinal or state inscription schemes that distribute data=20 > across 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 sev= eral=20 > times 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 avoi= d=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 ra= tio? > - 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 paymen= ts while allowing=20 > future flexibility =E2=80=94 because the TinyTx definition decreases auto= matically=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; // reje= ct=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 = ratio=20 > check static constexpr double TINY_RATIO_THRESHOLD =3D 0.6; // >=3D60% of= all=20 > outputs tiny =E2=86=92 reject static constexpr CAmount BASE_TINY_THRESHOL= D =3D 4096;=20 > // starting tiny threshold (sats) static constexpr int64_t=20 > FIRST_TINY_HALVING_H =3D 1260000; // first halving of tiny threshold stat= ic=20 > constexpr int64_t HALVING_INTERVAL =3D 210000; // blocks per subsequent= =20 > halving static constexpr CAmount MIN_TINY_FLOOR =3D 1; // never below 1 s= at=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; // = Era=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_FL= OOR;=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 10= 0;=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; i= f=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 currentHeig= ht =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_activat= ion_height=20 > =3D 969696; consensus.BulkDustActivationHeight =3D 1021021; // flag-day f= allback > --=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/= 71b4d969-8c0d-4ff2-a177-244aa86ca57an%40googlegroups.com. ------=_Part_133497_759746740.1761786144832 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Thanks!=C2=A0 I came here to post it myself.=C2=A0 I just want to point out= that it's awfully discouraging for a GitHub mod to "close" my 90% develope= d code, asking me to post it elsewhere... but anyway!

Original G= itHub post here:
https://github.com/bitcoin/bitcoin/issues/33737#issue= comment-3465288829

The first concept of this with static definit= ion of a "tiny" Tx was posted here (with no responses):=C2=A0 https://bitco= in.stackexchange.com/questions/129139/would-a-bulk-dust-relay-consensus-rul= e-limiting-100-sub-1-000-sat-outputs-p

Pastebin code probably lo= oks better here than what I can see in the OP of this thread:=C2=A0=C2=A0ht= tps://pastebin.com/9qdQCH83
On Wednesday, October 29, 2025 at 7:47:08=E2=80=AFPM UTC-= 5 Frenchanfry wrote:
A proposal on GitHub I found Highly interesting and a better improv= ement, dealing with spammers/congestion.

I=E2=80= =99m exploring a potential default filter or consensus-level rule (since a = large number of people believe that default filters don't work) to disc= ourage UTXO-bloat patterns without touching Script, witness data, or the bl= ock size limit.

The idea is to target =E2=80=9Cbulk dust=E2=80= =9D transactions =E2=80=94 those that create large numbers of extremely sma= ll outputs =E2=80=94 which are the main cause of long-term UTXO set growth.=

These types of "bulk dust" transactions have been the= No. 1 reason cited for wanting to expand the default OP_RETURN limit... an= d removing that limit obviously influenced BIP 444. So it appears to me tha= t there is overwhelming majority support for limiting these types of "= bulk dust" transactions, as they do present a legitimate concern for n= ode runners.

Concept

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 ou= tputs make up >=3D60% of all outputs in the transaction.

TinyTx threshold (dynamic halving schedule)=

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

Rather than fixing th= e =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 grad= ually tightening over time.

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

Year ---- Block Heig= ht -- TinyTx Threshold
2028 --- ~activat= ion ---- 4096 sats
2032 --- ~1,260,000 -= --- 2048 sats
2036 --- ~1,470,000 ---- 1= 024 sats
2040 --- ~1,680,000 ---- 512 sa= ts
=E2=80=A6 -- every 210,000 blocks -- = =E2=80=A6 until 1 sat floor

This gradual halving ensures the def= inition 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 longer represent spam= =E2=80=94 but rather normal payments.
B= y then, the TinyTx limit would already have adjusted down automatically.

Patterns thi= s would limit

  • Fake pubkeys or scripts used to embed data via many UTXOs
  • Bitcoin STAMPS / UTXO-a= rt spreading payloads across thousands of dust outputs
  • BRC-20 batch mints with 100s of "= ;tiny" sat fan-outs
  • Some Ordinal or state inscription schemes that distribute data acro= ss 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 output= s =E2=80=94 making them several times more costly under this rule.

Non-goals / unaffe= cted

  • Normal user transactions, LN channel opens, and multisig spends
  • Batched exchange payouts (= they typically have > 40% large-value outputs)
  • Single/few-output inscriptions using witne= ss data (not affected)
  • Any legitimate pattern where most outputs are above the threshold

Why a r= atio and a count?

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

Inquiry

  • Are there credible, non-spa= m use cases that truly require >=3D100 sub-4k-sat outputs (or equivalent= at later eras) and a >=3D60% tiny ratio?
  • Could this affect fee market behavior or any pr= ivacy tools in unintended ways?
  • Any concern with the 100 tiny_count limit or 60% tiny_ratio?=
  • Any other uninte= nded consequences?
  • Any objections in general?? What are they?

Intent

This pr= oposal doesn=E2=80=99t censor any monetary transaction or prevent inscripti= ons; 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 future flexibility =E2=80=94 because the = TinyTx definition decreases automatically in line with halvings and Bitcoin= =E2=80=99s long-term value growth.

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

RELAY POLICY FILTER sketch =E2=80=94

// Place in src/policy/policy.cpp, and call from within IsStandard= Tx() 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<%d)", tiny, total, 100.0 * tiny / total, tinyThresh); return true; // flag as bulk dust (nonstandard) } return false; }

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

// Helpers in src/consensus/tx_c= heck.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-ma= ny-tiny-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/71b4d969-8c0d-4ff2-a177-244aa86ca57an%40googlegroups.com.
------=_Part_133497_759746740.1761786144832-- ------=_Part_133496_2002457633.1761786144832--