Skip to content

BIP332: Stale Tip Relay - #2241

Merged
murchandamus merged 1 commit into
bitcoin:masterfrom
pseudoramdom:staletip-bip
Sep 9, 2026
Merged

BIP332: Stale Tip Relay#2241
murchandamus merged 1 commit into
bitcoin:masterfrom
pseudoramdom:staletip-bip

Conversation

@pseudoramdom

Copy link
Copy Markdown
Contributor

This BIP is a specification for a new an opt-in P2P message,staletip, for relaying recent stale tips between peers.
A proof-of-concept implementation is available at https://github.com/w0xlt/bitcoin/tree/staletip-v4.

Mailing list post - https://gnusha.org/pi/bitcoindev/d92f1615-368b-4406-b326-a1799c72a555n@googlegroups.com/
Discussion - https://groups.google.com/g/bitcoindev/c/AwOPNxF15mU

Feedback is welcome.

LLM Disclosure: LLMs were used to help develop the proposal’s initial outline and to rephrase portions of the draft. The proposal’s technical content was reviewed and finalized by the authors.

@edilmedeiros edilmedeiros left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for submitting your proposal. Gave a round of review, some comments are about the technique itself, but others are more editorial to try to improve and clarify the specification.

Comment thread bip-staletip.md Outdated

This BIP defines a new [BIP 434][BIP434] feature id ("the `staletip` feature"):

* `https://github.com/w0xlt/bitcoin/tree/staletip-v4`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be left to the reference implementation section.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL here is the temporary experimental featureid, not just a reference implementation link. BIP 434 requires an unassigned experimental feature to use a globally unique identifier, such as its repository URL. I’ll clarify that distinction and move the standalone implementation link to the Reference Implementation section.

For features published as a BIP, the featureid SHOULD be the assigned BIP number, eg "BIP434", or be based on the BIP number (eg, "BIP434v2" where the "v2" suffix covers versioning, or "BIP434.3" where the ".3" suffix covers part 3 of the BIP). For experimental features that do not (yet) have a BIP number assigned, some other unique identifier MUST be chosen, such as a URL to the repository where development is taking place, or the sha256 digest of some longer reference.

Comment thread bip-staletip.md Outdated
Comment on lines +82 to +84
If this specification is assigned a BIP number, the feature id SHOULD be updated
to a BIP-number based identifier as recommended by BIP 434, for example
`BIPxxx` or `BIPxxxv1`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to expect an specification proposal might not get a number… Why not specify BIPxxx for now an update when a number is assigned, without the conditional?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this gets confusing here: the feature is called staletip everywhere, but the serialization in BIP434 featureid part of the message should be BIPxxx?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

staletip is the descriptive feature name and P2P message command. It isn’t the serialized BIP 434 featureid. The experimental implementation currently uses the repository URL as its globally unique featureid, and the published specification will the BIP number. I’ll make that distinction explicit.

Comment thread bip-staletip.md Outdated
Comment on lines +86 to +101
The `staletip` feature data MUST contain at least one byte. The first byte is a
boolean, `prefers_blocks`, indicating whether the node advertising the feature
prefers to collect the full block data associated with stale tips:

* `\x00`: the node prefers header-only announcements and does not request that
announcements be delayed until block data is available.
* `\x01`: the node prefers announcements that include availability of block
data where practical.

Nodes receiving an empty `staletip` feature data field, or a first byte other
than `\x00` or `\x01`, MUST ignore that peer's `staletip` feature
advertisement. They SHOULD NOT disconnect solely because the feature data is not
understood.

For future compatibility, nodes MUST ignore any additional feature data bytes
after the first byte.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the feature is versioned, this slack is really needed? I mean, why not accept EXACTLY one byte with the two expected values?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point :). BIP 434’s versioned feature IDs give us a clean way to make future changes. I’ll update the specification to require one byte with value 0x00 or 0x01.

