Skip to content

feat: re-fork cometbft-bera onto the v0.40.x line - #63

Draft
fridrik01 wants to merge 3 commits into
bera-v0.40.xfrom
refork-cometbft-bera-to-0.40
Draft

feat: re-fork cometbft-bera onto the v0.40.x line#63
fridrik01 wants to merge 3 commits into
bera-v0.40.xfrom
refork-cometbft-bera-to-0.40

Conversation

@fridrik01

Copy link
Copy Markdown
Collaborator

This PR moves the Berachain CometBFT re-fork from upstream v0.39.x to upstream v0.40.x. It establishes the new bera-v0.40.x line, which starts from the pristine upstream tag v0.40.0 and carries the same Berachain changes that the v0.39 re-fork (#51) carried. The v0.39 re-fork was still a draft and never reached testnet or mainnet, so this PR replaces it rather than building on it. The bera-v0.39.x branch was not merged here. Everything was recreated on top of v0.40.0, so the branch keeps clean upstream ancestry.

The goal is unchanged from #51. A bera-v1.x network can move to this binary with a rolling restart and no state migration, because the consensus sign bytes, hashes, wire format and on-disk state are byte identical to bera-v1.x. The difference this time is that the claim is backed by committed tests (see Testing) instead of an out-of-tree harness.

Why v0.40

Upstream supports two release lines at a time, and v0.40 is now the newer of the two. Since the v0.39 re-fork had not been deployed anywhere yet, starting the Berachain line on v0.40.0 costs nothing today and gives us the longest runway before the next forced move. It also means the first upstream sync merges we do will be small v0.40.x patch releases rather than a jump from v0.39 to v0.40.

What is in it

The Berachain feature set is exactly the one described in #51, ported without behavior changes:

  • Proposer-Based Timestamps. Block time comes from the proposer and is validated with the synchrony parameters. Per-vote timestamps are gone from the signed bytes. PBTS is always on.
  • BLS12-381 signature aggregation. When every validator uses a BLS key, precommit signatures collapse into one aggregated commit that is gossiped whole and used for fast catch-up.
  • NextBlockDelay (ADR-115) and NextProposerAddress in ProcessProposal, which beacon-kit relies on.
  • Consensus parameter, validator and protobuf layout aligned with bera-v1.x, including the consensus p2p messages (has_proposal_block_part at 10, whole commit at 11).
  • Privval SignBytes request and compressed BLS public key support.
  • The follow-up fixes that were added to feat: re-fork cometbft-bera onto the v0.39.x line #51 during its review (PBTS timeliness at every round, MaxVotesCount cap on gossiped commits, race-free Commit.String, vote gossip skipping a whole-commit LastCommit).

On top of that, three small additions that came out of testing against a real bera-v1.x binary. All three are additive and wire compatible:

  1. The consensus WAL now persists the message receive time, using the same proto field bera-v1.x uses. Without it, a proposal replayed from the WAL after a restart had no receive time, failed the PBTS timeliness check, and the node tried to prevote nil for a height it had already voted on. The privval double-sign guard caught it, but every restart logged a signing error. With the field persisted, replay behaves like bera-v1.x.
  2. ValidatorUpdate understands both public key encodings. bera-v1.x writes pub_key_bytes plus pub_key_type, upstream writes pub_key. FinalizeBlock responses are persisted in the state store and read back during crash recovery and for /block_results, so the node now reads either form and writes both. This keeps the store readable in both directions of a rolling upgrade.
  3. The e2e workflows run on ubuntu-latest instead of the upstream depot runners, the same fix that was applied to bera-v0.39.x.

How this was built

The eight commits of #51 were cherry-picked one at a time onto v0.40.0. Most of them applied cleanly. Conflicts came up in a handful of files where upstream moved between v0.39.4 and v0.40.0, and they were resolved by hand:

  • state/execution.go. Upstream added a per-block validator set cache and turned the buildLastCommitInfoFromStore helpers into methods on BlockExecutor. The port keeps upstream's methods and threads FeatureParams and NextProposerAddress through them.
  • types/params.go and crypto/encoding/codec.go. Upstream added the ml-dsa-65 and secp256k1eth key types. They are kept, next to the synchrony and feature params from the port.
  • privval/retry_signer_client.go. Upstream made retries abortable on close. The ported SignBytes follows the same pattern.
  • version/version.go, CHANGELOG.md, one test file.

A file-by-file comparison with the #51 tree, after subtracting everything upstream changed between v0.39.4 and v0.40.0, leaves only the three additions above. In other words, the Berachain payload is the same diff as before, re-based by hand.

How to review

The bulk of the diff is the same code that was reviewed in #51, and the same split applies. The generated .pb.go files, crypto/bls12381, verifyAggregatedCommit, MakeBLSCommit, IsTimely and most of consensus/state.go are faithful ports and can be skimmed.

Worth a closer look:

  • The conflict resolutions listed above, since they are the only places where port code and new upstream code meet. state/execution.go is the largest one.
  • consensus/msgs.go and proto/tendermint/consensus/wal.proto for the WAL receive time.
  • types/protobuf.go (PubKeyFromValidatorUpdate, NormalizeValidatorUpdates) and state/store.go for the validator update encoding.
  • test/compat/, which is new. Start with its README.

Upstream changes between v0.39.4 and v0.40.0

Every upstream commit in that range was checked for interaction with the port. The ones that matter:

  • MedianTime no longer counts nil precommits (upstream marks this state-breaking). It does not change anything here. MedianTime is only used on the BFT Time path, and this fork forces PBTS, exactly as bera-v1.x does.
  • MaxSignatureSize grew to fit ml-dsa-65 signatures, which shrinks the maximum tx bytes a proposer offers by a few kilobytes per validator. It is a local sizing value, not part of any hash or validation, and beacon-kit sets the block size to 100 MB, so it has no practical effect. Noted for completeness.
  • New default block params, new default max tx size, new key types in keys.proto, and the privval noise listener. All defaults or additive.
  • The vote extension self-verification added to signVote. Vote extensions are disabled on Berachain.

Compatibility with bera-v1.x

  • Sign bytes, signatures, hashes, the persisted state proto, the consensus p2p wire format and the WAL are byte identical to bera-v1.x. This is asserted by the committed vectors test, in both directions.
  • A bera-v1.x data directory can be reused as is. This was verified live, including moving back to bera-v1.x on the same directory.
  • A mixed validator set keeps producing aggregated commits with round 0 through a rolling upgrade, and nodes on either version can block-sync from each other.

Two cosmetic differences remain and are asserted as such by the tests. The consensus params JSON written by this fork carries an empty authority object that bera-v1.x ignores, and ValidatorUpdate.pub_key is a non-nullable message here so an unset key still encodes as an empty field that bera-v1.x skips.

Testing

Unit tests and lint pass with and without -tags bls12381, including -race on the consensus, types, state and privval packages. make proto-gen reproduces the committed generated files.

New in this PR, under test/compat/:

  • vectors_test.go replays a fixed set of inputs through this fork and compares every output byte for byte with vectors produced by the bera-v1.x code. It covers vote and proposal sign bytes and BLS signatures, aggregated and individual commits and their hashes, block and header hashes, consensus params, validator sets, evidence, the persisted state, the ABCI responses, every consensus p2p message, the WAL entries, and the genesis and key files. It runs in CI.
  • gen-v1x/ is the generator. It is a separate Go module pinned to the bera-v1.x head, so the vectors can be regenerated from scratch at any time. Its -verify mode does the reverse check, decoding and verifying what this fork produces with bera-v1.x code.
  • rolling_upgrade.sh starts real nodes. Phase A runs one validator on bera-v1.x, then bera-v0.40.x, then bera-v1.x, then bera-v0.40.x, all on the same data directory. Phase B runs four BLS validators and moves them one at a time from bera-v1.x to bera-v0.40.x, restarts an upgraded node mid-soak, lets a node on each version block-sync the blocks it missed from mixed peers, sends a transaction through the upgraded network, rolls one node back to bera-v1.x and forward again. At every stage it checks that all validators signed every block, that both versions proposed, that commits stayed aggregated with round 0, that block hashes agree on every node, and that the logs are free of codec errors.

All of the above passed locally against the bera-v1.x head. The remaining step before testnet is the same devnet rolling upgrade exercise that was planned for #51, with beacon-kit built against this branch.

test/compat is meant to stay in the tree after the upgrade, and its pieces have different lifetimes:

  • The vectors test and its JSON files are a permanent guard. Once mainnet runs this line, the bytes they pin must never change again, and the most likely way they would change is a future upstream sync merge. The test is static data and a sub-second unit test in CI, so it is the cheapest protection we can have for that. Only the wording changes over time, from "matches bera-v1.x" to "matches what mainnet runs".
  • gen-v1x/ only exists to regenerate the vectors from the bera-v1.x code and to run the reverse check. It pins a bera-v1.x commit, so it is the one piece with a maintenance cost. It can be removed once bera-v1.x is retired from the network.
  • rolling_upgrade.sh takes the two binaries as parameters, so after this upgrade it becomes a generic "previous release versus next release" check. Every future bera-v0.40.x release is a rolling upgrade on mainnet, and the script proves data-dir reuse in both directions, a mixed validator set, block-sync across versions and rollback in a few minutes. A follow-up can rename the variables and drop the bera-v1.x defaults.

Base and upstream syncing

The bera-v0.40.x line is created from the upstream tag, as before:

git fetch upstream --tags
git checkout -b bera-v0.40.x v0.40.0

Future upstream releases are adopted by merging the release tag into bera-v0.40.x through a normal PR. Using the next patch release as the example:

# fetch exactly this release tag, explicitly from upstream cometbft
git fetch upstream tag v0.40.1
# verify the local tag matches what upstream serves (the two SHAs must match)
git ls-remote upstream 'refs/tags/v0.40.1^{}'
git rev-parse 'v0.40.1^{commit}'
# start a sync branch from the current tip of bera-v0.40.x
git checkout -b sync-upstream-v0.40.1 origin/bera-v0.40.x
# merge the tag, recording its upstream origin in the commit message
git merge v0.40.1 -m "Merge upstream cometbft tag v0.40.1 into bera-v0.40.x"
# resolve conflicts, rebuild and test, then open a PR into bera-v0.40.x

Sync PRs must be merged with a merge commit, never squashed. Squashing breaks the shared ancestry with upstream, so every later sync re-conflicts on changes we already took. Merging rather than rebasing also keeps the branch append-only, so open PRs, clones and go module consumers survive a sync, and each conflict is resolved once inside a reviewed PR.

The Berachain changes stay easy to inspect. git diff v0.40.0 bera-v0.40.x shows everything we maintain on top of the latest upstream tag, and the test/compat vectors test fails if a sync ever touches the consensus bytes.

Urgent upstream fixes can be cherry-picked between releases, and the next sync merge supersedes them.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Protobuf Lint / lint (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 21, 2026, 4:12 PM

@fridrik01
fridrik01 force-pushed the refork-cometbft-bera-to-0.40 branch from 4679819 to 3d10c38 Compare August 21, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant