Repository navigation
policy: 3,900,000 WU standard txs, global 1 sat dust limit, $DOG Mode preferential peering - #3
Conversation
df8b9d3 to
e5901b2
Compare
|
Went through this properly — built it from source, ran the full unit and functional suites myself instead of trusting the PR description, read the diff line by line. Results below. Tests
One thing to fix in the PR description itself: it says CodeChecked the constants and the dust change by hand rather than trusting the comments:
Two things I'd fix before merging: 1. The 2. Also: there's no Rest of it checks out — dust semantics, the weight/cluster math, eviction handling, plumbing. Good work overall, just want those two points addressed or explicitly justified before this merges. Build/test verification and the commit-history trace on the mempool floor were done with Claude Code — happy to share the full build/test logs if useful. |
|
Point 1. The margin 794a8ce created is byte-denominated: the cost of the cheap-fill games is floor bytes × min feerate, and the floor is preserved in bytes — 40 × 101 kvB = 4.04 MB → 5 × 976 kvB = 4.88 MB, so that cost is unchanged (marginally higher). What 5x preserves is the minimum permitted -maxmempool of ~5 MB; keeping 40x would raise it to 39 MB and break every existing -maxmempool=5..38 config. Not going to fix. Point 2: Valid and updated in 9b6f684 |
… PR #3 Review of bitcoindogmode/bitcoin#3 is arguing the 40x to 5x multiplier change from first principles: cluster-count margin versus byte-denominated margin. Neither side measured it. Same -maxmempool=5 on both builds, filled with independent maximum-size transactions. Stock holds 30 and its minimum feerate stays at the floor. The patched build holds 3, one transaction is 19% of the mempool, and four transactions raise the minimum feerate 13x. A control run with small transactions on the patched build reproduces stock exactly, which places the effect on transaction size against mempool size rather than on the multiplier alone. Bounded: an ordinary 10 sat/vB transaction was accepted in every case.
|
Not a code review, that ground is covered above. What I can add is measurement: I implemented these same three changes independently against v31.1 last week and measured their consequences, before this PR existed. Harness and raw JSON: https://github.com/tialkan/bitcoin-dust-utxo-lab Four things, none of which I think have come up yet. 1. The dust cap removes a choice operators currently have
That matters because the 1 sat threshold does not need a code change at all. On unmodified v31.1,
So the same user-visible outcome is available by changing the default Incidentally, the "294-546 sat" range as usually quoted is incomplete: P2TR is 330 and P2SH-P2WPKH is 540. 2. What a 1 sat output costs, measuredThe benefit side of this change has a number attached to it. The cost side has not, so far as I can see. 450,000 1-sat outputs created and mined on regtest. Marginal chainstate cost per additional UTXO settles and stays flat:
P2TR is both the type most likely to carry these transactions and the most expensive to hold. Note the inversion between the last two rows: P2PKH has the larger Per block, from the measured transaction shape: a block whose 1,000,000 vB is entirely 1-sat dust holds about 32,219 outputs, costs its creator 0.01 BTC at 1 sat/vB, and adds about 1.37 MB of chainstate to every node, permanently. Permanently is the operative word. An output is economically spendable only if its value exceeds the fee to spend it. A P2TR key path input is 57.5 vB, so a 1-sat output would need a feerate below 0.017 sat/vB to be worth spending, far under the 1 sat/vB relay floor. These are not transient entries that clear when fees fall. That is not an argument against the change. It is the other half of the ledger, and it belongs in the release notes: this is a decision to grow the UTXO set permanently, paid for by node operators rather than by the party creating the outputs. 3. Preferential peering has a cost that is charged whether or not it worksThe four reserved slots are added to
Exactly four, as the arithmetic predicts. 3.5% of inbound capacity at the default. Worth a release note line: a node running this improves its own transaction reach by slightly reducing what it serves back. The part that surprised me is when it is charged. The reserved-slot branch in
The last row is the interesting one, and it reproduced three times. With six reachable peers, all six go to the eight full-relay slots and the reserved branch is never reached at all, at 100% adoption. Starting from gossip instead of a pre-filled addrman, on a 24-node network, the observer plateaued at 9 known addresses and ended with 8 full-relay and 1 block-relay connection: one address short of the second block-relay slot, so preferential peering never engaged in 25 minutes. Caveat, because it matters: a small closed network is a much thinner address supply than a node bootstrapping from DNS seeds, so those timings do not transfer to mainnet. What does transfer is the ordering. A node short of peers pays four inbound slots for a mechanism it never reaches. If this is meant to work for nodes with modest connectivity, moving the reserved branch ahead of the block-relay branch, or reserving fewer slots, would be worth considering. 4. The
|
| Build | Max tx vsize | One tx as share of mempool | Txs held | mempoolminfee (BTC/kvB) |
|---|---|---|---|---|
| stock v31.1, 100 kvB txs | 99,312 | 1.99% | 30 | 0.00000100 |
| PR#3, 975 kvB txs | 961,112 | 19.22% | 3 | 0.00001299 |
| PR#3, 100 kvB txs (control) | 99,312 | 1.99% | 30 | 0.00000100 |
The control matters: PR#3 fed small transactions behaves identically to stock. So this is not the multiplier in isolation. It is what happens when maximum-size transactions meet a mempool near the floor.
What changes is sensitivity. Thirty transactions leave stock's minimum feerate untouched at the floor. Four leave PR#3's at thirteen times the floor, and the fifth is rejected with mempool min fee not met, 1153332 < 1248485 at the same feerate its four predecessors were accepted at, with the mempool around 90% full. One of the four had already been evicted by then.
So I do not think either framing above is quite it. The attacker's byte cost is preserved, as you say. The cluster count margin does drop, as raised. But the consequence a node operator actually sees at the floor is a minimum feerate that moves in ~20% steps because that is the granularity of one eviction. A node there will reject transactions the rest of the network accepts, and its fee estimation will be reading its own eviction noise.
Bounding the severity honestly: an ordinary transaction at 10 sat/vB was still accepted in every case, including after the fill. This is not a node that stops working. It is a node whose mempool policy diverges from the network's for reasons unrelated to fee market conditions.
Whether that is acceptable is a judgement call, and at the 300 MB default it does not arise at all. But it is a different question from the one being argued, and it is the one that bites the operators the floor exists for. If the goal is not breaking existing -maxmempool=5..38 configurations, raising those configs' effective value with a startup warning would achieve that without putting anyone's mempool at three transactions.
On the constants themselves, independently arrived at and identical: the three-way coupling through the two static_asserts in packages.h is real and easy to miss, and 3'904'000 / 976 is a tidier resolution of it than the 3'900'000 / 975 I had used. Same conclusion on the service bit: not reusing Libre Relay's 29, since the bit signals acceptance of a policy set and the two projects do not run the same one.
|
On point 3 (peering cost charged whether or not it works) — checked this against the actual code rather than just the measurement, and it holds up structurally, no need to redo the 24-node harness to believe it.
Agree the fix is cheap either way — move the branch up, or drop the reserved count — but given it's six tiers down, I'd lean toward reordering over shrinking the count. |
cre8tor-devops
left a comment
There was a problem hiding this comment.
Recommendation: do not merge this as-is. The 3.9M WU / 976 kvB / 3.904M package constants and NODE_DOG_MODE on bit 28 are correct. Three other choices in this patch are product bugs, not style nits. Fix them before this becomes v1, because they are painful to unwind once nodes are running the software.
Merge these
MAX_STANDARD_TX_WEIGHT = 3,900,000,MAX_PACKAGE_WEIGHT = 3,904,000,DEFAULT_CLUSTER_SIZE_LIMIT_KVB = 976. Thestatic_assertchain inpackages.his real. 3,904,000 / 976 is the right resolution of it (same 4k WU headroom Core uses). Independently reached by this PR and by the measurements in #1.NODE_DOG_MODEon bit 28, not Libre Relay's 29. The bit means "I accept this policy set." The two clients do not run the same set.- Tests, RPC/GUI plumbing, and the dedicated
p2p_dogmode_peering.pyharness. That is the right shape of a first release: a small overlay, tested.
Change these three before merge
1. Pin MAX_OP_RETURN_RELAY at 100,000. Do not inherit 975,000.
src/policy/policy.h still has MAX_OP_RETURN_RELAY = MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR. Raising tx weight therefore silently raises default -datacarriersize from 100k to 975k. The release notes mention this as a consequence. It is a second policy change, not a consequence.
Ordinals/inscriptions live in the witness. Runes use OP_RETURN, but 100k vB is already enormous. The announcement was 3.9M txs and 1-sat dust. It was not "almost-a-megabyte OP_RETURN by default."
Make it a literal:
static constexpr unsigned int MAX_OP_RETURN_RELAY{100'000};If 975k datacarrier is actually wanted, say that in the README and the original announcement, not as a footnote about a macro. Leaving it coupled also means every future weight tweak silently moves OP_RETURN again.
2. 1-sat dust is a default, not a cap. Do not change GetDustThreshold.
This PR keeps DUST_RELAY_TX_FEE{3000} and does return std::min(fee, CAmount{1}). That is the wrong mechanism.
Unmodified v31.1 already produces a 1-sat floor on every standard output type with -dustrelayfee=0. That flag is ALLOW_ANY | DEBUG_ONLY and is not gated on IsTestChain(). It works on mainnet today (measured in #1).
So the user-visible 1-sat outcome needs one line:
static constexpr unsigned int DUST_RELAY_TX_FEE{0};and no change to GetDustThreshold. The cap in this PR:
- removes the ability to run a stricter dust policy on this client (operators cannot restore 546)
- is extra fork delta against Core on every rebase
- buys nothing a default does not
Also put the cost side in the release notes, which currently only state the benefit. 1-sat P2TR is ~43–55 bytes of chainstate forever, paid by every full node. A P2TR key-path spend is ~57.5 vB, so these outputs are not economically spendable at the 1 sat/vB relay floor. They are markers. Say that.
3. Do not change Flatten from 40x to 5x.
src/txmempool.cpp Flatten() now uses cluster_size_vbytes * 5 so -maxmempool=5 still boots. That is optimizing for a config file at the expense of mempool behaviour.
The 40x factor is from Core 794a8cec (2015): cheap min-fee fill against the descendant/cluster limit. Byte-cost of a fill at the floor is similar either way (tialkan's point). What actually changes, measured on this PR vs stock at -maxmempool=5:
- stock holds ~30 × 100 kvB txs; min fee stays at the floor
- this PR holds 3 × 975 kvB txs; min fee jumps ~13× after four txs; the fifth is rejected while the mempool is ~90% full
A node at the floor then diverges from the network's mempool for reasons that are not the fee market. Default 300 MB users never see it. The operators the floor exists for do.
Keep * 40. The honest number is -maxmempool must be ≥ 40 MB. Put a startup error (already exists) plus a release-note migration line. If compatibility with -maxmempool=5 is mandatory, clamp with a warning and bump, not a quieter DoS margin.
-blocksonly currently implies -maxmempool=5. Raise that default to the same floor or blocksonly nodes will not start.
Peering: keep it, fix the slot order
Four reserved outbound slots costing four inbound is the right idea. Document the 114 → 110 inbound drop at default -maxconnections.
Do not ship it at the bottom of ThreadOpenConnections. The chain is: anchor block-relay → full-relay → block-relay → extra full-relay → extra block-relay → dogmode → feeler. Six senior tiers. If full-relay is short, dogmode never runs, but inbound capacity was already subtracted. tialkan measured 6-of-6 signalling peers → 0 of 4 dog slots. Mehdalon confirmed that in the source.
Move the dogmode branch up (after full-relay, before or with block-relay), or do not reserve slots you will not fill. Also clamp m_max_outbound_dog_mode the same way full-relay and block-relay are clamped (Mehdalon's point 2; the follow-up commit is good if it is actually in this head).
Add -nodogmode even if the default is on. Unconditional extra outbound slots with no off switch is an ops surprise. Libre Relay can get away with that as a one-person experiment. A named client should not.
Change the user-agent from /Satoshi: to /DOGMode:. Identity belongs in the version handshake, not only in a service bit.
Project-level, not this diff
- Enable Issues. Two RFCs exist as draft PRs only because they are off. That is how you lose reviewers.
- Do not rebrand the rest of Core. User-agent + README + these release notes are enough. A 300k-line string replace is rebase poison.
- No binaries until Guix-reproducible builds and a security contact that is this project, not Core's
SECURITY.md. - One miner path on signet before mainnet slogans. Relay policy without a miner is a diary.
What I would ship as v1
Four commits on top of v31.1:
- weight/package/cluster constants, OP_RETURN pinned at 100k, Flatten stays 40x,
-blocksonlymempool default raised, tests updated DUST_RELAY_TX_FEE = 0onlyNODE_DOG_MODEbit 28, 4 slots, reordered inThreadOpenConnections, clamped,-nodogmode, user-agent/DOGMode/- release notes that include the 40 MB floor, the inbound-slot cost, and the UTXO cost of 1-sat outputs
That is the entire client. Everything else is Core and should stay Core.
Happy to turn (1)–(3) into a patch on this branch if the author wants it. I am not going to open a competing PR.
… two The finding described the reserved-slot branch as sitting after the full-relay and block-relay branches, implying two tiers ahead of it. The if/else-if chain in ThreadOpenConnections actually has five ahead: anchor block-relay, full-relay, block-relay, GetTryNewOutboundPeer, and the periodic extra-block-relay branch. This strengthens rather than weakens the result. Each pass commits to one connection type, so any permanently unmet senior quota blocks the chain from reaching the reserved branch at all, which is what the 6-of-6 run shows. Correction due to Mehdalon on bitcoindogmode/bitcoin#3; verified against the source before applying.
$DOG Mode: raise the maximum transaction weight we are willing to relay/mine from 400,000 WU to 3,900,000 WU (975 kvB), just under the 4,000,000 WU consensus block weight limit. A maximum size transaction still fits into a block (leaving room for a minimal coinbase) and its serialization always fits within the 4 MB MAX_PROTOCOL_MESSAGE_LENGTH p2p limit. Dependent limits are raised to keep the policy consistent: - DEFAULT_CLUSTER_SIZE_LIMIT_KVB: 101 -> 976 kvB, so that a maximum size transaction is accepted to the mempool as a cluster of one. - MAX_PACKAGE_WEIGHT: 404,000 -> 3,904,000 WU, preserving the invariant MAX_PACKAGE_WEIGHT >= MAX_STANDARD_TX_WEIGHT. Note that MAX_OP_RETURN_RELAY is defined as MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR, so the default -datacarriersize rises from 100,000 to 975,000 vbytes. The -maxmempool floor check scales with the cluster size limit (40x -> 5x), keeping the minimum at ~5 MB instead of rising to 40 MB, so small-mempool configurations remain usable. Tests are updated for the new limits; wallet_miniscript.py keeps testing at the legacy ~330KB miniscript size since building a ~3.8MB miniscript string is impractical.
$DOG Mode: the dust limit for spendable outputs is lowered from 294-546 sats (depending on output type) to a global 1 sat, i.e. only zero-value outputs are considered dust. GetDustThreshold() now caps the fee-rate-based computation at 1 sat, so -dustrelayfee can only lower the dust limit further (0 disables it entirely, as before) but never raise it above 1 sat. Tests are updated for the new limit. Note that the wallet's change output viability is dominated by the discard feerate and is largely unaffected; mempool_ephemeral_dust.py keeps exercising ephemeral dust with 0-value outputs, the only dust left.
$DOG Mode nodes advertise the NODE_DOG_MODE service flag (bit 28) and open up to MAX_DOG_MODE_CONNECTIONS (4) extra long-lived outbound connections to peers advertising the same flag, so that $DOG Mode nodes form a relay subnetwork for transactions that are non-standard on the rest of the network. The implementation is a port of Libre Relay's preferential peering (commit 66518db "Peer with 4 other Libre Relay nodes" by Peter Todd), renamed to $DOG Mode and adapted to current connection types. We use service bit 28 so as to stay distinct from Libre Relay's NODE_LIBRE_RELAY (bit 29). A new "dog" connection type is plumbed through net, net_processing, RPC (getpeerinfo, addconnection for tests), bitcoin-cli -netinfo and the GUI. Peers connected to via a dog connection that do not advertise NODE_DOG_MODE are disconnected after the version handshake. Tests: - new functional test p2p_dogmode_peering.py covering the advertised service flag, dog connection setup, transaction relay over dog connections, the connection capacity limit and disconnection of peers missing the flag - unit test coverage for the new connection type and service flag - p2p_eviction.py: bump -maxconnections by the 4 new outbound slots, preserving the tested number of inbound slots (Libre Relay disabled this test instead) - p2p_node_network_limited.py: expect the new service flag
a39b0e7 to
067450b
Compare
|
@Mehdalon Thanks for the detailed review. Addressed:
Squashed into policy/peering commits. |
|
@tialkan Thanks for the independent measurements — catching the six-tier peering-priority point is the most useful structural critique here.
|
|
@Mehdalon Right — six tiers of priority ahead, not two. Agree reorder over shrink. Will fold that in when deciding the change in the peering commit. |
|
@Mehdalon Thanks for the detailed review. Addressed:
Squashed into policy/peering commits. |
|
@tialkan Thanks for the independent measurements — catching the six-tier peering-priority point is the most useful structural critique here.
|
|
@Mehdalon Right — six tiers of priority ahead, not two. Agree reorder over shrink. Will fold that in when deciding the change in the peering commit. |
|
tl;dr: All review points addressed in |
|
Not a code review, the experts above have the internals covered, and from What I'd push on is release notes, because that's what determines whether
Adoption is the thing that makes preferential peering work at all. Documented |
|
Went further on this — sanitizers, fuzzing, and two stress scenarios nobody had measured yet. Results below. Sanitizers (TSan + ASan)Rebuilt with Fuzzing (cold, no seed corpus)
Wallet performance with many 1-sat UTXOsNobody had measured what a wallet actually holding dust does. Filled one to 10,000+ 1-sat UTXOs on regtest:
Linear scaling, ~0.1ms/UTXO. None of this is catastrophic, but a 1-second Mixed-size mempool at the
|
|
Everything here is more than reasonable. Further optimizations should be placed into a followup commit. |
|
Are there any remaining objections to a merge? |
|
The mixed-size mempool run is the right control. The sharp minfee jumps I reported are the all-max-size fill, not organic traffic. That still matters for someone sitting on The wallet No remaining objection to merge from me. |
|
reviewed the notes / will test when binaries drop |
One, and it is mine to raise because it is mine to correct: I ran into it building a node-adoption chart, working out how a node on this branch would be identifiable on the network at all — which is its own finding, at the bottom. Surveying the bit space to answer that turned up bit 28 already declared, by Knots, for something considerably more awkward than another relay policy. Bit 28 is
|
| bit | nodes | declared as | dominant user agent |
|---|---|---|---|
| 24 | 0 | NODE_UTREEXO_TMP (Knots) |
— |
| 26 | 406 | NODE_REPLACE_BY_FEE (Knots) |
/Satoshi:28.1.0/Knots:20250305/ (218) |
| 27 | 1,622 | NODE_REDUCED_DATA (Knots) |
/Satoshi:29.3.0/Knots:20260508/ (783) |
| 28 | 350 | NODE_BLAKE2B (Knots) |
/Satoshi:29.4.1/Knots:20260508rc4/ (304) |
| 29 | 32 | NODE_LIBRE_RELAY / NODE_MALICIOUS (Knots) |
/Satoshi:30.2.0/ (24) |
348 of the 350 report Knots 29.4.1/20260508rc4 or rc5; the remaining 2 report /Satoshi:29.1.0/, which I cannot account for. And it is unconditional on those builds rather than opt-in:
| Knots build | nodes | setting bit 28 |
|---|---|---|
20260508 (current release) |
1,473 | 0 |
29.4.1/20260508rc4 |
318 | 318 (100%) |
29.4.1/20260508rc5 |
30 | 30 (100%) |
So 350 is a release-candidate population. Knots is 4,662 reachable nodes on this crawl, so when 29.4.1 ships, occupancy of bit 28 grows by roughly an order of magnitude on its own.
Unoccupied on this crawl: bits 1, 5, 7–9, 14–24, 30, 31.
Reproducible from the raw snapshot; index 1 of each node record is the user agent, index 3 the services bitfield:
import json, urllib.request
from collections import Counter
req = urllib.request.Request(
"https://bitnodes.io/api/v1/snapshots/latest/",
headers={"user-agent": "service-bit-survey/1.0"}, # bitnodes 403s the urllib default
)
snap = json.load(urllib.request.urlopen(req, timeout=60))
BIT = 28
seen = Counter()
for v in snap["nodes"].values():
if isinstance(v, list) and len(v) > 3 and isinstance(v[3], int) and v[3] >> BIT & 1:
seen[v[1] or "(none)"] += 1
print(f"bit {BIT}: {sum(seen.values())} nodes")
for agent, n in seen.most_common(5):
print(f"{n:6d} {agent}")Why this is worse than the bit 29 case
In #2 I argued against reusing Libre Relay's bit because the two policy sets differ. That argument applies here with more force, because the collision is not with another relay policy — it is with a hardfork signal.
- The handshake check cannot help. This PR disconnects a
dogpeer that does not advertise the flag. These nodes do advertise it. They pass. - The semantics invert the project's central claim. DOG Mode's whole position is that it changes no consensus rule and cannot split the chain. On this bit, a node built from this branch is indistinguishable from a node announcing that it enforces a BLAKE2b proof-of-work hardfork — and to a Knots peer reading the bit, that is what it is saying. I would not want to have to defend that sentence, and I do not think this PR should have to.
- The peers are useless in the ordinary sense too. They are Knots, the client whose relay policy sits furthest in the opposite direction from this one. A peer occupying a
dogslot is not merely unhelpful for propagating a 3.9M WU transaction — it is guaranteed to reject it. - The slots fill wrong from day one. 350 nodes on bit 28, zero running this branch. A node's four reserved outbound slots would go to Knots nodes essentially every time, and that worsens as 29.4.1 rolls out. The mechanism arrives inverted rather than degrading gradually.
- It compounds with the priority ordering @Mehdalon found. Six higher-priority tiers ahead of the dogmode branch is already thin; spending what gets through on peers that cannot relay for you leaves close to nothing.
There is also no second signal to fall back on. This PR does not change CLIENT_NAME or the user agent — a node built from it announces itself the way Core does — so the service bit is the only way a DOG Mode node is identifiable, by a peer or by anyone counting adoption.
Suggestion
Take one from 14–23 rather than the 24–31 neighbourhood. Knots occupies 24, 26, 27 and 28 there and labels 29 NODE_MALICIOUS; the low range is empty by a wide margin and nothing is near it.
Whichever bit is chosen, check it two ways before release: read the flag declarations in the clients that are actually deployed — including their release branches, not just master — and count the live network. I did neither properly when I suggested 28 in #2. I read Libre Relay's patch and picked an adjacent number, which is exactly the "inheriting by copying a patch" I was warning against three paragraphs earlier in the same comment.
Caveats, as with my earlier measurements: bitnodes sees listening nodes only, this is a single crawl, and the counts move. The declaration in Knots' source does not move, and the occupancy is not marginal enough for the counts to change the conclusion.
Flagging the provenance rather than leaving you to find it: this came out of the node page on bitcoindogmode.com, which counts client adoption from the same crawl. That page will show the bit 28 figure beside what those nodes report themselves as running, and will not fold it into a DOG Mode total — 350 nodes attributed to a client with no binary would be wrong in the direction that flatters us. Happy to word it differently if you think it reads unfairly against this PR.
Bit 28 is declared as NODE_BLAKE2B by Bitcoin Knots in the v29.4.1 release candidates: a BLAKE2b proof-of-work hardfork signal, set unconditionally on those builds and included in their SeedsServiceFlags(). Mode nodes on bit 28 would be indistinguishable from hardfork-signalling Knots nodes, and the reserved dog outbound slots would fill with Knots peers that reject 3.9M WU transactions. Bit 14 is declared by no deployed client (checked Core, Knots master and v29.4.1 rc4, Libre Relay v30.2) and unadvertised on the live network. It deliberately sits outside the 24-31 experimental range, which Knots is actively occupying (bits 24, 26-29).
|
Confirmed and fixed in 76ae6e2 — thank you for catching this, and for making it reproducible. Verification (independent of your crawl):
Fix: Change surface was three lines ( Process fix adopted from your comment: before each release, re-check flag declarations in deployed clients' release branches/tags (not just master) and re-count the live network. The bit-28 population moving 13% in 10 hours is exhibit A for why the second check can't be a one-time thing. On the bitcoindogmode.com node page: attributing the bit-28 population to Knots rather than folding it into a Mode total reads fairly to me — that's what those nodes are actually running. |
|
Not a code review, the ground above is well covered. One point is still open in the head and What the head does. Two facts.
So the question is not whether runes need it (they do not) and not whether Core forbids it The ask, whichever way it goes: make it a headline line in the README and the release notes, From our side: our node will run whatever ships. We are neutral on the number and firm |
|
ack 76ae6e2 |
|
Since this merged, a follow-up rather than a review — two lines I measured and you accepted here on 28 August ("will add to release notes") haven't landed in One correction first: the datacarrier line @dogofbitcoin's ask implied was missing is already in the release notes, with the number in it —
So that half was already done. The gap is the README, which still opens with "Website: Coming Soon" and says nothing about the weight, the dust limit or the data carrier — an operator reading it today learns none of the three things that actually shipped. The other two are the ones you accepted and that haven't landed:
Paste-ready, in the file's existing voice:
- Lowering the dust limit grows the UTXO set, and the cost is borne by every
node rather than by the party creating the outputs. Measured steady-state
marginal chainstate cost per additional UTXO is approximately 55 bytes for
P2TR, 43 for P2WPKH and 40 for P2PKH. These entries are effectively
permanent: a P2TR key path input is 57.5 vB, so a 1 satoshi output is
economic to spend only below roughly 0.017 sat/vB, far under the 1 sat/vB
relay floor. Operators sizing storage should treat this as growth that does
not reverse when fees fall.Under P2P, after the peering bullets: - The 4 reserved outbound slots are added to the automatic outbound maximum,
so inbound capacity falls by the same 4 at any given `-maxconnections`
(114 -> 110 at the default). A node running this improves its own
transaction reach by slightly reducing what it serves back to the network.
Bitcoin DOG Mode is a fork of Bitcoin Core with three relay policy changes:
a maximum standard transaction weight of 3,900,000 WU instead of 400,000, a
global 1 satoshi dust limit instead of 294-546, and preferential peering
between nodes advertising `NODE_DOG_MODE` (service bit 14). Because
`MAX_OP_RETURN_RELAY` is one quarter of the standard transaction weight, the
default `-datacarriersize` rises to 975,000 bytes as a consequence.
Consensus rules are unchanged. A DOG Mode node validates blocks exactly as
Bitcoin Core does and cannot cause a chain split. See
`doc/release-notes-dogmode-policy.md` for the full list, including what the
dust change costs node operators.Adjust or ignore the wording freely — the ask is only that the four numbers land where someone deciding whether to run this will see them, rather than staying in a merged thread. On the OP_RETURN size itself I have no position; I care that whichever default is kept is written down as a choice rather than inherited silently. |
|
Independent Linux/Qt build and regtest validation of the merged commit Environment: native linux/amd64 Ubuntu 24.04 container, GCC 13.3, Qt 6.4.2, Release build with wallet, GUI tests, QR, DBus, and external-signer support compiled in; IPC was disabled. The source checkout was mounted read-only and the build/runtime container had no network access. Results:
The receiving wallet confirmed 1.25 regtest BTC from the normal GUI send and 2 BTC from the GUI PSBT flow. No failure was found in the tested paths. This is a test report rather than a code-review ACK or release certification. Windows/macOS, hardware signers, wallet encryption/backup UI, public-network sync, the full functional suite, fuzzing, and multiprocess IPC were outside scope. Full report, dependency recipe, exact package versions, configure log, test logs, result CSV, and binary hashes: https://gist.github.com/J25dunn/bde00e07f72f8fa45d19ad3054d02812 |
|
Two mainnet measurements against the merge commit 1 sat output, relayed. Peering, what we see after 42 hours. The full node has two inbound peers advertising bit 14 (the Umbrel above and a third party's Umbrel package on 8433, both connected since 2026-09-12) and has filled 0 of its 4 The 3.9 MB test is built (a taproot script-path reveal at 3,899,868 WU, the shape that passes a node with The DNS seed now reports two numbers on seed.dogofbitcoin.org: nodes advertising bit 14 that the crawler can reach (2 today), and the subset it can answer with (1, since an A record carries no port and the reference seeder hands out full nodes on 8333 only). The seeder build is public at github.com/dogofbitcoin/bitcoin-seeder, one commit on sipa's pinned base. |
|
The second mainnet test from the 2026-09-06 list, run tonight against the merge commit A 3,847,522 WU transaction, relayed from one DOG Mode node to another. The control is the pair, not the transaction alone. Its parent commit What it carries. A real ordinals envelope, Anyone running a DOG Mode node can check it on their own machine while it is in mempools: The 96,189 sat fee is only paid if a miner includes it, so the coin will be spent back through a Core node shortly and every dog mempool will drop the test when that confirms. The txid, both hashes and the readings are recorded either way. Two things worth passing on to anyone else testing this policy, both of which cost us a run to find:
One correction we owe, from 09-07. @tialkan pointed out that our OP_RETURN comment implied the datacarrier line was missing from the release notes when it was already there with the number in it. He was right, and we should have said so then: the release notes carry that line. The README gap he named in the same comment is the one still open. |
|
One measurement before a tag, with a fix offered. With any #10 moves it below them, with a regtest test that fails on |
Summary
Three $DOG Mode policy changes, split into reviewable commits:
policy: raise maximum standard transaction weight to 3,900,000 WU (975 kvB, from 400,000 WU) — just under the 4,000,000 WU consensus block weight limit, so a maximum size transaction still fits in a block with a minimal coinbase and within the 4 MB
MAX_PROTOCOL_MESSAGE_LENGTHp2p limit. Dependent limits are raised to stay consistent:-limitclustersizedefault 101 -> 976 kvB andMAX_PACKAGE_WEIGHT404,000 -> 3,904,000 WU. The-maxmempoolsanity floor is rescaled (40x -> 5x the cluster size limit) so the minimum stays ~5 MB. BecauseMAX_OP_RETURN_RELAY = MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR, the default-datacarriersizerises to 975,000 vbytes.policy: lower the dust limit to a global 1 sat (from 294-546 sats depending on output type).
GetDustThreshold()now caps the feerate-based computation at 1 sat;-dustrelayfeecan only lower the limit further (0 disables it entirely, as before), never raise it above 1 sat.net: add $DOG Mode preferential peering — a port of Libre Relay's preferential peering (commit
66518dbe2f8f, Peter Todd). Nodes advertiseNODE_DOG_MODE(service bit 28, distinct from Libre Relay's bit 29) and open up to 4 extra long-lived outbounddogconnections to peers advertising the same flag, so $DOG Mode nodes form a relay subnetwork for transactions that are non-standard elsewhere. Peers on dog connections that don't advertise the flag are disconnected after the version handshake. Visible ingetpeerinfo,bitcoin-cli -netinfo, and the GUI peers window.Release notes included in
doc/release-notes-dogmode-policy.md.Tests
p2p_dogmode_peering.py(v1+v2 transports): flag advertisement, dog connection setup, transaction relay over dog connections, connection capacity limit, and disconnection of peers missing the flag. New unit testcnode_dog_mode_conn_testcovers the new connection type and service flag.transaction_tests,txpackage_tests,orphanage_tests,coinselector_tests,mempool_accept,mempool_dust,mempool_ephemeral_dust,mempool_cluster,mempool_package_limits,mempool_sigoplimit,wallet_send,wallet_sendall,wallet_fundrawtransaction,p2p_orphan_handling,p2p_segwit,p2p_eviction,p2p_node_network_limited,rpc_psbt,interface_bitcoin_cli, test framework constants).p2p_eviction.pyis kept enabled by bumping-maxconnectionsby the 4 new outbound slots (Libre Relay disabled it instead).Test plan
test_bitcoin(full unit suite): passes.tool_bitcoin.py, which requires the multiprocessbitcoin-nodebinary (not built withoutENABLE_IPC); verified to fail identically on the base branch with the same build config.test/lint/lint-files.pypasses; branch diff isgit diff --checkclean.