Comment thread bip-staletip.md Outdated
Comment thread bip-staletip.md Outdated
Comment thread bip-staletip.md Outdated
Comment on lines +257 to +258
- Nodes SHOULD ignore any headers found to be invalid, and SHOULD NOT disconnect
or otherwise punish peers for relaying invalid headers[^rat-ignoreinvalid].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this open a DoS vector?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DoS vectors for p2p are for processing time or memory/disk usage or amplification attacks where we receive a small amount of garbage data and then send a large amount of garbage data to other peers as a result. The bandwidth it takes to send the message isn't a DoS vector, because there's a million ways to send data to a p2p node, and you can't prevent all of them.

Comment thread bip-0332.md
- Nodes SHOULD ignore any headers found to be invalid, and SHOULD NOT disconnect
or otherwise punish peers for relaying invalid headers[^rat-ignoreinvalid].
- If `have_block` is `true`, nodes that prefer to collect the full block data
SHOULD request missing block data for the announced stale branch in the normal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHOULD request missing block data for the announced stale branch in the normal

Sounds that MAY would work better here. What if I already have that specific stale block, SHOULD I request it again?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I already have that specific stale block

Spec says "SHOULD request missing block data". If the node already has that stale block, its data is not missing and should not be requested again.

Comment thread bip-0332.md
Comment on lines +662 to +668
[^rat-compressedheader]: Omitting the previous block hash from each header saves
32 bytes per header (40%), as this field can be reconstructed from the
preceding headers in the message. This does not apply to the first header,
which is why the fork point must be included explicitly. This BIP does not
attempt to omit `nBits` or compress `nTime` or `nVersion`, because
reconstructing those fields is significantly more complicated for
comparatively much less potential gain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It assumes there's only one stale branch, which is probably not true in testnet and may even happen in mainnet. The design can deal with more than one competing branch?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each staletip message describes one linear branch. If there are multiple competing branches, the sender will have to send a separate message for each one. I’ll clarify this in the spec.

Comment thread bip-0332.md
Comment on lines +296 to +319
#### Reconstructing Headers

Headers may be reconstructed from a `staletip` message via the following
algorithm:

```
std::vector<CBlockHeader> headers;
uint256 prev_hash = staletip_msg.fork_point;
for (const auto& ch : staletip_msg.headers) {
headers.emplace_back({
.nVersion = ch.version,
.hashPrevBlock = prev_hash,
.hashMerkleRoot = ch.merkle_root,
.nTime = ch.time,
.nBits = ch.bits,
.nNonce = ch.nonce,
});
prev_hash = headers.back().GetHash();
}
```

Note that headers are reconstructed in order, from oldest (closest to the
`fork_point`) to newest (the stale tip itself).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is necessary and potentially implies maintaining this code in the specification over time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having the reconstruction algorithm in the specification is helpful. That said, it doesn’t need to be C++, so I’ve replaced it with pseudocode.

Comment thread bip-staletip.md Outdated
@0xB10C

0xB10C commented Aug 12, 2026

Copy link
Copy Markdown

Thanks for opening this here. Plan to review!

@pseudoramdom

Copy link
Copy Markdown
Contributor Author

Thanks for submitting your proposal. Gave a round of review, some comments are about the technique itself, but others are more editorial to try to improve and clarify the specification.

@edilmedeiros Thank you so much for taking the time to review.


Rebased and added a commit (d055be1) to address Edil's feedback.
The question of whether to reserve a one-byte BIP 324 message ID remains open.

Comment thread bip-0332.md
| Name | Value | Meaning |
| ---- | ----- | ------- |
| `MAX_STALETIP_HEADERS` | 20 | Recommended maximum number of `CompressedHeader` entries in one `staletip` message |
| `STALETIP_RECENT_WINDOW` | 1000 blocks | Recommended maximum distance from the receiver's active tip |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the reason for choosing 1000 blocks when Bitcoin Core, as the most used implementation, would reject any header more than ~144 blocks old (GetAntiDoSWorkThreshold)? With the current implementation, it seems that these older headers would be sent out but would not be accepted by the peer by default. Or does the BIP intend to recommend that participating peers accept headers to their database within STALETIP_RECENT_WINDOW, basically recommending to lower the 144 block threshold?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a round number; more inspired by the average interval between stale blocks on mainnet (so that there's a decent chance a new node using this feature would see data fairly quickly) than anything else.

@mzumsande mzumsande Aug 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, but I find it not ideal to ask for stale headers and then immediately discard them because they are too old to satisfy GetAntiDoSWorkThreshold.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STALETIP_RECENT_WINDOW number is arbitrary. 1000 blocks gives monitoring nodes roughly seven days to discover staletip data.

With the current implementation, it seems that these older headers would be sent out but would not be accepted by the peer by default.

Good catch, this might've been an oversight in the PoC implementation. Staletip relay path should probably define a separate bounded acceptance policy.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, but I find it not ideal to ask for stale headers and then immediately discard them because they are too old to satisfy GetAntiDoSWorkThreshold.

Right, but that's a problem with the implementation, not the spec? (Beyond the spec noting the interaction to help implementers avoid pitfalls, anyway)

I think 48 hours / 288 blocks hours is likely enough for a stale tip to propagate to 99% of interested nodes just via extra-block-relay-only connections if only 1% of listening nodes support stale tip relay, and 24 hours / 144 blocks is likely enough if ~2.5% of listening nodes support the feature. Those numbers are worse if the extra-block-relay-only connections fail frequently due to addrman poisoning, or better if there are enough stale tip relay nodes that they form connected components in the p2p network. Also better if non-listening nodes are participating. So to me, that says changing this to recommend 144 wouldn't be overly concerning, but I'd still lean towards 1000 to maximise propagation chance to anyone interested.

def propogate(lnodes, support, blks):
    probes = blks*2 # 10min blks, 5min extra-b-r-o-conn
    have = [False]*int(lnodes*support)
    t = len(have)
    assert t > 1
    have[0] = True
    for _ in range(probes):
        for i in range(t): # each node makes a probe
            k = int(random.random() * lnodes) # who does i connect to?
            if k < t and (have[i] or have[k]): # staletip relay?
                have[i] = have[k] = True
    return sum(have)/t
    
sum(propogate(20000, 0.01, 144) for _ in range(20))/20
# 0.542 -- 54% of staletip relay nodes see the stale tip with 1% sat over 144 blocks
sum(propogate(20000, 0.01, 288) for _ in range(20))/20
# 0.9894999999999999 -- 99% of staletip relay nodes see the stale tip with 1% sat over 288 blocks
sum(propogate(20000, 0.025, 144) for _ in range(20))/20
# 0.9994999999999999 -- 99% of staletip relay nodes see the stale tip with 2.5% sat over 144 blocks
sum(propogate(20000, 0.003, 1000) for _ in range(20))/20
# 0.9991666666666668 -- 99% of staletip relay nodes see the stale tip with 0.3% sat over 1000 blocks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking that in the context of the increased peer limit of Bitcoin Core, this could be even more reliable if blocks-only peers optionally participate in stale tip relay. Perhaps this BIP could make a recommendation whether stale tips should be announced on blocks-only connections or not.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, keeping 1000 blocks in the spec gives stale tips a better chance to propagate while adoption is low. Implementations can still apply stricter local limits.
I’ll add a note that implementations reusing their normal header-processing path should ensure its admission policy is compatible with its staletip recency window.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocks-only peers optionally participate in stale tip relay

I don't think block-relay-only mode is documented in a BIP anywhere (treating it as a node configuration details rather than a standard), so it seems slightly weird to introduce it as a BIP-worthy concept here?

I think stale tip relay/announcements should be done on block-relay-only connections, because that allows the extra-block-relay-only connections to make the p2p graph dynamic for the purposes of this message, making it more likely to achieve wide distribution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense.
The BIP can remain connection-agnostic. I won’t add a normative rule about block-relay-only connections.
Implementations can still enable it on their extra block-relay-only connections they maintain, but I don’t think the feature should be restricted to those connections.

@danielabrozzoni danielabrozzoni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a very good read :) I left a question and a couple of nits.

Comment thread bip-staletip.md Outdated
Comment thread bip-staletip.md Outdated
Comment thread bip-0332.md Outdated

@murchandamus murchandamus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough work, this proposal was an excellent read and looks already very mature.

I’m assigning BIP332 to this proposal. Please add a README entry, incorporate the BIP number into the preamble and set the Assigned header to 2026-08-21.

Comment thread bip-0332.md
Comment on lines +484 to +485
* Nodes MAY advertise stale tips only when the stale tip itself has a
difficulty greater than some higher threshold, for example 1,000,000.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn’t one of the interesting uses for this BIP be to map the many branches of testnet4?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I can see mapping competing branches on testnet4 being useful.
Do you think the BIP should avoid prescribing a difficulty threshold for testnet3 and testnet4 and leave it entirely to local policy? We could drop these two recommendations and rely on the general proof-of-work, rate-limiting, and resource-bound requirements instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Difficulty one blocks can be created at a rate of ~200/s per TH/s, so a map that includes those doesn't seem very useful, and likely opens you up to a disk filling attack: make your diff-1 alternative blocks contain 1MvB worth of txs that conflict with the main chain. They're only listed as MAY and SHOULD requirements, so you could still do this while complying with the bip, though.

@murchandamus murchandamus Aug 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t have strong feelings about supporting Testnet3 and Testnet4. Especially Testnet3 seems obsolete to me, Forknet4 slightly less so, although my understanding is that it is of limited use. Forknet4 is largely immune to blockstorms, it just branches more. I was just asking because Testnet4 came to mind in the context, but I am not overly invested.

Comment thread bip-0332.md
| Name | Value | Meaning |
| ---- | ----- | ------- |
| `MAX_STALETIP_HEADERS` | 20 | Recommended maximum number of `CompressedHeader` entries in one `staletip` message |
| `STALETIP_RECENT_WINDOW` | 1000 blocks | Recommended maximum distance from the receiver's active tip |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking that in the context of the increased peer limit of Bitcoin Core, this could be even more reliable if blocks-only peers optionally participate in stale tip relay. Perhaps this BIP could make a recommendation whether stale tips should be announced on blocks-only connections or not.

@murchandamus murchandamus changed the title BIP Draft: Stale Tip Relay BIP332: Stale Tip Relay Aug 21, 2026
@Vahid12015595

This comment was marked as low quality.

@murchandamus

Copy link
Copy Markdown
Member

@0xB10C, any others: did anyone want to do another round of review?

@pseudoramdom: What do you think about the status of this? Do you still have open planned work or are looking for input on this in some specific aspect?

@0xB10C

0xB10C commented Sep 9, 2026

Copy link
Copy Markdown

Gave this a read and found everything I was going to mention (bitcoin/bitcoin#33266, and fingerprinting via stale blocks, which is already possible, see bitcoin/bitcoin#24571) already included. I don't have anything to add or change at the moment.

@pseudoramdom

Copy link
Copy Markdown
Contributor Author

Gave this a read and found everything I was going to mention (bitcoin/bitcoin#33266, and fingerprinting via stale blocks, which is already possible, see bitcoin/bitcoin#24571) already included. I don't have anything to add or change at the moment.

Thanks for reviewing!

@pseudoramdom: What do you think about the status of this? Do you still have open planned work or are looking for input on this in some specific aspect?

Responded to some comments and made minor edits to address Daniela's feedback
I'm done with the changes for now. @murchandamus

@murchandamus murchandamus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks for the quick turnaround. I gave the new edits a quick read, they look good to me. Let’s get this published, if anyone notices some minor improvements they can be follow-ups.

@murchandamus
murchandamus merged commit 4adea84 into bitcoin:master Sep 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants