From 3c4f038f06b2cbf4ba7c1bda57212a7fbbcc8ca9 Mon Sep 17 00:00:00 2001 From: satya kwok Date: Tue, 16 Jun 2026 04:10:11 +0700 Subject: [PATCH 01/32] consensus: add LoadValidatorsFast to skip proposer-priority advance (#1693) (#5857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1693. For replay paths that only need validator addresses + voting powers (`buildLastCommitInfo`, `buildExtendedCommitInfo`), advancing proposer priorities forward from the most recent stored checkpoint is wasted work. The advance is `O(height % valSetCheckpointInterval)` and dominates replay time on chains with infrequent validator-set changes. The original issue reports ~10x slowdown observed in production. This PR's benchmark on a 100-validator set shows up to **903x** at worst case (load at `checkpoint + 99,999`). ## Changes - Add `Store.LoadValidatorsFast(height)` that returns the `*ValidatorSet` without `IncrementProposerPriority`. The returned set has correct addresses, public keys, and voting powers; `ProposerPriority` reflects the last stored checkpoint, not the requested height. Documented that callers needing accurate proposer priority must continue to use `LoadValidators`. - Wire `buildLastCommitInfoFromStore` and `buildExtendedCommitInfoFromStore` to use the fast path. Both downstream paths only call `TM2PB.Validator` (`types/protobuf.go:41`) which reads `PubKey.Address()` and `VotingPower` only — no proposer priority access. - Refactor `LoadValidators` internals into a shared `loadValidatorsAtHeight` helper so both fast and full paths share the lookup logic. - Update mock `state/mocks/store.go`. ## Bench `state/store_test.go::BenchmarkLoadValidatorsSawtooth` on AMD EPYC, 100-validator set, ValidatorsInfo header persisted at the queried height but ValidatorSet only stored at checkpoint: | Offset from checkpoint | LoadValidators | LoadValidatorsFast | Speedup | |------------------------|----------------|--------------------|---------| | at_checkpoint | 165 µs | 158 µs | 1.04x | | +1 | 149 µs | 137 µs | 1.09x | | +100 | 270 µs | 134 µs | 2.0x | | +10,000 | 12.4 ms | 132 µs | 94x | | +99,999 | 126 ms | 140 µs | **903x** | The sawtooth pattern described in the issue is reproduced and eliminated. Reproduce locally: ``` go test ./state -bench BenchmarkLoadValidatorsSawtooth -benchtime=1s -run=^$ ``` ## Compatibility - `LoadValidators` semantics unchanged (still advances proposer priority). - `LoadValidatorsFast` is additive — no caller is forced to migrate. - Interface change is additive — third-party `state.Store` implementations need to add the new method, but this is a single small addition (mock provided as a template in the diff). Signed-off-by: satyakwok <119509589+satyakwok@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 4 ++ state/execution.go | 25 +++++++++- state/store.go | 85 +++++++++++++++++++++----------- state/store_test.go | 117 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 487e01c7b4d..6b5b1b4f718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,10 @@ ([\#5889](https://github.com/cometbft/cometbft/pull/5889)) - `[execution]` cache validator set within a block cycle. ([\#5834](https://github.com/cometbft/cometbft/pull/5834)) +- `[state]` skip the proposer-priority advance when loading validators for the + block-replay commit-info path (`LoadValidatorsFast`); up to ~900x faster at + the largest checkpoint offsets. + ([\#5204](https://github.com/cometbft/cometbft/issues/5204)) - `[consensus]` reuse encode/decode buffers in WALEncoder and WALDecoder. ([\#5865](https://github.com/cometbft/cometbft/pull/5865)) - `[blocksync]` validate blocksync response sender and signature count diff --git a/state/execution.go b/state/execution.go index d682def3171..b21acec2f29 100644 --- a/state/execution.go +++ b/state/execution.go @@ -538,6 +538,29 @@ func (blockExec *BlockExecutor) setLastValidatedBlock(old, new *types.Block) { //--------------------------------------------------------- // Helper functions for executing blocks and updating state +// fastValidatorLoader is the duck-typed interface for stores that can return a +// ValidatorSet without advancing proposer priorities. dbStore implements it +// (see state/store.go); external Store implementations don't have to. The +// helper below uses the fast path when available and falls back to +// LoadValidators otherwise, so the addition stays additive for third-party +// Store implementors. +type fastValidatorLoader interface { + LoadValidatorsFast(int64) (*types.ValidatorSet, error) +} + +// loadValidatorsForCommitInfo loads the ValidatorSet at height for building ABCI +// CommitInfo records, which only read validator addresses and voting powers. +// When store implements LoadValidatorsFast it is used to skip the +// IncrementProposerPriority loop that dominates replay time on chains with +// infrequent validator-set changes (issue #1693); otherwise it falls back to +// LoadValidators. +func loadValidatorsForCommitInfo(store Store, height int64) (*types.ValidatorSet, error) { + if fast, ok := store.(fastValidatorLoader); ok { + return fast.LoadValidatorsFast(height) + } + return store.LoadValidators(height) +} + func buildLastCommitInfoFromStore(block *types.Block, store Store, initialHeight int64) abci.CommitInfo { if block.Height == initialHeight { // check for initial height before loading validators // there is no last commit for the initial height. @@ -545,7 +568,7 @@ func buildLastCommitInfoFromStore(block *types.Block, store Store, initialHeight return abci.CommitInfo{} } - lastValSet, err := store.LoadValidators(block.Height - 1) + lastValSet, err := loadValidatorsForCommitInfo(store, block.Height-1) if err != nil { panic(fmt.Errorf("failed to load validator set at height %d: %w", block.Height-1, err)) } diff --git a/state/store.go b/state/store.go index b4e12f7ed72..e2052382b57 100644 --- a/state/store.go +++ b/state/store.go @@ -547,44 +547,73 @@ func (store dbStore) SaveFinalizeBlockResponse(height int64, resp *abci.Response // LoadValidators loads the ValidatorSet for a given height. // Returns ErrNoValSetForHeight if the validator set can't be found for this height. +// +// If the validator set was last persisted at a checkpoint earlier than height, +// LoadValidators advances proposer priorities forward via +// IncrementProposerPriority. That advance is O(height - lastStoredHeight) and +// can dominate replay time on chains with infrequent validator-set changes +// (issue #1693). For call sites that only read validator addresses and voting +// powers (e.g. building ABCI VoteInfo / CommitInfo), prefer LoadValidatorsFast. func (store dbStore) LoadValidators(height int64) (*types.ValidatorSet, error) { - valInfo, err := loadValidatorsInfo(store.db, height) + valInfo, lastStoredHeight, err := store.loadValidatorsAtHeight(height) if err != nil { - return nil, ErrNoValSetForHeight{height} + return nil, err } - if valInfo.ValidatorSet == nil { - lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged) - valInfo2, err := loadValidatorsInfo(store.db, lastStoredHeight) - if err != nil || valInfo2.ValidatorSet == nil { - return nil, - fmt.Errorf("couldn't find validators at height %d (height %d was originally requested): %w", - lastStoredHeight, - height, - err, - ) - } - - vs, err := types.ValidatorSetFromProto(valInfo2.ValidatorSet) - if err != nil { - return nil, err - } - + vs, err := types.ValidatorSetFromProto(valInfo.ValidatorSet) + if err != nil { + return nil, err + } + if lastStoredHeight != height { vs.IncrementProposerPriority(cmtmath.SafeConvertInt32(height - lastStoredHeight)) // mutate - vi2, err := vs.ToProto() - if err != nil { - return nil, err - } - - valInfo2.ValidatorSet = vi2 - valInfo = valInfo2 } + return vs, nil +} - vip, err := types.ValidatorSetFromProto(valInfo.ValidatorSet) +// LoadValidatorsFast loads the ValidatorSet for a given height without +// advancing proposer priorities forward from the most recent checkpoint. +// +// The returned ValidatorSet has correct addresses, public keys, and voting +// powers; ProposerPriority fields reflect the last stored checkpoint, not the +// requested height. Callers that need accurate proposer priority MUST use +// LoadValidators instead. +// +// This method is intentionally not part of the Store interface — adding it +// there would force every external Store implementation to provide it. +// Callers should use the duck-typed helper in state/execution.go which falls +// back to LoadValidators when the underlying store does not implement this +// fast path. +func (store dbStore) LoadValidatorsFast(height int64) (*types.ValidatorSet, error) { + valInfo, _, err := store.loadValidatorsAtHeight(height) if err != nil { return nil, err } + return types.ValidatorSetFromProto(valInfo.ValidatorSet) +} + +// loadValidatorsAtHeight returns the ValidatorsInfo whose ValidatorSet covers +// height, together with the height at which that ValidatorSet was actually +// stored. If the requested height has no stored ValidatorSet (i.e. unchanged +// since the last checkpoint), the call falls back to the most recent stored +// checkpoint and returns the difference via lastStoredHeight so callers can +// decide whether to apply IncrementProposerPriority. +func (store dbStore) loadValidatorsAtHeight(height int64) (*cmtstate.ValidatorsInfo, int64, error) { + valInfo, err := loadValidatorsInfo(store.db, height) + if err != nil { + return nil, 0, ErrNoValSetForHeight{height} + } + if valInfo.ValidatorSet != nil { + return valInfo, height, nil + } - return vip, nil + lastStoredHeight := lastStoredHeightFor(height, valInfo.LastHeightChanged) + valInfo2, err := loadValidatorsInfo(store.db, lastStoredHeight) + if err != nil || valInfo2.ValidatorSet == nil { + return nil, 0, fmt.Errorf( + "couldn't find validators at height %d (height %d was originally requested): %w", + lastStoredHeight, height, err, + ) + } + return valInfo2, lastStoredHeight, nil } func lastStoredHeightFor(height, lastHeightChanged int64) int64 { diff --git a/state/store_test.go b/state/store_test.go index ebc085acbf4..fb536559352 100644 --- a/state/store_test.go +++ b/state/store_test.go @@ -84,6 +84,123 @@ func BenchmarkLoadValidators(b *testing.B) { } } +// fastLoader is the duck-typed interface tests use to reach LoadValidatorsFast +// without exporting the dbStore concrete type or adding the method to the +// public Store interface. Mirrors the production helper in state/execution.go. +type fastLoader interface { + LoadValidatorsFast(int64) (*types.ValidatorSet, error) +} + +// BenchmarkLoadValidatorsSawtooth reproduces the issue #1693 sawtooth pattern: +// a ValidatorsInfo header is persisted at every height (recording +// LastHeightChanged) but the full ValidatorSet is only stored at checkpoints. +// LoadValidators must therefore advance proposer priorities from the most +// recent checkpoint to the requested height, an O(offset) loop that dominates +// replay time at large offsets. LoadValidatorsFast skips that loop. +func BenchmarkLoadValidatorsSawtooth(b *testing.B) { + const valSetSize = 100 + + config := test.ResetTestRoot("state_sawtooth_") + defer os.RemoveAll(config.RootDir) + dbType := dbm.BackendType(config.DBBackend) + stateDB, err := dbm.NewDB("state", dbType, config.DBDir()) + require.NoError(b, err) + stateStore := sm.NewStore(stateDB, sm.StoreOptions{DiscardABCIResponses: false}) + state, err := stateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) + require.NoError(b, err) + + state.Validators = genValSet(valSetSize) + state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) + require.NoError(b, stateStore.Save(state)) + + const checkpoint = int64(sm.ValSetCheckpointInterval) + // Persist the full ValidatorSet only at the checkpoint; for offsets above + // it we write a header that records LastHeightChanged but leaves + // ValidatorSet nil, forcing LoadValidators back to the checkpoint. + require.NoError(b, sm.SaveValidatorsInfo(stateDB, checkpoint, 1, state.Validators)) + + fast, ok := stateStore.(fastLoader) + require.True(b, ok, "dbStore must implement LoadValidatorsFast") + + offsets := []struct { + name string + height int64 + }{ + {"at_checkpoint", checkpoint}, + {"+1", checkpoint + 1}, + {"+100", checkpoint + 100}, + {"+10000", checkpoint + 10_000}, + {"+99999", checkpoint + 99_999}, + } + + for _, o := range offsets { + // header-only record so LoadValidators falls back to the checkpoint + if o.height != checkpoint { + // saveValidatorsInfo writes a header-only record (no full + // ValidatorSet) whenever height != LastHeightChanged and + // height % valSetCheckpointInterval != 0, which is exactly what + // we need for the sawtooth setup. + require.NoError(b, sm.SaveValidatorsInfo(stateDB, o.height, 1, state.Validators)) + } + + b.Run("LoadValidators/"+o.name, func(b *testing.B) { + for n := 0; n < b.N; n++ { + if _, err := stateStore.LoadValidators(o.height); err != nil { + b.Fatal(err) + } + } + }) + b.Run("LoadValidatorsFast/"+o.name, func(b *testing.B) { + for n := 0; n < b.N; n++ { + if _, err := fast.LoadValidatorsFast(o.height); err != nil { + b.Fatal(err) + } + } + }) + } +} + +// TestLoadValidatorsFastParity verifies LoadValidatorsFast returns the same +// validator identities (addresses, public keys, voting powers) as LoadValidators +// across a sawtooth of off-checkpoint heights. The only intentional difference +// is ProposerPriority — Fast leaves it at the checkpoint snapshot — which is +// safe for the ABCI VoteInfo / CommitInfo call sites that only consume the +// other fields. +func TestLoadValidatorsFastParity(t *testing.T) { + stateDB := dbm.NewMemDB() + stateStore := sm.NewStore(stateDB, sm.StoreOptions{DiscardABCIResponses: false}) + + vals := genValSet(10) + + const checkpoint = int64(sm.ValSetCheckpointInterval) + require.NoError(t, sm.SaveValidatorsInfo(stateDB, checkpoint, 1, vals)) + + fast, ok := stateStore.(fastLoader) + require.True(t, ok, "dbStore must implement LoadValidatorsFast") + + for _, offset := range []int64{0, 1, 100, 10_000, 99_999} { + h := checkpoint + offset + if offset != 0 { + // Header-only record so LoadValidators falls back to the + // checkpoint and applies IncrementProposerPriority(offset). + require.NoError(t, sm.SaveValidatorsInfo(stateDB, h, 1, vals)) + } + + slow, err := stateStore.LoadValidators(h) + require.NoError(t, err, "LoadValidators(h=%d)", h) + quick, err := fast.LoadValidatorsFast(h) + require.NoError(t, err, "LoadValidatorsFast(h=%d)", h) + + require.Equal(t, slow.Size(), quick.Size(), "size mismatch at offset=%d", offset) + for i := range slow.Validators { + s, q := slow.Validators[i], quick.Validators[i] + require.Equal(t, s.Address, q.Address, "address mismatch at offset=%d idx=%d", offset, i) + require.True(t, s.PubKey.Equals(q.PubKey), "pubkey mismatch at offset=%d idx=%d", offset, i) + require.Equal(t, s.VotingPower, q.VotingPower, "voting power mismatch at offset=%d idx=%d", offset, i) + } + } +} + func TestPruneStates(t *testing.T) { testcases := map[string]struct { makeHeights int64 From e338da318d9e15d89b026fe53558817477337403 Mon Sep 17 00:00:00 2001 From: songgaoye <217724508+songgaoye@users.noreply.github.com> Date: Tue, 16 Jun 2026 05:10:25 +0800 Subject: [PATCH 02/32] fix(blocksync): fix redo event loss, stale event cancellation, and optimize retry timer (#5592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three improvements to the blocksync block requester: **Fix: redo events no longer silently dropped** `redoCh` was typed `chan p2p.ID` with capacity 1. In the dual-peer scenario, if `redo(peerA)` was already pending in the channel, a concurrent `redo(peerB)` would be silently dropped — peerB would stall until the retry timer fired. Replace with `chan struct{}` (capacity 1) as a coalesced wake-up signal. Peer IDs are appended to a mutex-protected `redoPeers` slice in `redo()`, so no event is ever lost regardless of call frequency. **Fix: stale redo events no longer cancel new requests** When the retry timer fired and restarted the outer loop, any pending redo events in `redoPeers` carried over to the next iteration. If the new iteration happened to pick the same peer, those stale events would cancel a valid new request. A generation counter is incremented at the start of each outer loop. `redo()` records the current generation alongside the peer ID; events from a previous generation are silently discarded. The retry timer path also clears `redoPeers` before restarting. **Perf: reuse retry timer instead of allocating a new one each loop** Before (new timer allocated per OUTER_LOOP iteration): - 19396 ns/op, 63488 B/op, 768 allocs/op After (single timer, reset on each iteration): - 7589 ns/op, 248 B/op, 3 allocs/op #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: Alex | Cosmos Labs Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 + blocksync/pool.go | 97 +++++++++++++++++++++++++++++------- blocksync/pool_bench_test.go | 44 ++++++++++++++++ blocksync/pool_test.go | 47 +++++++++++++++++ blocksync/pool_timer_test.go | 52 +++++++++++++++++++ 5 files changed, 224 insertions(+), 18 deletions(-) create mode 100644 blocksync/pool_bench_test.go create mode 100644 blocksync/pool_timer_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b5b1b4f718..a70f76ad765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -197,6 +197,8 @@ ([\#5692](https://github.com/cometbft/cometbft/pull/5692)) - `[p2p]` feat(p2p): add adaptive sync for comet-p2p ([\#5705](https://github.com/cometbft/cometbft/pull/5705)) +- `[blocksync]` fix redo event loss, stale event cancellation, and optimize retry timer + ([\#5592](https://github.com/cometbft/cometbft/pull/5592)) ### FEATURES diff --git a/blocksync/pool.go b/blocksync/pool.go index e520c351e48..692e8e968a9 100644 --- a/blocksync/pool.go +++ b/blocksync/pool.go @@ -722,23 +722,32 @@ type bpRequester struct { pool *BlockPool height int64 gotBlockCh chan struct{} - redoCh chan p2p.ID // redo may got multiple messages, add peerId to identify repeat + redoCh chan struct{} // coalesced wake-up: capacity 1, signals that redoPeers has entries newHeightCh chan int64 mtx cmtsync.Mutex peerID p2p.ID secondPeerID p2p.ID // alternative peer to request from (if close to pool's height) gotBlockFrom p2p.ID + generation uint64 // incremented each outer loop; protects against stale redo events; protected by mtx + redoPeers []redoEvent // peers that need to be re-requested; protected by mtx block *types.Block extCommit *types.ExtendedCommit } +// redoEvent pairs a peer ID with the generation in which the redo was requested, +// so stale events from a previous outer-loop iteration can be discarded. +type redoEvent struct { + peerID p2p.ID + gen uint64 +} + func newBPRequester(pool *BlockPool, height int64) *bpRequester { bpr := &bpRequester{ pool: pool, height: height, gotBlockCh: make(chan struct{}, 1), - redoCh: make(chan p2p.ID, 1), + redoCh: make(chan struct{}, 1), newHeightCh: make(chan int64, 1), peerID: "", @@ -842,11 +851,13 @@ func (bpr *bpRequester) reset(peerID p2p.ID) (removedBlock bool) { } // Tells bpRequester to pick another peer and try again. -// NOTE: Nonblocking, and does nothing if another redo -// was already requested. +// Nonblocking: state is stored under mtx; the channel is a coalesced wake-up. func (bpr *bpRequester) redo(peerID p2p.ID) { + bpr.mtx.Lock() + bpr.redoPeers = append(bpr.redoPeers, redoEvent{peerID, bpr.generation}) + bpr.mtx.Unlock() select { - case bpr.redoCh <- peerID: + case bpr.redoCh <- struct{}{}: default: } } @@ -909,13 +920,56 @@ func (bpr *bpRequester) newHeight(height int64) { } } +// requestRetryTimer wraps time.Timer with helpers that safely drain pending +// events before reuse so bpRequester can reset a single timer across retries. +type requestRetryTimer struct { + timer *time.Timer + duration time.Duration +} + +func newRequestRetryTimer(duration time.Duration) *requestRetryTimer { + t := time.NewTimer(duration) + if !t.Stop() { + <-t.C + } + return &requestRetryTimer{ + timer: t, + duration: duration, + } +} + +func (rt *requestRetryTimer) Stop() { + if !rt.timer.Stop() { + select { + case <-rt.timer.C: + default: + } + } +} + +func (rt *requestRetryTimer) Reset() { + rt.Stop() + rt.timer.Reset(rt.duration) +} + +func (rt *requestRetryTimer) C() <-chan time.Time { + return rt.timer.C +} + // Responsible for making more requests as necessary // Returns only when a block is found (e.g. AddBlock() is called) func (bpr *bpRequester) requestRoutine() { gotBlock := false + retryTimer := newRequestRetryTimer(requestRetrySeconds * time.Second) + defer retryTimer.Stop() OUTER_LOOP: for { + bpr.mtx.Lock() + bpr.generation++ + currentGen := bpr.generation + bpr.mtx.Unlock() + bpr.pickPeerAndSendRequest() poolHeight := bpr.pool.Height() @@ -923,8 +977,7 @@ OUTER_LOOP: bpr.pickSecondPeerAndSendRequest() } - retryTimer := time.NewTimer(requestRetrySeconds * time.Second) - defer retryTimer.Stop() + retryTimer.Reset() for { select { @@ -935,24 +988,35 @@ OUTER_LOOP: return case <-bpr.Quit(): return - case <-retryTimer.C: + case <-retryTimer.C(): if !gotBlock { bpr.Logger.Debug("Retrying block request(s) after timeout", "height", bpr.height, "peer", bpr.peerID, "secondPeerID", bpr.secondPeerID) + bpr.mtx.Lock() + bpr.redoPeers = nil + bpr.mtx.Unlock() bpr.reset(bpr.peerID) bpr.reset(bpr.secondPeerID) continue OUTER_LOOP } - case peerID := <-bpr.redoCh: - if bpr.didRequestFrom(peerID) { - removedBlock := bpr.reset(peerID) - if removedBlock { - gotBlock = false + case <-bpr.redoCh: + bpr.mtx.Lock() + events := bpr.redoPeers + bpr.redoPeers = nil + bpr.mtx.Unlock() + for _, ev := range events { + if ev.gen != currentGen { + continue // stale event from a previous iteration + } + if bpr.didRequestFrom(ev.peerID) { + removedBlock := bpr.reset(ev.peerID) + if removedBlock { + gotBlock = false + } } } // If both peers returned NoBlockResponse or bad block, reschedule both // requests. If not, wait for the other peer. if len(bpr.requestedFrom()) == 0 { - retryTimer.Stop() continue OUTER_LOOP } case newHeight := <-bpr.newHeightCh: @@ -962,10 +1026,7 @@ OUTER_LOOP: // If the second peer was just set, reset the retryTimer to give the // second peer a chance to respond. if picked := bpr.pickSecondPeerAndSendRequest(); picked { - if !retryTimer.Stop() { - <-retryTimer.C - } - retryTimer.Reset(requestRetrySeconds * time.Second) + retryTimer.Reset() } } case <-bpr.gotBlockCh: diff --git a/blocksync/pool_bench_test.go b/blocksync/pool_bench_test.go new file mode 100644 index 00000000000..03b1387ea09 --- /dev/null +++ b/blocksync/pool_bench_test.go @@ -0,0 +1,44 @@ +package blocksync + +import ( + "testing" + "time" +) + +// benchmarkRetryTimer simulates the retry loop in bpRequester. The legacy version +// allocated a fresh timer per iteration, while the optimized version reuses a single +// requestRetryTimer and simply resets it. The difference in allocations mirrors what +// we expect when a requester repeatedly retries fetching the same block. +func benchmarkRetryTimer(b *testing.B, reuse bool) { + const iterationsPerOp = 256 + b.ReportAllocs() + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if reuse { + rt := newRequestRetryTimer(time.Nanosecond) + for i := 0; i < iterationsPerOp; i++ { + rt.Reset() + } + rt.Stop() + } else { + for i := 0; i < iterationsPerOp; i++ { + timer := time.NewTimer(time.Nanosecond) + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + } + } + } +} + +func BenchmarkRetryTimerLegacy(b *testing.B) { + benchmarkRetryTimer(b, false) +} + +func BenchmarkRetryTimerReusable(b *testing.B) { + benchmarkRetryTimer(b, true) +} diff --git a/blocksync/pool_test.go b/blocksync/pool_test.go index 44a6df24256..8c3b77843a7 100644 --- a/blocksync/pool_test.go +++ b/blocksync/pool_test.go @@ -260,6 +260,53 @@ func TestBlockPoolTimeout(t *testing.T) { } } +func TestBPRequesterRedoPreservesBothPeers(t *testing.T) { + requester := newBPRequester(nil, 1) + requester.redo("peerA") + requester.redo("peerB") + + // Exactly one coalesced wake-up signal. + require.Equal(t, 1, len(requester.redoCh)) + + requester.mtx.Lock() + events := requester.redoPeers + requester.mtx.Unlock() + + peerSet := map[p2p.ID]struct{}{} + for _, ev := range events { + peerSet[ev.peerID] = struct{}{} + } + require.Contains(t, peerSet, p2p.ID("peerA")) + require.Contains(t, peerSet, p2p.ID("peerB")) +} + +// Regression test: with the old chan p2p.ID capacity-1 design, if a redo signal +// was already pending in the channel, a concurrent redo for the second peer would +// be silently dropped. Verify that no redo event is ever lost. +func TestBPRequesterRedoNeverDropsEvent(t *testing.T) { + requester := newBPRequester(nil, 1) + + // Two redo calls for peerA fill the old capacity-2 channel, then peerB's + // redo would have been dropped with the previous implementation. + requester.redo("peerA") + requester.redo("peerA") + requester.redo("peerB") + + // Still exactly one wake-up signal (coalesced). + require.Equal(t, 1, len(requester.redoCh)) + + requester.mtx.Lock() + events := requester.redoPeers + requester.mtx.Unlock() + + counts := map[p2p.ID]int{} + for _, ev := range events { + counts[ev.peerID]++ + } + require.Equal(t, 2, counts["peerA"]) + require.Equal(t, 1, counts["peerB"], "peerB redo must not be dropped") +} + func TestBlockPoolRemovePeer(t *testing.T) { peers := make(testPeers, 10) for i := 0; i < 10; i++ { diff --git a/blocksync/pool_timer_test.go b/blocksync/pool_timer_test.go new file mode 100644 index 00000000000..43cd90d84ed --- /dev/null +++ b/blocksync/pool_timer_test.go @@ -0,0 +1,52 @@ +package blocksync + +import ( + "testing" + "time" +) + +func TestRequestRetryTimerStopDrainsChannel(t *testing.T) { + rt := newRequestRetryTimer(5 * time.Millisecond) + t.Cleanup(rt.Stop) + + time.Sleep(10 * time.Millisecond) // let the timer fire + rt.Stop() + + select { + case <-rt.C(): + t.Fatal("timer channel should have been drained") + default: + } +} + +func TestRequestRetryTimerResetRearms(t *testing.T) { + rt := newRequestRetryTimer(5 * time.Millisecond) + t.Cleanup(rt.Stop) + + start := time.Now() + rt.Reset() + + select { + case <-rt.C(): + if time.Since(start) < 5*time.Millisecond { + t.Fatalf("timer fired too early: %v", time.Since(start)) + } + case <-time.After(50 * time.Millisecond): + t.Fatal("timer never fired after reset") + } +} + +func TestRequestRetryTimerResetDropsStaleEvents(t *testing.T) { + rt := newRequestRetryTimer(20 * time.Millisecond) + t.Cleanup(rt.Stop) + + for i := 0; i < 5; i++ { + rt.Reset() + select { + case <-rt.C(): + t.Fatalf("unexpected retry event on iteration %d", i) + case <-time.After(2 * time.Millisecond): + // retry signal should not fire before duration elapses + } + } +} From 05693b1ee6e056153fafdacc8fea5f03c0edb289 Mon Sep 17 00:00:00 2001 From: songgaoye <217724508+songgaoye@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:09:21 +0800 Subject: [PATCH 03/32] test(flowrate): fix flaky TestWriter by tolerating clock quantization in Idle (#5929) ## Description Fix CI failure at: https://github.com/cometbft/cometbft/actions/runs/27431355897/job/81082257035?pr=5879 `statusesAreEqual` compared `Status.Idle` with exact equality (`==`) while every other time-derived field (`Duration`, `TimeRem`, the rates) allowed a deviation. `Idle` is the difference of two timestamps each rounded to the 20ms `clockRate`, so scheduling jitter on loaded runners can push the two reads onto adjacent ticks, producing a spurious 20ms idle and failing the strict `==`. This compares `Idle` with the same `maxDeviationForDuration` tolerance, and makes `durationsAreEqual` symmetric (absolute diff) so it bounds drift in both directions. --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 3 +++ libs/flowrate/io_test.go | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a70f76ad765..623dce26e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### BUG FIXES +- `[flowrate]` fix flaky `TestWriter` by comparing `Idle` with a duration + tolerance instead of exact equality + ([\#5929](https://github.com/cometbft/cometbft/pull/5929)) - `[rpc]` escape the request `Host` in the endpoints listing page so it cannot break out of the generated HTML ([\#5921](https://github.com/cometbft/cometbft/pull/5921)) diff --git a/libs/flowrate/io_test.go b/libs/flowrate/io_test.go index b82f8ff2250..9acd6633fbc 100644 --- a/libs/flowrate/io_test.go +++ b/libs/flowrate/io_test.go @@ -168,7 +168,7 @@ func statusesAreEqual(s1 *Status, s2 *Status) bool { if s1.Active == s2.Active && s1.Start.Equal(s2.Start) && durationsAreEqual(s1.Duration, s2.Duration, maxDeviationForDuration) && - s1.Idle == s2.Idle && + durationsAreEqual(s1.Idle, s2.Idle, maxDeviationForDuration) && s1.Bytes == s2.Bytes && s1.Samples == s2.Samples && ratesAreEqual(s1.InstRate, s2.InstRate, maxDeviationForRate) && @@ -184,7 +184,11 @@ func statusesAreEqual(s1 *Status, s2 *Status) bool { } func durationsAreEqual(d1 time.Duration, d2 time.Duration, maxDeviation time.Duration) bool { - return d2-d1 <= maxDeviation + diff := d2 - d1 + if diff < 0 { + diff = -diff + } + return diff <= maxDeviation } func ratesAreEqual(r1 int64, r2 int64, maxDeviation int64) bool { From bbfd7d88b4dc2bb2b91594ea990f7b4442d976b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:13:11 -0400 Subject: [PATCH 04/32] build(deps): Bump golang.org/x/sync from 0.20.0 to 0.21.0 (#5927) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.20.0 to 0.21.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/sync&package-manager=go_modules&previous-version=0.20.0&new-version=0.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- kms/go.mod | 2 +- kms/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 17025178092..86c37ba48b0 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/crypto v0.52.0 golang.org/x/net v0.55.0 - golang.org/x/sync v0.20.0 + golang.org/x/sync v0.21.0 gonum.org/v1/gonum v0.17.0 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 diff --git a/go.sum b/go.sum index 4eb3ca8ef5b..99b29d99755 100644 --- a/go.sum +++ b/go.sum @@ -576,8 +576,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/kms/go.mod b/kms/go.mod index 6d0ddaacdc2..47555bac2f3 100644 --- a/kms/go.mod +++ b/kms/go.mod @@ -135,7 +135,7 @@ require ( golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.55.0 // indirect - golang.org/x/sync v0.20.0 // indirect + golang.org/x/sync v0.21.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect golang.org/x/text v0.37.0 // indirect diff --git a/kms/go.sum b/kms/go.sum index 1a181c38a29..b52fce9d39f 100644 --- a/kms/go.sum +++ b/kms/go.sum @@ -426,8 +426,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 87d5264799a9b30618a0dee4be5e680f070d305a Mon Sep 17 00:00:00 2001 From: songgaoye <217724508+songgaoye@users.noreply.github.com> Date: Tue, 16 Jun 2026 23:26:16 +0800 Subject: [PATCH 05/32] fix(blocksync): fix deadlock in AddBlock caused by holding pool.mtx during sendError (#5931) ## Summary Fix CI failure at: https://github.com/cometbft/cometbft/actions/runs/27214146296/job/80350996164 - `AddBlock` held `pool.mtx` via a deferred `Unlock` while calling `sendError` on an unbuffered channel. Any concurrent caller that also needed `pool.mtx` would deadlock until the channel was drained. - Fix by collecting the error in `sendErr` and dispatching it inside the `defer` after the mutex is released. - Adds a regression test `TestAddBlockDoesNotDeadlockOnSendError`. --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 3 +++ blocksync/pool.go | 18 +++++++++++------- blocksync/pool_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 623dce26e6a..2191a194380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ `VerifyVoteExtension` handlers are inconsistent halts the node with a clear `CONSENSUS FAILURE` instead of stalling the whole network ([\#5204](https://github.com/cometbft/cometbft/issues/5204)) +- `[blocksync]` fix deadlock in `AddBlock` caused by holding `pool.mtx` during + `sendError` + ([\#5931](https://github.com/cometbft/cometbft/pull/5931)) - `[blocksync]` hold `pool.mtx` and recompute `maxPeerHeight` in `Enable()` ([\#5888](https://github.com/cometbft/cometbft/pull/5888)) - `[inspect]` fix flaky `TestInspectRun` and consolidate start/stop handshake diff --git a/blocksync/pool.go b/blocksync/pool.go index 692e8e968a9..7ca88967166 100644 --- a/blocksync/pool.go +++ b/blocksync/pool.go @@ -366,7 +366,13 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, extCommit *ty } pool.mtx.Lock() - defer pool.mtx.Unlock() + var sendErr error + defer func() { + pool.mtx.Unlock() + if sendErr != nil { + pool.sendError(sendErr, peerID) + } + }() requester := pool.requesters[block.Height] if requester == nil { @@ -375,19 +381,17 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, extCommit *ty // can't punish it. But if the peer sent us a block we clearly didn't // request, we disconnect. if block.Height > pool.height || block.Height < pool.startHeight { - err := fmt.Errorf("peer sent us block #%d we didn't expect (current height: %d, start height: %d)", + sendErr = fmt.Errorf("peer sent us block #%d we didn't expect (current height: %d, start height: %d)", block.Height, pool.height, pool.startHeight) - pool.sendError(err, peerID) - return err + return sendErr } return fmt.Errorf("got an already committed block #%d (possibly from the slow peer %s)", block.Height, peerID) } if !requester.setBlock(block, extCommit, peerID) { - err := fmt.Errorf("requested block #%d from %v, not %s", block.Height, requester.requestedFrom(), peerID) - pool.sendError(err, peerID) - return err + sendErr = fmt.Errorf("requested block #%d from %v, not %s", block.Height, requester.requestedFrom(), peerID) + return sendErr } pool.numPending.Add(-1) diff --git a/blocksync/pool_test.go b/blocksync/pool_test.go index 8c3b77843a7..be0cf8749f6 100644 --- a/blocksync/pool_test.go +++ b/blocksync/pool_test.go @@ -640,6 +640,46 @@ func TestBlockPoolMaxPeerHeightRefreshesOnPopRequest(t *testing.T) { "peer B must contribute to maxPeerHeight once pool.height reaches its base") } +// TestAddBlockDoesNotDeadlockOnSendError is a regression test for AddBlock +// holding pool.mtx while calling sendError on an unbuffered channel. +func TestAddBlockDoesNotDeadlockOnSendError(t *testing.T) { + requestsCh := make(chan BlockRequest, 10) + errorsCh := make(chan peerError) // unbuffered: keeps AddBlock blocked in sendError + + pool := NewBlockPool(1, requestsCh, errorsCh, time.Second) + pool.SetLogger(log.TestingLogger()) + require.NoError(t, pool.Start()) + t.Cleanup(func() { _ = pool.Stop() }) + + pool.mtx.Lock() + req := newBPRequester(pool, 1) + req.peerID = "A" + pool.requesters[1] = req + pool.mtx.Unlock() + + block := &types.Block{Header: types.Header{Height: 1}, LastCommit: &types.Commit{}} + extCommit := &types.ExtendedCommit{Height: 1} + + // "B" did not request the block; setBlock fails → sendError while holding pool.mtx. + go func() { _ = pool.AddBlock("B", block, extCommit, 123) }() + time.Sleep(50 * time.Millisecond) + + heightDone := make(chan struct{}) + go func() { + pool.Height() + close(heightDone) + }() + + select { + case <-heightDone: + <-errorsCh + case <-time.After(500 * time.Millisecond): + <-errorsCh + <-heightDone + t.Fatal("deadlock: AddBlock held pool.mtx while blocked in sendError") + } +} + func TestBlockPoolHasPendingRequestFrom(t *testing.T) { requestsCh := make(chan BlockRequest, 10) errorsCh := make(chan peerError, 10) From 6b567cd510acb6d729869db8621ae93ac79247cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:10:48 -0400 Subject: [PATCH 06/32] build(deps): Bump golang.org/x/net from 0.55.0 to 0.56.0 (#5925) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.55.0 to 0.56.0.
Commits
  • 9e7fdbf internal/http3: fix wrong argument being given when validating header value
  • b686e5f internal/http3: add gzip support to transport
  • 8a34885 go.mod: update golang.org/x dependencies
  • 72eaf98 dns/dnsmessage: correctly validate SVCB record parameter order
  • 82e7868 dns/dnsmessage: avoid panic when parsing SVCB record with truncated data
  • b64f1fa internal/http3: add server support for "Trailer:" magic prefix
  • 2707ee2 internal/http3: implement HTTP/3 clientConn methods
  • 31358cc internal/http3: snapshot response headers at WriteHeader time
  • 8ecbaa9 html: don't adjust xml:base
  • 8ae811a html: properly handle end script tag in fragment mode
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/net&package-manager=go_modules&previous-version=0.55.0&new-version=0.56.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Co-authored-by: Alex Cozart Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- go.mod | 14 +++++++------- go.sum | 32 ++++++++++++++++---------------- kms/go.mod | 14 +++++++------- kms/go.sum | 28 ++++++++++++++-------------- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 86c37ba48b0..11e8cecb0b6 100644 --- a/go.mod +++ b/go.mod @@ -43,8 +43,8 @@ require ( github.com/stretchr/testify v1.11.1 github.com/supranational/blst v0.3.16 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - golang.org/x/crypto v0.52.0 - golang.org/x/net v0.55.0 + golang.org/x/crypto v0.53.0 + golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 gonum.org/v1/gonum v0.17.0 google.golang.org/grpc v1.81.1 @@ -186,12 +186,12 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.45.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 99b29d99755..95854f362af 100644 --- a/go.sum +++ b/go.sum @@ -530,8 +530,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= @@ -542,8 +542,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -565,8 +565,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -606,10 +606,10 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -617,8 +617,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -628,8 +628,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -642,8 +642,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/kms/go.mod b/kms/go.mod index 47555bac2f3..26c9e2ee0c4 100644 --- a/kms/go.mod +++ b/kms/go.mod @@ -131,16 +131,16 @@ require ( go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.52.0 // indirect + golang.org/x/crypto v0.53.0 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.45.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/kms/go.sum b/kms/go.sum index b52fce9d39f..ec868633856 100644 --- a/kms/go.sum +++ b/kms/go.sum @@ -381,8 +381,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= @@ -393,8 +393,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -415,8 +415,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -450,10 +450,10 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= +golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -469,8 +469,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -483,8 +483,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From d7e47b757f49adbab6a564521412333f89554a9d Mon Sep 17 00:00:00 2001 From: songgaoye <217724508+songgaoye@users.noreply.github.com> Date: Fri, 19 Jun 2026 01:00:09 +0800 Subject: [PATCH 07/32] fix(lp2p): fallback to conn remote addr when resolving inbound peer (#5879) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `handleStream` calls `resolvePeer` to look up or provision the incoming peer, but `resolvePeer` relies on the peerstore having the peer's address. libp2p's identify protocol populates the peerstore asynchronously — there is a window where A has opened a CometBFT stream to B but B's peerstore is still empty for A, causing `resolvePeer` to fail and the first message to be dropped. The fix passes the connection's remote multiaddr directly into `resolvePeer` as a fallback. When the peerstore has no addresses for the peer, `resolvePeer` constructs the `addrInfo` inline from the live connection instead of returning an error. This closes the race without any peerstore side effects. --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 ++ lp2p/switch.go | 14 +++++++--- lp2p/switch_test.go | 66 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2191a194380..71f7b9b5cfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ ([\#5868](https://github.com/cometbft/cometbft/pull/5868)) - `[node]` close partial listeners on startRPC failure ([\#5869](https://github.com/cometbft/cometbft/pull/5869)) +- `[lp2p]` fallback to conn remote addr when resolving inbound peer + ([\#5879](https://github.com/cometbft/cometbft/pull/5879)) - `[consensus]` release cs.mtx before sending to statsMsgQueue ([\#5813](https://github.com/cometbft/cometbft/pull/5813)) diff --git a/lp2p/switch.go b/lp2p/switch.go index 1de72edf8f6..cd899de0067 100644 --- a/lp2p/switch.go +++ b/lp2p/switch.go @@ -16,6 +16,7 @@ import ( "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/core/protocol" + ma "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" ) @@ -404,7 +405,7 @@ func (s *Switch) handleStream(stream network.Stream) { } // 3. Retrieve the peer from the peerSet (or provision if it's not). - peer, err := s.resolvePeer(peerID) + peer, err := s.resolvePeer(peerID, stream.Conn().RemoteMultiaddr()) if err != nil { s.Logger.Error("Failed to resolve peer", "protocol", protocolID, "peer_id", peerID.String(), "err", err) return @@ -465,7 +466,7 @@ func (s *Switch) handleStream(stream network.Stream) { s.reactors.Receive(reactor.name, messageType, envelope, priority) } -func (s *Switch) resolvePeer(id peer.ID) (p2p.Peer, error) { +func (s *Switch) resolvePeer(id peer.ID, connRemoteAddr ma.Multiaddr) (p2p.Peer, error) { key := peerIDToKey(id) // peer exists (99% of the time) @@ -473,9 +474,16 @@ func (s *Switch) resolvePeer(id peer.ID) (p2p.Peer, error) { return peer, nil } + // addrInfo most likely exists... addrInfo := s.host.Peerstore().PeerInfo(id) + + // ... but peer identification runs asynchronously to stream handling, + // thus addrInfo may not yet be populated. fallback to connRemoteAddr. if len(addrInfo.Addrs) == 0 { - return nil, errors.New("peer has no addresses in peerstore") + addrInfo = peer.AddrInfo{ + ID: id, + Addrs: []ma.Multiaddr{connRemoteAddr}, + } } var isPrivate, isPersistent, isUnconditional bool diff --git a/lp2p/switch_test.go b/lp2p/switch_test.go index 06efb8bc902..702b70b0ea9 100644 --- a/lp2p/switch_test.go +++ b/lp2p/switch_test.go @@ -19,7 +19,9 @@ import ( "github.com/cometbft/cometbft/p2p/conn" p2pmock "github.com/cometbft/cometbft/p2p/mock" "github.com/cometbft/cometbft/test/utils" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/p2p/protocol/identify" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" ) @@ -580,6 +582,70 @@ func TestSwitch(t *testing.T) { require.Equal(t, 1, switchB.Peers().Size(), "B should still be connected to A on filter pass") }) + + t.Run("ResolvePeerBeforeIdentify", func(t *testing.T) { + // ARRANGE + // Reproduce the race where B receives a CometBFT stream from A before + // B's identify goroutine has populated B's peerstore with A's address. + // Slowing A's identify responder keeps B's peerstore empty for A. + const ( + channelID = byte(0xF2) + identifyDelay = 500 * time.Millisecond + ) + + hosts := makeTestHosts(t, 2, withLogging()) + hostA, hostB := hosts[0], hosts[1] + + hostA.SetStreamHandler(identify.ID, func(s network.Stream) { + time.Sleep(identifyDelay) + _ = s.Reset() + }) + + channelDescriptor := &conn.ChannelDescriptor{ + ID: channelID, + Priority: 1, + RecvMessageCapacity: 1024, + MessageType: &types.RequestEcho{}, + } + + reactorA := newReactorMock([]*conn.ChannelDescriptor{channelDescriptor}, hostA.Logger()) + switchA, err := NewSwitch(nil, hostA, []SwitchReactor{{Name: "echo", Reactor: reactorA}}, p2p.NopMetrics(), hostA.Logger()) + require.NoError(t, err) + + reactorB := newReactorMock([]*conn.ChannelDescriptor{channelDescriptor}, hostB.Logger()) + switchB, err := NewSwitch(nil, hostB, []SwitchReactor{{Name: "echo", Reactor: reactorB}}, p2p.NopMetrics(), hostB.Logger()) + require.NoError(t, err) + + connectSwitches(t, []*Switch{switchA, switchB}) + + require.Eventually(t, func() bool { + return switchA.Peers().Size() == 1 + }, time.Second, 20*time.Millisecond, "A should see B as peer") + + // Pre-condition: B's peerstore must not have A's address yet. + // libp2p only populates the peerstore via identify, not from the raw + // connection dial. Blocking A's identify responder keeps B's peerstore + // for A empty. + require.Empty(t, hostB.Peerstore().Addrs(hostA.ID()), + "B should not know A's address yet (identify blocked)") + + // ACT + switchA.BroadcastAsync(p2p.Envelope{ + ChannelID: channelID, + Message: &types.RequestEcho{Message: "hello"}, + }) + + // ASSERT + require.Eventually(t, func() bool { + envs := reactorB.receivedEnvelopes() + if len(envs) != 1 { + return false + } + req, ok := envs[0].Message.(*types.RequestEcho) + return ok && req.Message == "hello" + }, 2*time.Second, 20*time.Millisecond, + "B must receive the message even though B's peerstore was empty for A at stream time") + }) } // filteringReactor is a mock reactor that optionally filters messages via the From 877c62f4a1bf5a672793ee7434aed01c67b05e34 Mon Sep 17 00:00:00 2001 From: Adam Boudj Date: Thu, 18 Jun 2026 12:23:15 -0500 Subject: [PATCH 08/32] blocksync: skip proto round-trip in respondToPeer (#5761) Fixes #2143. `respondToPeer` was calling `store.LoadBlock`, which internally unmarshal the raw DB bytes into a `cmtproto.Block` and then calls `BlockFromProto` to get a `*types.Block`. The function then immediately called `block.ToProto()` to get back a `cmtproto.Block` for the wire response. The intermediate Go struct was never used. Added `LoadBlockProto` to `BlockStore` that stops after the `proto.Unmarshal` step and returns the `*cmtproto.Block` directly. `LoadBlock` now delegates to it. `respondToPeer` type-asserts to `*store.BlockStore` and calls `LoadBlockProto` when possible, with a fallback for the interface case (used in tests with mocks). Tested: added `TestLoadBlockProto` to `store/store_test.go` that checks nil on missing height, correct hash round-trip, and consistency with `LoadBlock`. Full `./store/...` and `./blocksync/...` suites pass. --------- Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> Co-authored-by: Alex | Cosmos Labs Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- blocksync/reactor.go | 15 +++++---------- store/store.go | 17 ++++++++++++++--- store/store_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/blocksync/reactor.go b/blocksync/reactor.go index c34a47a24df..9cac8978a22 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -76,7 +76,7 @@ type Reactor struct { adaptiveSyncEnabled bool blockExec *sm.BlockExecutor - store sm.BlockStore + store *store.BlockStore pool *BlockPool localAddr crypto.Address poolRoutineWg sync.WaitGroup @@ -288,8 +288,9 @@ func (r *Reactor) RemovePeer(peer p2p.Peer, _ any) { // respondToPeer loads a block and sends it to the requesting peer, // if we have it. Otherwise, we'll respond saying we don't have it. func (r *Reactor) respondToPeer(msg *bcproto.BlockRequest, src p2p.Peer) { - block := r.store.LoadBlock(msg.Height) - if block == nil { + // Load the block directly in proto form to skip the BlockFromProto+ToProto round-trip. + bl := r.store.LoadBlockProto(msg.Height) + if bl == nil { r.Logger.Info("Peer asking for a block we don't have", "src", src, "height", msg.Height) src.TrySend(p2p.Envelope{ ChannelID: BlocksyncChannel, @@ -309,17 +310,11 @@ func (r *Reactor) respondToPeer(msg *bcproto.BlockRequest, src p2p.Peer) { if state.ConsensusParams.ABCI.VoteExtensionsEnabled(msg.Height) { extCommit = r.store.LoadBlockExtendedCommit(msg.Height) if extCommit == nil { - r.Logger.Error("Found block in store with no extended commit", "block", block) + r.Logger.Error("Found block in store with no extended commit", "height", msg.Height) return } } - bl, err := block.ToProto() - if err != nil { - r.Logger.Error("Unable to convert the block to protobuf", "err", err) - return - } - src.TrySend(p2p.Envelope{ ChannelID: BlocksyncChannel, Message: &bcproto.BlockResponse{ diff --git a/store/store.go b/store/store.go index 4b9ab564dab..1e081b0a018 100644 --- a/store/store.go +++ b/store/store.go @@ -133,9 +133,9 @@ func (bs *BlockStore) LoadBaseMeta() *types.BlockMeta { return bs.LoadBlockMeta(bs.base) } -// LoadBlock returns the block with the given height. -// If no block is found for that height, it returns nil. -func (bs *BlockStore) LoadBlock(height int64) *types.Block { +// LoadBlockProto returns the block at the given height in its proto representation, +// without deserializing into a [types.Block]. Returns nil if no block is found. +func (bs *BlockStore) LoadBlockProto(height int64) *cmtproto.Block { blockMeta := bs.LoadBlockMeta(height) if blockMeta == nil { return nil @@ -159,6 +159,17 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block { panic(fmt.Sprintf("Error reading block: %v", err)) } + return pbb +} + +// LoadBlock returns the block with the given height. +// If no block is found for that height, it returns nil. +func (bs *BlockStore) LoadBlock(height int64) *types.Block { + pbb := bs.LoadBlockProto(height) + if pbb == nil { + return nil + } + block, err := types.BlockFromProto(pbb) if err != nil { panic(cmterrors.ErrMsgFromProto{MessageName: "Block", Err: err}) diff --git a/store/store_test.go b/store/store_test.go index 155655d36f4..0008b25d91f 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -382,6 +382,33 @@ func stripExtensions(ec *types.ExtendedCommit) bool { return stripped } +func TestLoadBlockProto(t *testing.T) { + state, bs, cleanup := makeStateAndBlockStore() + defer cleanup() + + // missing block returns nil + require.Nil(t, bs.LoadBlockProto(1)) + + block, err := state.MakeBlock(1, nil, new(types.Commit), nil, state.Validators.GetProposer().Address) + require.NoError(t, err) + partSet, err := block.MakePartSet(types.BlockPartSizeBytes) + require.NoError(t, err) + seenCommit := makeTestExtCommit(1, cmttime.Now()).ToCommit() + bs.SaveBlock(block, partSet, seenCommit) + + pbb := bs.LoadBlockProto(1) + require.NotNil(t, pbb) + + // proto representation must round-trip back to the same block + loaded, err := types.BlockFromProto(pbb) + require.NoError(t, err) + require.Equal(t, block.Hash(), loaded.Hash()) + + // LoadBlock and LoadBlockProto must agree + direct := bs.LoadBlock(1) + require.Equal(t, direct.Hash(), loaded.Hash()) +} + // TestSaveBlockWithExtendedCommitPanicOnAbsentExtension tests that saving a // block with an extended commit panics when the extension data is absent. func TestSaveBlockWithExtendedCommitPanicOnAbsentExtension(t *testing.T) { From 75c78305bb72a76bce197641400e6a5bff920da0 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Mon, 22 Jun 2026 11:33:12 -0500 Subject: [PATCH 09/32] chore: Migrate kms (#5932) --- Remove kms in favor of https://github.com/cosmos/kms --- .github/workflows/kms.yml | 110 ---- Makefile | 59 -- kms/README.md | 374 ------------ kms/cmd/cometkms/main.go | 167 ------ kms/cmd/cometkms/main_test.go | 27 - kms/go.mod | 149 ----- kms/go.sum | 536 ------------------ kms/internal/app/build.go | 135 ----- kms/internal/app/build_pkcs11_test.go | 38 -- kms/internal/app/build_test.go | 53 -- kms/internal/app/integration_test.go | 350 ------------ kms/internal/backend/backend.go | 20 - kms/internal/backend/pkcs11/algo.go | 59 -- kms/internal/backend/pkcs11/algo_test.go | 40 -- kms/internal/backend/pkcs11/module.go | 68 --- kms/internal/backend/pkcs11/pin_test.go | 53 -- kms/internal/backend/pkcs11/pkcs11.go | 261 --------- kms/internal/backend/pkcs11/pkcs11_test.go | 135 ----- .../backend/pkcs11/pkcs11test/pkcs11test.go | 193 ------- kms/internal/backend/softsign/softsign.go | 78 --- .../backend/softsign/softsign_test.go | 57 -- kms/internal/config/config.go | 98 ---- kms/internal/config/config_pkcs11_test.go | 175 ------ kms/internal/config/config_test.go | 181 ------ kms/internal/config/validate.go | 185 ------ kms/internal/identity/identity.go | 25 - kms/internal/identity/identity_test.go | 22 - kms/internal/manager/dialer.go | 45 -- kms/internal/manager/dialer_test.go | 53 -- kms/internal/manager/manager.go | 205 ------- kms/internal/signer/chain_signer.go | 94 --- kms/internal/signer/chain_signer_test.go | 130 ----- kms/internal/signer/privkey_adapter.go | 44 -- kms/internal/signer/privkey_adapter_test.go | 34 -- kms/internal/transport/noise.go | 44 -- kms/internal/transport/noise_test.go | 69 --- kms/internal/version/version.go | 9 - kms/internal/version/version_test.go | 9 - 38 files changed, 4384 deletions(-) delete mode 100644 .github/workflows/kms.yml delete mode 100644 kms/README.md delete mode 100644 kms/cmd/cometkms/main.go delete mode 100644 kms/cmd/cometkms/main_test.go delete mode 100644 kms/go.mod delete mode 100644 kms/go.sum delete mode 100644 kms/internal/app/build.go delete mode 100644 kms/internal/app/build_pkcs11_test.go delete mode 100644 kms/internal/app/build_test.go delete mode 100644 kms/internal/app/integration_test.go delete mode 100644 kms/internal/backend/backend.go delete mode 100644 kms/internal/backend/pkcs11/algo.go delete mode 100644 kms/internal/backend/pkcs11/algo_test.go delete mode 100644 kms/internal/backend/pkcs11/module.go delete mode 100644 kms/internal/backend/pkcs11/pin_test.go delete mode 100644 kms/internal/backend/pkcs11/pkcs11.go delete mode 100644 kms/internal/backend/pkcs11/pkcs11_test.go delete mode 100644 kms/internal/backend/pkcs11/pkcs11test/pkcs11test.go delete mode 100644 kms/internal/backend/softsign/softsign.go delete mode 100644 kms/internal/backend/softsign/softsign_test.go delete mode 100644 kms/internal/config/config.go delete mode 100644 kms/internal/config/config_pkcs11_test.go delete mode 100644 kms/internal/config/config_test.go delete mode 100644 kms/internal/config/validate.go delete mode 100644 kms/internal/identity/identity.go delete mode 100644 kms/internal/identity/identity_test.go delete mode 100644 kms/internal/manager/dialer.go delete mode 100644 kms/internal/manager/dialer_test.go delete mode 100644 kms/internal/manager/manager.go delete mode 100644 kms/internal/signer/chain_signer.go delete mode 100644 kms/internal/signer/chain_signer_test.go delete mode 100644 kms/internal/signer/privkey_adapter.go delete mode 100644 kms/internal/signer/privkey_adapter_test.go delete mode 100644 kms/internal/transport/noise.go delete mode 100644 kms/internal/transport/noise_test.go delete mode 100644 kms/internal/version/version.go delete mode 100644 kms/internal/version/version_test.go diff --git a/.github/workflows/kms.yml b/.github/workflows/kms.yml deleted file mode 100644 index 23f41a2c1cf..00000000000 --- a/.github/workflows/kms.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: KMS -# Build, test, and lint the cometkms nested module (kms/). -# Runs on every pull request and push to main. Each job is gated by a paths -# filter so it is skipped when no Go/Makefile/config files have changed. The -# filter includes all *.go files (not just kms/) because the kms module depends -# on the parent cometbft module via `replace => ../`, so parent changes can -# affect the kms build. - -on: - pull_request: - merge_group: - push: - branches: - - main - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -jobs: - build: - name: Build - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v6 - - - id: filter - uses: dorny/paths-filter@v4 - with: - filters: | - code: - - '**/*.go' - - 'Makefile' - - 'go.*' - - 'kms/go.*' - - '.github/workflows/kms.yml' - - - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - - - uses: actions/setup-go@v6 - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - with: - go-version: ${{ env.GO_VERSION }} - - - name: Build cometkms - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - run: make kms-build - - test: - name: Test - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v6 - - - id: filter - uses: dorny/paths-filter@v4 - with: - filters: | - code: - - '**/*.go' - - 'Makefile' - - 'go.*' - - 'kms/go.*' - - '.github/workflows/kms.yml' - - - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - - - uses: actions/setup-go@v6 - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - with: - go-version: ${{ env.GO_VERSION }} - - - name: Test cometkms (race detector) - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - run: make kms-test-race - - lint: - name: Lint - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v6 - - - id: filter - uses: dorny/paths-filter@v4 - with: - filters: | - code: - - '**/*.go' - - 'Makefile' - - 'go.*' - - 'kms/go.*' - - '.golangci.yml' - - '.github/workflows/kms.yml' - - - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - - - uses: actions/setup-go@v6 - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - with: - go-version: ${{ env.GO_VERSION }} - - - name: Lint cometkms - if: steps.filter.outputs.code == 'true' && hashFiles('kms/go.mod') != '' - run: make kms-lint diff --git a/Makefile b/Makefile index e1ac384cac3..7dc53a51008 100644 --- a/Makefile +++ b/Makefile @@ -88,65 +88,6 @@ install: CGO_ENABLED=$(CGO_ENABLED) go install $(BUILD_FLAGS) -tags '$(BUILD_TAGS)' ./cmd/cometbft .PHONY: install -### cometkms (external remote signer; nested module in kms/) ### - -KMS_OUTPUT ?= $(BUILDDIR)/cometkms -KMS_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.1.0-dev") -KMS_LDFLAGS := -X github.com/cometbft/cometbft/kms/internal/version.Version=$(KMS_VERSION) -KMS_BUILD_FLAGS := -mod=readonly -ldflags "$(KMS_LDFLAGS)" -KMS_COVERPROFILE ?= $(BUILDDIR)/kms-coverage.out -KMS_COVERHTML ?= $(BUILDDIR)/kms-coverage.html - -#? kms-build: Build cometkms into $(BUILDDIR) -kms-build: - CGO_ENABLED=1 go build -C kms $(KMS_BUILD_FLAGS) -o $(KMS_OUTPUT) ./cmd/cometkms -.PHONY: kms-build - -#? kms-install: Install cometkms to GOBIN -kms-install: - CGO_ENABLED=1 go install -C kms $(KMS_BUILD_FLAGS) ./cmd/cometkms -.PHONY: kms-install - -#? kms-test: Run the cometkms test suite -kms-test: - CGO_ENABLED=1 go test -C kms ./... -count=1 -.PHONY: kms-test - -#? kms-test-race: Run the cometkms test suite with the race detector -kms-test-race: - CGO_ENABLED=1 go test -C kms ./... -race -count=1 -.PHONY: kms-test-race - -#? kms-cover: Run kms tests with coverage; write profile + HTML to $(BUILDDIR) and print the total -kms-cover: - @mkdir -p $(BUILDDIR) - go test -C kms ./... -covermode=atomic -coverpkg=./... -coverprofile=$(KMS_COVERPROFILE) -count=1 - go -C kms tool cover -func=$(KMS_COVERPROFILE) | tail -n 1 - go -C kms tool cover -html=$(KMS_COVERPROFILE) -o $(KMS_COVERHTML) - @echo "coverage profile: $(KMS_COVERPROFILE)" - @echo "coverage html: $(KMS_COVERHTML)" -.PHONY: kms-cover - -#? kms-vet: Vet the cometkms module -kms-vet: - CGO_ENABLED=1 go vet -C kms ./... -.PHONY: kms-vet - -#? kms-lint: Lint the cometkms module (pinned golangci-lint, same version as the root lint target) -kms-lint: - cd kms && CGO_ENABLED=1 go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.3 run -.PHONY: kms-lint - -#? kms-tidy: Tidy the cometkms module dependencies -kms-tidy: - go mod tidy -C kms -.PHONY: kms-tidy - -#? kms-clean: Remove cometkms build and coverage artifacts -kms-clean: - rm -f $(KMS_OUTPUT) $(KMS_COVERPROFILE) $(KMS_COVERHTML) -.PHONY: kms-clean - ############################################################################### ### Metrics ### ############################################################################### diff --git a/kms/README.md b/kms/README.md deleted file mode 100644 index 42fd0259efc..00000000000 --- a/kms/README.md +++ /dev/null @@ -1,374 +0,0 @@ -# cometkms - -`cometkms` is an external remote signer for CometBFT validators. It lives as a -nested Go module (`github.com/cometbft/cometbft/kms`) inside the CometBFT -repository and is conceptually similar to [tmkms](https://github.com/iqlusioninc/tmkms), -but implemented entirely in Go and building directly on top of the CometBFT -libraries. It dials *out* to one or more validator nodes, authenticates each -connection with either CometBFT's SecretConnection protocol or the libp2p Noise -transport (selected per-validator via the address scheme), and serves Ed25519 -consensus signing requests (votes, proposals, and vote extensions) with -mandatory per-chain double-sign protection. - ---- - -## Status - -| Scope | Status | -|---|---| -| Ed25519 consensus signing (votes, proposals, vote extensions) | **Supported** | -| `softsign` key backend (file-based, in-memory Ed25519) | **Supported** | -| `pkcs11` key backend (HSM / token, Ed25519) | **Supported** | -| `cometp2p` transport (TCP + SecretConnection) | **Supported** | -| Multi-chain, multi-validator support | **Supported** | -| Double-sign protection (reuses CometBFT FilePV state machine) | **Supported** | -| Dial-out + automatic exponential-backoff reconnect | **Supported** | -| AWS KMS backend | Planned | -| libp2p transport (Noise) | **Supported** | -| Account / raw-bytes / ECDSA signing | Planned | -| ML-DSA / eth_secp256k1 key types | Planned | - ---- - -## How it works - -- **Dial-out model.** `cometkms` dials out to the validator's privval listener - (`priv_validator_laddr` in `config.toml`) rather than listening itself. This - removes the need to expose any port on the KMS host. -- **SecretConnection authentication.** Each connection is authenticated and - encrypted using CometBFT's SecretConnection protocol. `cometkms` uses a - dedicated Ed25519 *identity key* (distinct from the consensus signing key) to - authenticate itself to the validator. -- **Request serving.** Once connected, the KMS handles `PubKey`, `SignVote`, - `SignProposal`, and `Ping` requests using `privval.DefaultValidationRequestHandler`. -- **Double-sign protection.** Signing is delegated to CometBFT's `FilePV` state - machine, which persists the last-signed height/round/step to a per-chain state - file and refuses to sign any regression. This survives process restarts. -- **Automatic reconnect.** When a connection drops (validator restart, network - hiccup), `cometkms` reconnects with capped exponential backoff (200 ms initial, - 10 s ceiling) without any manual intervention. - ---- - -## Build - -Requires Go 1.25 or newer. Build from the repository root via the top-level -Makefile (the binary is written to `build/cometkms`): - -```sh -make kms-build # build/cometkms -make kms-install # install to GOBIN -``` - ---- - -## Quick start - -### 1. Initialise the home directory - -```sh -cometkms init --home ~/.cometkms -``` - -This creates: - -- `~/.cometkms/cometkms.toml` — a stub configuration file. -- `~/.cometkms/identity.json` — a fresh Ed25519 identity key for the - SecretConnection. - -### 2. Edit the config - -Open `~/.cometkms/cometkms.toml` and fill in the real values (see the -[example config](#example-config) below). - -### 3. Place the consensus key file - -Copy or symlink the `priv_validator_key.json` for each chain to the path you -set as `key_file` in `[[providers.softsign]]`. - -### 4. Configure the validator - -In the validator's `config.toml` enable the remote signer listener: - -```toml -priv_validator_laddr = "tcp://0.0.0.0:26659" -``` - -The address must be reachable from the host running `cometkms`, and it must -match the `addr` you set in `[[validator]]`. - -### 5. Start the KMS - -```sh -cometkms start --home ~/.cometkms -``` - -`cometkms` will dial the validator and begin serving signing requests. It logs -each connection and any signing errors to stdout. - ---- - -## Configuration reference - -### `[[chain]]` - -Declares one blockchain. You need exactly one `[[chain]]` block per chain you -want to sign for. - -| Field | Type | Required | Description | -|---|---|---|---| -| `id` | string | yes | The chain-id string (e.g. `cosmoshub-4`). | -| `state_file` | string | no | Path to the double-sign state file. Defaults to `/state/.json`. Relative paths are resolved against `--home`. | - -### `[[validator]]` - -Declares one outbound connection to a validator node. A single chain can have -multiple `[[validator]]` blocks (e.g. primary + backup nodes). - -| Field | Type | Required | Description | -|---|---|---|---| -| `chain_id` | string | yes | Must match a declared `[[chain]].id`. | -| `addr` | string | yes | Address of the validator's privval listener. Use `tcp://host:port` for the standard SecretConnection transport, or `noise://@host:port` for the libp2p Noise transport (see [libp2p Noise transport](#libp2p-noise-transport)). | -| `identity_key` | string | yes | Path to the Ed25519 identity key file used to authenticate the SecretConnection. Relative paths are resolved against `--home`. Use the file generated by `cometkms init`. | -| `reconnect` | bool | no | Whether to reconnect automatically after a dropped connection. Defaults to `true`. | - -### `[[providers.softsign]]` - -Binds a file-based Ed25519 private key to one or more chains. - -| Field | Type | Required | Description | -|---|---|---|---| -| `chain_ids` | list of strings | yes | Chain IDs this key is used to sign for. Each chain may only have one softsign provider. | -| `key_file` | string | yes | Path to the key file. Accepts either a CometBFT `priv_validator_key.json` (typed JSON with a `"priv_key"` field) or a file containing the raw base64-encoded 64-byte Ed25519 private key. | - -### `[[providers.pkcs11]]` - -Binds an Ed25519 key stored on a PKCS#11 token or HSM to one or more chains. The -private key never leaves the token: signing is performed on-device via `CKM_EDDSA`. - -| Field | Type | Required | Description | -|---|---|---|---| -| `chain_ids` | list of strings | yes | Chain IDs this key is used to sign for. Each chain may only have one backend (softsign *or* pkcs11). | -| `module` | string | yes | Path to the PKCS#11 module shared library (e.g. `/usr/lib/softhsm/libsofthsm2.so`). Relative paths are resolved against `--home`. | -| `token_label` | string | one of token_label/slot | `CKA_LABEL` of the token to use. | -| `slot` | integer | one of token_label/slot | Slot number of the token to use. Mutually exclusive with `token_label`. | -| `key_label` | string | at least one of key_label/key_id | `CKA_LABEL` of the key object. | -| `key_id` | string (hex) | at least one of key_label/key_id | Hex-encoded `CKA_ID` of the key object. | -| `pin` | string | exactly one PIN source | User PIN, inline. | -| `pin_env` | string | exactly one PIN source | Name of an environment variable holding the user PIN. Preferred over inline. | -| `pin_file` | string | exactly one PIN source | Path to a file containing the user PIN (trailing whitespace trimmed). Relative paths resolved against `--home`. | -| `algorithm` | string | no | Key algorithm. Defaults to `ed25519` (the only supported value today). | - -Provision the key with your HSM tooling before starting `cometkms`; the KMS only -*uses* an existing key, it does not generate or import keys. The key must be an -Ed25519 (`CKK_EC_EDWARDS`) signing key. Example using `pkcs11-tool` with SoftHSM2: - -```sh -softhsm2-util --init-token --free --label comet --pin 1234 --so-pin 4321 -pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --login --pin 1234 \ - --keypairgen --key-type EC:edwards25519 --label validator --id 01 -``` - -Example provider block (PIN supplied via environment, keeping it out of the -config file): - -```toml -[[providers.pkcs11]] -chain_ids = ["cosmoshub-4"] -module = "/usr/lib/softhsm/libsofthsm2.so" -token_label = "comet" -key_label = "validator" -key_id = "01" -pin_env = "COMETKMS_PIN" -# algorithm defaults to "ed25519" -``` - ---- - -## Example config - -```toml -# ~/.cometkms/cometkms.toml - -[[chain]] -id = "cosmoshub-4" -# state_file defaults to /state/cosmoshub-4.json when omitted - -[[validator]] -chain_id = "cosmoshub-4" -addr = "tcp://10.0.0.1:26659" -identity_key = "identity.json" # relative to --home - -[[providers.softsign]] -chain_ids = ["cosmoshub-4"] -key_file = "/secrets/priv_validator_key.json" -``` - -Multi-chain example: - -```toml -[[chain]] -id = "cosmoshub-4" - -[[chain]] -id = "osmosis-1" - -[[validator]] -chain_id = "cosmoshub-4" -addr = "tcp://10.0.0.1:26659" -identity_key = "identity.json" - -[[validator]] -chain_id = "osmosis-1" -addr = "tcp://10.0.0.2:26659" -identity_key = "identity.json" - -[[providers.softsign]] -chain_ids = ["cosmoshub-4"] -key_file = "/secrets/cosmoshub_priv_validator_key.json" - -[[providers.softsign]] -chain_ids = ["osmosis-1"] -key_file = "/secrets/osmosis_priv_validator_key.json" -``` - ---- - -## libp2p Noise transport - -The libp2p Noise transport is an alternative to the default SecretConnection -(`tcp://`) channel. Both sides use the same TCP port and listener — the address -scheme (`noise://` vs `tcp://`) is what selects which handshake is performed. -No libp2p switch, host, or gossip network is involved; it is a direct TCP -connection secured by the [Noise_XX handshake](https://noiseprotocol.org/). - -The key difference from SecretConnection is **pinned peer IDs on both sides**. -SecretConnection uses an ephemeral, unpinned key on the validator's listener, -which means the KMS cannot verify it is talking to the right validator. With the -Noise transport, each side asserts a stable libp2p identity derived from its -existing keys (the validator's node key, the KMS's `identity.json`), and each -side refuses any connection from an unexpected peer. - -### Obtaining the two peer IDs - -**KMS peer ID** (give this to the validator operator so they can pin it in -`priv_validator_laddr`): - -```sh -cometkms peer-id --home ~/.cometkms -``` - -This reads `identity.json` from `` and prints the corresponding libp2p -peer ID. - -**Validator peer ID** (give this to the KMS operator so they can pin it in -`[[validator]].addr`): - -```sh -cometbft show-node-id --libp2p --home ~/.cometbft -``` - -This prints the libp2p peer ID derived from the validator's node key. - -### KMS configuration - -Set `addr` to a `noise://` URI that embeds the validator's peer ID: - -```toml -[[validator]] -chain_id = "cosmoshub-4" -addr = "noise://12D3KooW...validatorPeerID...@10.0.0.1:26659" -identity_key = "identity.json" # reused as the KMS's libp2p identity -``` - -The `identity_key` field serves double duty: it authenticates the -SecretConnection channel when `tcp://` is used, and it provides the KMS's -libp2p identity (peer ID) when `noise://` is used. No additional key file is -needed. - -### Validator configuration - -Set `priv_validator_laddr` in the validator's `config.toml` to a `noise://` -URI that embeds the KMS peer ID: - -```toml -priv_validator_laddr = "noise://12D3KooW...kmsPeerID...@0.0.0.0:26659" -``` - -The validator uses its **node key** (`node_key.json`) as its Noise identity. -Any incoming connection whose authenticated peer ID does not match the pinned -KMS peer ID is rejected before any signing request is served. - -### Mutual pinning requirement - -Both sides **must** configure the other's peer ID: - -- The KMS encodes the validator's peer ID in the `noise://` address it dials — - the handshake fails immediately if the remote key does not match. -- The validator encodes the KMS's peer ID in `priv_validator_laddr` — any - connection from a different peer is dropped. - -There is no way to disable peer-ID pinning when using `noise://`; it is -enforced unconditionally. - -### Key types - -Ed25519 and secp256k1 keys are supported. Consensus keys and node keys in -CometBFT are Ed25519, so no extra setup is needed. - - -## Security notes - -- **softsign is NOT for production custody.** The private key is loaded from - disk and held in process memory in plaintext for the lifetime of the process. - Use the `pkcs11` backend (or a future AWS KMS backend) for production - environments where the key must never leave secure hardware. -- **The identity key is not the consensus signing key.** `identity.json` - authenticates the SecretConnection channel; it does not sign consensus - messages and does not need to be protected to the same degree as the - `priv_validator_key.json`. -- **Double-sign protection is per state file.** The state file records the - highest height/round/step that has been signed. `cometkms` refuses to sign - any message that would regress this high-water mark. Never run two `cometkms` - instances against the same validator with different (or missing) state files — - doing so removes the double-sign protection. -- **Validator listener exposure.** The validator's `priv_validator_laddr` binds - a TCP port. Ensure it is not reachable from untrusted networks (use a firewall - or a private VLAN between the validator and the KMS host). - ---- - -## Testing - -From the repository root: - -```sh -make kms-test # go test ./... -count=1 -make kms-test-race # with the race detector -``` - ---- - -## Repository layout - -``` -kms/ -├── cmd/ -│ └── cometkms/ # Binary entrypoint; CLI subcommands (version, init, start, peer-id) -│ ├── main.go -│ └── main_test.go -└── internal/ - ├── version/ # Version string; overridable at link time via -ldflags - ├── config/ # TOML config types (config.go) and validation (validate.go) - ├── identity/ # Identity key load/generate (wraps CometBFT p2p.NodeKey) - ├── backend/ # backend.Signer interface - │ ├── softsign/ # File-based Ed25519 backend - │ └── pkcs11/ # PKCS#11 / HSM Ed25519 backend (+ pkcs11test helpers) - ├── signer/ # ChainSigner: double-sign protection + PrivValidator impl - │ ├── chain_signer.go - │ └── privkey_adapter.go - ├── manager/ # Manager: supervised dial-out connections with backoff - │ ├── manager.go - │ └── dialer.go - └── app/ # Wiring: Build() assembles Manager from a validated Config -``` diff --git a/kms/cmd/cometkms/main.go b/kms/cmd/cometkms/main.go deleted file mode 100644 index e690b10fa67..00000000000 --- a/kms/cmd/cometkms/main.go +++ /dev/null @@ -1,167 +0,0 @@ -// Command cometkms is an external remote signer for CometBFT validators. -package main - -import ( - "fmt" - "os" - "os/signal" - "path/filepath" - "syscall" - - "github.com/cometbft/cometbft/libs/log" - "github.com/cometbft/cometbft/lp2p" - "github.com/spf13/cobra" - - "github.com/cometbft/cometbft/kms/internal/app" - "github.com/cometbft/cometbft/kms/internal/config" - "github.com/cometbft/cometbft/kms/internal/identity" - "github.com/cometbft/cometbft/kms/internal/version" -) - -const defaultConfigTemplate = `# cometkms configuration - -[[chain]] -id = "my-chain-1" -# state_file defaults to /state/.json if omitted - -[[validator]] -chain_id = "my-chain-1" -addr = "tcp://127.0.0.1:26659" -identity_key = "identity.json" - -[[providers.softsign]] -chain_ids = ["my-chain-1"] -key_file = "priv_validator_key.json" - -# To sign from a PKCS#11 token / HSM instead of softsign, replace the -# [[providers.softsign]] block above with a pkcs11 provider: -# -# [[providers.pkcs11]] -# chain_ids = ["my-chain-1"] -# module = "/usr/lib/softhsm/libsofthsm2.so" -# token_label = "comet" -# key_label = "validator" -# pin_env = "COMETKMS_PIN" -` - -// home is the home directory of cometkms -var home string - -func main() { - if err := rootCmd().Execute(); err != nil { - fmt.Fprintln(os.Stderr, "error:", err) - os.Exit(1) - } -} - -func rootCmd() *cobra.Command { - root := &cobra.Command{Use: "cometkms", Short: "External remote signer for CometBFT validators"} - root.PersistentFlags().StringVar(&home, "home", ".", "the home directory of cometkms") - root.AddCommand(versionCmd(), initCmd(), startCmd(), peerIDCmd()) - return root -} - -func peerIDFromIdentity(path string) (string, error) { - key, err := identity.LoadOrGen(path) - if err != nil { - return "", err - } - id, err := lp2p.IDFromPrivateKey(key) - if err != nil { - return "", err - } - return id.String(), nil -} - -func peerIDCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "peer-id", - Short: "Print the libp2p peer ID of the KMS identity key (for the validator's noise allowlist)", - RunE: func(_ *cobra.Command, _ []string) error { - id, err := peerIDFromIdentity(filepath.Join(home, "identity.json")) - if err != nil { - return err - } - fmt.Println(id) - return nil - }, - } - return cmd -} - -func versionCmd() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Print the cometkms version", - RunE: func(_ *cobra.Command, _ []string) error { - fmt.Println(version.String()) - return nil - }, - } -} - -func initCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "init", - Short: "Scaffold a config file and generate an identity key", - RunE: func(_ *cobra.Command, _ []string) error { return runInit(home) }, - } - return cmd -} - -func runInit(home string) error { - if err := os.MkdirAll(home, 0o700); err != nil { - return err - } - path := cfgPath(home) - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.WriteFile(path, []byte(defaultConfigTemplate), 0o600); err != nil { - return err - } - } - if _, err := identity.LoadOrGen(filepath.Join(home, "identity.json")); err != nil { - return err - } - fmt.Printf("initialized cometkms in %s\n", home) - return nil -} - -func startCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "start", - Short: "Connect to validators and serve signing requests", - RunE: func(_ *cobra.Command, _ []string) error { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - - cfg, err := config.Load(cfgPath(home)) - if err != nil { - return err - } - if err := cfg.Validate(home); err != nil { - return err - } - - mgr, cleanup, err := app.Build(cfg, logger) - if err != nil { - return err - } - defer cleanup() - if err := mgr.Start(); err != nil { - return err - } - defer mgr.Stop() - - logger.Info("cometkms started; press Ctrl-C to stop") - sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) - <-sig - logger.Info("cometkms shutting down") - return nil - }, - } - return cmd -} - -func cfgPath(home string) string { - return filepath.Join(home, "cometkms.toml") -} diff --git a/kms/cmd/cometkms/main_test.go b/kms/cmd/cometkms/main_test.go deleted file mode 100644 index 0e418b702ef..00000000000 --- a/kms/cmd/cometkms/main_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestInitCreatesConfigAndIdentity(t *testing.T) { - home := t.TempDir() - require.NoError(t, runInit(home)) - - _, err := os.Stat(filepath.Join(home, "cometkms.toml")) - require.NoError(t, err) - _, err = os.Stat(filepath.Join(home, "identity.json")) - require.NoError(t, err) -} - -func TestPeerIDFromIdentity(t *testing.T) { - home := t.TempDir() - require.NoError(t, runInit(home)) - id, err := peerIDFromIdentity(filepath.Join(home, "identity.json")) - require.NoError(t, err) - require.NotEmpty(t, id) -} diff --git a/kms/go.mod b/kms/go.mod deleted file mode 100644 index 26c9e2ee0c4..00000000000 --- a/kms/go.mod +++ /dev/null @@ -1,149 +0,0 @@ -module github.com/cometbft/cometbft/kms - -go 1.25.0 - -replace github.com/cometbft/cometbft => ../ - -require ( - github.com/BurntSushi/toml v1.6.0 - github.com/cometbft/cometbft v0.0.0 - github.com/libp2p/go-libp2p v0.47.0 - github.com/miekg/pkcs11 v1.1.2 - github.com/spf13/cobra v1.10.2 - github.com/stretchr/testify v1.11.1 -) - -require ( - github.com/DataDog/zstd v1.5.7 // indirect - github.com/benbjohnson/clock v1.3.5 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.3 // indirect - github.com/cockroachdb/errors v1.11.3 // indirect - github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v1.1.1 // indirect - github.com/cockroachdb/redact v1.1.5 // indirect - github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft-db v0.14.1 // indirect - github.com/cosmos/gogoproto v1.7.2 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect - github.com/dgraph-io/badger/v4 v4.2.0 // indirect - github.com/dgraph-io/ristretto v0.1.1 // indirect - github.com/dunglas/httpsfv v1.1.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/flynn/noise v1.1.0 // indirect - github.com/getsentry/sentry-go v0.27.0 // indirect - github.com/go-kit/kit v0.13.0 // indirect - github.com/go-kit/log v0.2.1 // indirect - github.com/go-logfmt/logfmt v0.6.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.2.5 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/flatbuffers v1.12.1 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/huin/goupnp v1.3.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/go-cid v0.5.0 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect - github.com/jmhodges/levigo v1.0.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/koron/go-ssdp v0.0.6 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-flow-metrics v0.2.0 // indirect - github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-netroute v0.3.0 // indirect - github.com/libp2p/go-reuseport v0.4.0 // indirect - github.com/libp2p/go-yamux/v5 v5.0.1 // indirect - github.com/linxGnu/grocksdb v1.8.14 // indirect - github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect - github.com/miekg/dns v1.1.66 // indirect - github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect - github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect - github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mr-tron/base58 v1.3.0 // indirect - github.com/multiformats/go-base32 v0.1.0 // indirect - github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr v0.16.1 // indirect - github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect - github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect - github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.9.1 // indirect - github.com/multiformats/go-multihash v0.2.3 // indirect - github.com/multiformats/go-multistream v0.6.1 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect - github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect - github.com/pion/datachannel v1.5.10 // indirect - github.com/pion/dtls/v2 v2.2.12 // indirect - github.com/pion/dtls/v3 v3.0.11 // indirect - github.com/pion/ice/v4 v4.0.10 // indirect - github.com/pion/interceptor v0.1.40 // indirect - github.com/pion/logging v0.2.4 // indirect - github.com/pion/mdns/v2 v2.0.7 // indirect - github.com/pion/randutil v0.1.0 // indirect - github.com/pion/rtcp v1.2.15 // indirect - github.com/pion/rtp v1.8.19 // indirect - github.com/pion/sctp v1.8.39 // indirect - github.com/pion/sdp/v3 v3.0.13 // indirect - github.com/pion/srtp/v3 v3.0.6 // indirect - github.com/pion/stun v0.6.1 // indirect - github.com/pion/stun/v3 v3.0.0 // indirect - github.com/pion/transport/v2 v2.2.10 // indirect - github.com/pion/transport/v3 v3.0.7 // indirect - github.com/pion/transport/v4 v4.0.1 // indirect - github.com/pion/turn/v4 v4.0.2 // indirect - github.com/pion/webrtc/v4 v4.1.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.23.2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.68.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.1 // indirect - github.com/quic-go/webtransport-go v0.10.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/sasha-s/go-deadlock v0.3.9 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect - github.com/supranational/blst v0.3.16 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect - github.com/wlynxg/anet v0.0.5 // indirect - go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect - go.opencensus.io v0.24.0 // indirect - go.uber.org/dig v1.19.0 // indirect - go.uber.org/fx v1.24.0 // indirect - go.uber.org/mock v0.5.2 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/net v0.56.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect - golang.org/x/text v0.38.0 // indirect - golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.45.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect - google.golang.org/grpc v1.81.1 // indirect - google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - lukechampine.com/blake3 v1.4.1 // indirect -) diff --git a/kms/go.sum b/kms/go.sum deleted file mode 100644 index ec868633856..00000000000 --- a/kms/go.sum +++ /dev/null @@ -1,536 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= -github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= -github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= -github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd/btcutil v1.2.0 h1:p3+S2g3Q+7G5NOh4Ji+2UrBOrg5Z0Q4ykzShWG1Dhgs= -github.com/btcsuite/btcd/btcutil v1.2.0/go.mod h1:/Taflm113pYjUpbWKKQEfa6XOtI/+WS8awxeMZpY75k= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= -github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= -github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= -github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= -github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= -github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= -github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= -github.com/cosmos/gogoproto v1.7.2 h1:5G25McIraOC0mRFv9TVO139Uh3OklV2hczr13KKVHCA= -github.com/cosmos/gogoproto v1.7.2/go.mod h1:8S7w53P1Y1cHwND64o0BnArT6RmdgIvsBuco6uTllsk= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= -github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= -github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= -github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= -github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= -github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54= -github.com/dunglas/httpsfv v1.1.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= -github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= -github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= -github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= -github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= -github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= -github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= -github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= -github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= -github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= -github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= -github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= -github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= -github.com/libp2p/go-libp2p v0.47.0 h1:qQpBjSCWNQFF0hjBbKirMXE9RHLtSuzTDkTfr1rw0yc= -github.com/libp2p/go-libp2p v0.47.0/go.mod h1:s8HPh7mMV933OtXzONaGFseCg/BE//m1V34p3x4EUOY= -github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= -github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= -github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= -github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= -github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= -github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc= -github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= -github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= -github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= -github.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg= -github.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= -github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= -github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= -github.com/marcopolo/simnet v0.0.4 h1:50Kx4hS9kFGSRIbrt9xUS3NJX33EyPqHVmpXvaKLqrY= -github.com/marcopolo/simnet v0.0.4/go.mod h1:tfQF1u2DmaB6WHODMtQaLtClEf3a296CKQLq5gAsIS0= -github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= -github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= -github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= -github.com/miekg/pkcs11 v1.1.2 h1:/VxmeAX5qU6Q3EwafypogwWbYryHFmF2RpkJmw3m4MQ= -github.com/miekg/pkcs11 v1.1.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= -github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= -github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= -github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= -github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= -github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI= -github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8= -github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= -github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= -github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= -github.com/multiformats/go-multiaddr v0.16.1 h1:fgJ0Pitow+wWXzN9do+1b8Pyjmo8m5WhGfzpL82MpCw= -github.com/multiformats/go-multiaddr v0.16.1/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= -github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M= -github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= -github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= -github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo= -github.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo= -github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= -github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= -github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ= -github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= -github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= -github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= -github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= -github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= -github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/dtls/v3 v3.0.11 h1:zqn8YhoAU7d9whsWLhNiQlbB8QdpJj8XQVSc5ImUons= -github.com/pion/dtls/v3 v3.0.11/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= -github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= -github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= -github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= -github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= -github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= -github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= -github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= -github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= -github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= -github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= -github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= -github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= -github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4= -github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= -github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= -github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= -github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= -github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= -github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= -github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= -github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= -github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= -github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= -github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= -github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= -github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= -github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= -github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= -github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= -github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= -github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY= -github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= -github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= -github.com/quic-go/webtransport-go v0.10.0 h1:LqXXPOXuETY5Xe8ITdGisBzTYmUOy5eSj+9n4hLTjHI= -github.com/quic-go/webtransport-go v0.10.0/go.mod h1:LeGIXr5BQKE3UsynwVBeQrU1TPrbh73MGoC6jd+V7ow= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sasha-s/go-deadlock v0.3.9 h1:fiaT9rB7g5sr5ddNZvlwheclN9IP86eFW9WgqlEQV+w= -github.com/sasha-s/go-deadlock v0.3.9/go.mod h1:KuZj51ZFmx42q/mPaYbRk0P1xcwe697zsJKE03vD4/Y= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= -github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE= -github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= -github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 h1:qxen9oVGzDdIRP6ejyAJc760RwW4SnVDiTYTzwnXuxo= -go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5/go.mod h1:eW0HG9/oHQhvRCvb1/pIXW4cOvtDqeQK+XSi3TnwaXY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= -go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= -go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= -go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= -golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= -gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= -lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo= diff --git a/kms/internal/app/build.go b/kms/internal/app/build.go deleted file mode 100644 index 0d154e889ff..00000000000 --- a/kms/internal/app/build.go +++ /dev/null @@ -1,135 +0,0 @@ -// Package app wires a validated config into a runnable manager. -package app - -import ( - "encoding/hex" - "fmt" - "io" - - "github.com/cometbft/cometbft/libs/log" - - "github.com/cometbft/cometbft/kms/internal/backend" - "github.com/cometbft/cometbft/kms/internal/backend/pkcs11" - "github.com/cometbft/cometbft/kms/internal/backend/softsign" - "github.com/cometbft/cometbft/kms/internal/config" - "github.com/cometbft/cometbft/kms/internal/identity" - "github.com/cometbft/cometbft/kms/internal/manager" - "github.com/cometbft/cometbft/kms/internal/signer" - "github.com/cometbft/cometbft/kms/internal/transport" -) - -// Build constructs a Manager from a validated config. The returned cleanup -// function releases backend resources (e.g. PKCS#11 sessions) and must be called -// on shutdown. cleanup is non-nil even when an error is returned, so callers can -// always defer it. -func Build(c *config.Config, logger log.Logger) (mgr *manager.Manager, cleanup func(), err error) { - // Backends that hold OS/HSM resources are closed by cleanup. - var closers []io.Closer - cleanup = func() { - for _, cl := range closers { - _ = cl.Close() - } - } - // On error, release anything already opened before returning. - defer func() { - if err != nil { - cleanup() - } - }() - - // chainID -> backend (one backend per chain). - backends := map[string]backend.Signer{} - for _, p := range c.Providers.Softsign { - s, lerr := softsign.Load(p.KeyFile) - if lerr != nil { - return nil, cleanup, lerr - } - for _, id := range p.ChainIDs { - if _, dup := backends[id]; dup { - return nil, cleanup, fmt.Errorf("app: multiple backends bound to chain %q", id) - } - backends[id] = s - } - } - - for _, p := range c.Providers.PKCS11 { - var keyID []byte - if p.KeyID != "" { - keyID, err = hex.DecodeString(p.KeyID) - if err != nil { - return nil, cleanup, fmt.Errorf("app: pkcs11 provider key_id %q: %w", p.KeyID, err) - } - } - s, oerr := pkcs11.Open(pkcs11.Config{ - Module: p.Module, - TokenLabel: p.TokenLabel, - Slot: p.Slot, - KeyLabel: p.KeyLabel, - KeyID: keyID, - PIN: p.PIN, - PINEnv: p.PINEnv, - PINFile: p.PINFile, - Algorithm: p.Algorithm, - }) - if oerr != nil { - return nil, cleanup, oerr - } - closers = append(closers, s) - for _, id := range p.ChainIDs { - if _, dup := backends[id]; dup { - return nil, cleanup, fmt.Errorf("app: multiple backends bound to chain %q", id) - } - backends[id] = s - } - } - - // chainID -> state file. - stateFiles := map[string]string{} - for _, ch := range c.Chains { - stateFiles[ch.ID] = ch.StateFile - } - - // chainID -> *ChainSigner. - signers := map[string]*signer.ChainSigner{} - for id, be := range backends { - cs, cerr := signer.NewChainSigner(id, be, stateFiles[id]) - if cerr != nil { - return nil, cleanup, cerr - } - signers[id] = cs - } - - // One ValidatorConn per [[validator]]; validators of a chain share its signer. - var conns []manager.ValidatorConn - for _, v := range c.Validators { - cs, ok := signers[v.ChainID] - if !ok { - return nil, cleanup, fmt.Errorf("app: chain %q has no backend", v.ChainID) - } - idKey, lerr := identity.LoadOrGen(v.IdentityKey) - if lerr != nil { - return nil, cleanup, lerr - } - tr, addr, validatorPeer, perr := v.ParsedTransport() - if perr != nil { - return nil, cleanup, perr - } - vc := manager.ValidatorConn{ - ChainID: v.ChainID, - Addr: v.Addr, - IdentityKey: idKey, - Signer: cs, - Reconnect: v.ReconnectEnabled(), - } - if tr == config.TransportNoise { - d, derr := transport.NoiseDialer(addr, idKey, validatorPeer, manager.DefaultDialTimeout) - if derr != nil { - return nil, cleanup, derr - } - vc.Dialer = d - } - conns = append(conns, vc) - } - - return manager.New(logger, conns), cleanup, nil -} diff --git a/kms/internal/app/build_pkcs11_test.go b/kms/internal/app/build_pkcs11_test.go deleted file mode 100644 index edc27e6409a..00000000000 --- a/kms/internal/app/build_pkcs11_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package app_test - -import ( - "testing" - - "github.com/cometbft/cometbft/libs/log" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/app" - "github.com/cometbft/cometbft/kms/internal/backend/pkcs11/pkcs11test" - "github.com/cometbft/cometbft/kms/internal/config" -) - -// TestBuildWiresPKCS11Backend verifies a [[providers.pkcs11]] block is opened and -// wired into the manager, and that cleanup releases the HSM session. -func TestBuildWiresPKCS11Backend(t *testing.T) { - module := pkcs11test.FindModule(t) - pkcs11test.SetupToken(t, module) - home := t.TempDir() - - c := &config.Config{ - Chains: []config.Chain{{ID: "c1"}}, - Validators: []config.Validator{{ChainID: "c1", Addr: "tcp://127.0.0.1:1", IdentityKey: home + "/identity.json"}}, - Providers: config.Providers{PKCS11: []config.PKCS11Provider{{ - ChainIDs: []string{"c1"}, - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: pkcs11test.KeyLabel, - PIN: pkcs11test.UserPIN, - }}}, - } - require.NoError(t, c.Validate(home)) - - mgr, cleanup, err := app.Build(c, log.TestingLogger()) - require.NoError(t, err) - t.Cleanup(cleanup) // releases the PKCS#11 session even if assertions below fail - require.NotNil(t, mgr) -} diff --git a/kms/internal/app/build_test.go b/kms/internal/app/build_test.go deleted file mode 100644 index b6bf860b1c5..00000000000 --- a/kms/internal/app/build_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package app_test - -import ( - "os" - "path/filepath" - "testing" - - "github.com/cometbft/cometbft/crypto/ed25519" - cmtjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/libs/log" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/app" - "github.com/cometbft/cometbft/kms/internal/config" -) - -func TestBuildWiresChainSigners(t *testing.T) { - home := t.TempDir() - - keyPath := filepath.Join(home, "key.json") - raw, err := cmtjson.MarshalIndent(struct { - PrivKey ed25519.PrivKey `json:"priv_key"` - }{PrivKey: ed25519.GenPrivKey()}, "", " ") - require.NoError(t, err) - require.NoError(t, os.WriteFile(keyPath, raw, 0o600)) - - identPath := filepath.Join(home, "identity.json") - - c := &config.Config{ - Chains: []config.Chain{{ID: "c1"}}, - Validators: []config.Validator{{ChainID: "c1", Addr: "tcp://127.0.0.1:1", IdentityKey: identPath}}, - Providers: config.Providers{Softsign: []config.SoftsignProvider{{ChainIDs: []string{"c1"}, KeyFile: keyPath}}}, - } - require.NoError(t, c.Validate(home)) - - mgr, cleanup, err := app.Build(c, log.TestingLogger()) - require.NoError(t, err) - t.Cleanup(cleanup) - require.NotNil(t, mgr) -} - -func TestBuildFailsOnMissingKeyFile(t *testing.T) { - home := t.TempDir() - c := &config.Config{ - Chains: []config.Chain{{ID: "c1"}}, - Validators: []config.Validator{{ChainID: "c1", Addr: "tcp://127.0.0.1:1", IdentityKey: filepath.Join(home, "id.json")}}, - Providers: config.Providers{Softsign: []config.SoftsignProvider{{ChainIDs: []string{"c1"}, KeyFile: filepath.Join(home, "missing.json")}}}, - } - require.NoError(t, c.Validate(home)) - _, cleanup, err := app.Build(c, log.TestingLogger()) - t.Cleanup(cleanup) - require.Error(t, err) -} diff --git a/kms/internal/app/integration_test.go b/kms/internal/app/integration_test.go deleted file mode 100644 index 3093620085b..00000000000 --- a/kms/internal/app/integration_test.go +++ /dev/null @@ -1,350 +0,0 @@ -package app_test - -import ( - "context" - "errors" - "net" - "os" - "path/filepath" - "testing" - "time" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - cmtjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/libs/log" - "github.com/cometbft/cometbft/lp2p" - "github.com/cometbft/cometbft/privval" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cometbft/cometbft/types" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/backend" - "github.com/cometbft/cometbft/kms/internal/backend/softsign" - "github.com/cometbft/cometbft/kms/internal/manager" - "github.com/cometbft/cometbft/kms/internal/signer" - "github.com/cometbft/cometbft/kms/internal/transport" -) - -// failingBackend is a backend.Signer that is reachable and exposes a real public -// key, but whose Sign always returns a fixed error. It simulates a signing -// backend (HSM, cloud KMS, ...) that is connected yet rejects the signing -// operation itself — as opposed to a network/connection failure. -type failingBackend struct { - pub crypto.PubKey - err error -} - -var _ backend.Signer = failingBackend{} - -func (b failingBackend) PubKey(context.Context) (crypto.PubKey, error) { return b.pub, nil } -func (b failingBackend) Sign(context.Context, []byte) ([]byte, error) { return nil, b.err } - -// writeKey writes a softsign key file and returns its path. -func writeKey(t *testing.T, dir string) string { - t.Helper() - raw, err := cmtjson.MarshalIndent(struct { - PrivKey ed25519.PrivKey `json:"priv_key"` - }{PrivKey: ed25519.GenPrivKey()}, "", " ") - require.NoError(t, err) - p := filepath.Join(dir, "key.json") - require.NoError(t, os.WriteFile(p, raw, 0o600)) - return p -} - -// startListener starts a cometbft validator-side signer listener on ln and -// returns the endpoint. The KMS dials into this. -func startListener(t *testing.T, logger log.Logger, ln net.Listener) *privval.SignerListenerEndpoint { - t.Helper() - ep := privval.NewSignerListenerEndpoint(logger, privval.NewTCPListener(ln, ed25519.GenPrivKey())) - require.NoError(t, ep.Start()) - return ep -} - -func TestEndToEndSigning(t *testing.T) { - const chainID = "integration-chain" - dir := t.TempDir() - logger := log.TestingLogger() - - // cometkms (signer) side. - be, err := softsign.Load(writeKey(t, dir)) - require.NoError(t, err) - cs, err := signer.NewChainSigner(chainID, be, filepath.Join(dir, "state.json")) - require.NoError(t, err) - - // validator (listener) side. - ln, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - addr := ln.Addr().String() - listener := startListener(t, logger, ln) - defer func() { _ = listener.Stop() }() - - // cometkms Manager dials in. - mgr := manager.New(logger, []manager.ValidatorConn{{ - ChainID: chainID, - Addr: "tcp://" + addr, - IdentityKey: ed25519.GenPrivKey(), - Signer: cs, - Reconnect: true, - }}) - require.NoError(t, mgr.Start()) - defer mgr.Stop() - - // Drive requests as the validator would. - client, err := privval.NewSignerClient(listener, chainID) - require.NoError(t, err) - require.NoError(t, client.WaitForConnection(5*time.Second)) - - pub, err := client.GetPubKey() - require.NoError(t, err) - require.NotNil(t, pub) - - // Vote — signature must verify against canonical sign-bytes. - vote := &cmtproto.Vote{Type: cmtproto.PrevoteType, Height: 5, Round: 0} - require.NoError(t, client.SignVote(chainID, vote)) - require.True(t, pub.VerifySignature(types.VoteSignBytes(chainID, vote), vote.Signature)) - - // Proposal. - prop := &cmtproto.Proposal{Type: cmtproto.ProposalType, Height: 6, Round: 0} - require.NoError(t, client.SignProposal(chainID, prop)) - require.True(t, pub.VerifySignature(types.ProposalSignBytes(chainID, prop), prop.Signature)) - - // Double-sign: a lower height must be refused. - lower := &cmtproto.Vote{Type: cmtproto.PrevoteType, Height: 4, Round: 0} - require.Error(t, client.SignVote(chainID, lower)) -} - -// TestSigningBackendErrorReturnsEmbeddedError verifies that when the signing -// backend fails the signing operation, the validator-side client receives the -// backend's error *embedded* in the signed response (a RemoteSignerError) rather -// than a transport-level disconnect/connection drop, and that the connection -// survives the failure so subsequent requests still succeed. -func TestSigningBackendErrorReturnsEmbeddedError(t *testing.T) { - const chainID = "backend-error-chain" - dir := t.TempDir() - logger := log.TestingLogger() - - // cometkms (signer) side: a backend that is reachable (real pubkey) but whose - // Sign always fails. - wantErr := errors.New("backend signing unavailable") - be := failingBackend{pub: ed25519.GenPrivKey().PubKey(), err: wantErr} - cs, err := signer.NewChainSigner(chainID, be, filepath.Join(dir, "state.json")) - require.NoError(t, err) - - // validator (listener) side. - ln, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - addr := ln.Addr().String() - listener := startListener(t, logger, ln) - defer func() { _ = listener.Stop() }() - - // cometkms Manager dials in. - mgr := manager.New(logger, []manager.ValidatorConn{{ - ChainID: chainID, - Addr: "tcp://" + addr, - IdentityKey: ed25519.GenPrivKey(), - Signer: cs, - Reconnect: true, - }}) - require.NoError(t, mgr.Start()) - defer mgr.Stop() - - client, err := privval.NewSignerClient(listener, chainID) - require.NoError(t, err) - require.NoError(t, client.WaitForConnection(5*time.Second)) - - // The pubkey path does not exercise the failing Sign, so it must succeed — - // confirming the connection is healthy before we trigger the signing error. - pub, err := client.GetPubKey() - require.NoError(t, err) - require.NotNil(t, pub) - - // Signing must fail with the backend's error embedded in the response. The KMS - // handler wraps the backend error in a RemoteSignerError and still replies on - // the same connection — so the client sees a *privval.RemoteSignerError, not an - // EOF/timeout/connection-drop error. - vote := &cmtproto.Vote{Type: cmtproto.PrevoteType, Height: 5, Round: 0} - err = client.SignVote(chainID, vote) - require.Error(t, err) - - var rse *privval.RemoteSignerError - require.ErrorAs(t, err, &rse, - "client should receive an embedded RemoteSignerError, not a connection drop (got %T: %v)", err, err) - require.Contains(t, rse.Description, wantErr.Error(), - "embedded error should carry the signing backend's failure message") - - // A proposal goes through the same embedded-error path. - prop := &cmtproto.Proposal{Type: cmtproto.ProposalType, Height: 6, Round: 0} - err = client.SignProposal(chainID, prop) - require.Error(t, err) - require.ErrorAs(t, err, &rse, - "client should receive an embedded RemoteSignerError for proposals too (got %T: %v)", err, err) - - // The signing failures must NOT have dropped the connection: a follow-up - // request on the same client still succeeds, proving the connection survived - // (no reconnect/EOF in between). - pub2, err := client.GetPubKey() - require.NoError(t, err, "connection should survive an embedded signing error") - require.True(t, pub2.Equals(pub)) -} - -func TestEndToEndSigningNoise(t *testing.T) { - const chainID = "noise-chain" - dir := t.TempDir() - logger := log.TestingLogger() - - // Keys: validator node key (server) and KMS identity key (client). - validatorKey := ed25519.GenPrivKey() - kmsKey := ed25519.GenPrivKey() - validatorPeer, err := lp2p.IDFromPrivateKey(validatorKey) - require.NoError(t, err) - kmsPeer, err := lp2p.IDFromPrivateKey(kmsKey) - require.NoError(t, err) - - // KMS signer (softsign + ChainSigner). - be, err := softsign.Load(writeKey(t, dir)) - require.NoError(t, err) - cs, err := signer.NewChainSigner(chainID, be, filepath.Join(dir, "state.json")) - require.NoError(t, err) - - // Validator side: a Noise listener (node-key identity, allowlisting the KMS - // peer) wired into the standard SignerListenerEndpoint. - tcpLn, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - nl, err := privval.NewNoiseListener(tcpLn, validatorKey, []peer.ID{kmsPeer}) - require.NoError(t, err) - listener := privval.NewSignerListenerEndpoint(logger, nl) - require.NoError(t, listener.Start()) - defer func() { _ = listener.Stop() }() - - // KMS dials in over Noise, pinning the validator peer. - dial, err := transport.NoiseDialer(tcpLn.Addr().String(), kmsKey, validatorPeer, 3*time.Second) - require.NoError(t, err) - mgr := manager.New(logger, []manager.ValidatorConn{{ - ChainID: chainID, - Addr: "noise://" + tcpLn.Addr().String(), - IdentityKey: kmsKey, - Signer: cs, - Reconnect: true, - Dialer: dial, - }}) - require.NoError(t, mgr.Start()) - defer mgr.Stop() - - client, err := privval.NewSignerClient(listener, chainID) - require.NoError(t, err) - require.NoError(t, client.WaitForConnection(5*time.Second)) - - pub, err := client.GetPubKey() - require.NoError(t, err) - - vote := &cmtproto.Vote{Type: cmtproto.PrevoteType, Height: 7, Round: 0} - require.NoError(t, client.SignVote(chainID, vote)) - require.True(t, pub.VerifySignature(types.VoteSignBytes(chainID, vote), vote.Signature)) - - prop := &cmtproto.Proposal{Type: cmtproto.ProposalType, Height: 8, Round: 0} - require.NoError(t, client.SignProposal(chainID, prop)) - require.True(t, pub.VerifySignature(types.ProposalSignBytes(chainID, prop), prop.Signature)) -} - -func TestReconnectAfterListenerRestart(t *testing.T) { - const chainID = "rc-chain" - dir := t.TempDir() - logger := log.TestingLogger() - - be, err := softsign.Load(writeKey(t, dir)) - require.NoError(t, err) - cs, err := signer.NewChainSigner(chainID, be, filepath.Join(dir, "state.json")) - require.NoError(t, err) - - ln, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - addr := ln.Addr().String() - listener := startListener(t, logger, ln) - - mgr := manager.New(logger, []manager.ValidatorConn{{ - ChainID: chainID, Addr: "tcp://" + addr, IdentityKey: ed25519.GenPrivKey(), Signer: cs, Reconnect: true, - }}) - require.NoError(t, mgr.Start()) - defer mgr.Stop() - - client, err := privval.NewSignerClient(listener, chainID) - require.NoError(t, err) - require.NoError(t, client.WaitForConnection(5*time.Second)) - _, err = client.GetPubKey() - require.NoError(t, err) - - // Gracefully stop the validator side (sends FIN -> KMS sees EOF) and free the - // port. listener.Stop() closes the underlying net.Listener (ln), so we do not - // close ln again here. - require.NoError(t, listener.Stop()) - time.Sleep(100 * time.Millisecond) - - // Bring the validator back on the SAME address; the KMS must redial and resume. - ln2, err := net.Listen("tcp", addr) - require.NoError(t, err) - listener2 := startListener(t, logger, ln2) - defer func() { _ = listener2.Stop() }() - - client2, err := privval.NewSignerClient(listener2, chainID) - require.NoError(t, err) - - require.Eventually(t, func() bool { - if err := client2.WaitForConnection(500 * time.Millisecond); err != nil { - return false - } - _, err := client2.GetPubKey() - return err == nil - }, 20*time.Second, 200*time.Millisecond, "manager did not reconnect after graceful restart") -} - -func TestReconnectDisabledStopsAfterDrop(t *testing.T) { - const chainID = "rc-disabled-chain" - dir := t.TempDir() - logger := log.TestingLogger() - - be, err := softsign.Load(writeKey(t, dir)) - require.NoError(t, err) - cs, err := signer.NewChainSigner(chainID, be, filepath.Join(dir, "state.json")) - require.NoError(t, err) - - ln, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - addr := ln.Addr().String() - listener := startListener(t, logger, ln) - - mgr := manager.New(logger, []manager.ValidatorConn{{ - ChainID: chainID, Addr: "tcp://" + addr, IdentityKey: ed25519.GenPrivKey(), Signer: cs, Reconnect: false, - }}) - require.NoError(t, mgr.Start()) - defer mgr.Stop() - - client, err := privval.NewSignerClient(listener, chainID) - require.NoError(t, err) - require.NoError(t, client.WaitForConnection(5*time.Second)) - _, err = client.GetPubKey() - require.NoError(t, err) - - // Gracefully stop the validator side (KMS sees EOF) and free the port. - require.NoError(t, listener.Stop()) - time.Sleep(100 * time.Millisecond) - - // Bring the validator back on the SAME address. - ln2, err := net.Listen("tcp", addr) - require.NoError(t, err) - listener2 := startListener(t, logger, ln2) - defer func() { _ = listener2.Stop() }() - - client2, err := privval.NewSignerClient(listener2, chainID) - require.NoError(t, err) - - // With reconnect disabled, the KMS must NOT redial after the drop. - require.Never(t, func() bool { - if err := client2.WaitForConnection(300 * time.Millisecond); err != nil { - return false - } - _, err := client2.GetPubKey() - return err == nil - }, 3*time.Second, 300*time.Millisecond) -} diff --git a/kms/internal/backend/backend.go b/kms/internal/backend/backend.go deleted file mode 100644 index 6da5b759444..00000000000 --- a/kms/internal/backend/backend.go +++ /dev/null @@ -1,20 +0,0 @@ -// Package backend defines the signing-key abstraction used by cometkms. -// Implementations wrap a concrete key custodian (softsign file, PKCS#11 HSM, -// AWS KMS, ...). The interface is algorithm-agnostic: the public key carries its -// own algorithm via crypto.PubKey.Type(), and Sign produces a valid signature -// over the canonical consensus sign-bytes using whatever scheme the key requires. -package backend - -import ( - "context" - - "github.com/cometbft/cometbft/crypto" -) - -// Signer is implemented by every key backend. -type Signer interface { - // PubKey returns the validator public key. - PubKey(ctx context.Context) (crypto.PubKey, error) - // Sign signs the canonical consensus sign-bytes and returns the signature. - Sign(ctx context.Context, signBytes []byte) ([]byte, error) -} diff --git a/kms/internal/backend/pkcs11/algo.go b/kms/internal/backend/pkcs11/algo.go deleted file mode 100644 index 4486ccb6624..00000000000 --- a/kms/internal/backend/pkcs11/algo.go +++ /dev/null @@ -1,59 +0,0 @@ -package pkcs11 - -import ( - "fmt" - - "github.com/miekg/pkcs11" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" -) - -// ckmEDDSA is the standard PKCS#11 v3.0 EdDSA signing mechanism. miekg/pkcs11 -// v1.1.x does not export it, so it is defined here against the spec value. -const ckmEDDSA = 0x00001057 - -// algoEd25519 is the config "algorithm" name for Ed25519 keys (the default). -const algoEd25519 = "ed25519" - -// keyAlgo describes how one validator key algorithm maps onto PKCS#11: which -// signing mechanism to use, how to turn the token's public-key bytes into a -// crypto.PubKey, and how to normalize the raw signature the token returns. -// -// Adding a new key type (ml-dsa, secp256k1eth, ...) is a single new entry in -// algos: its mechanism, a decodePub, and (for ECDSA-family keys) a fixSig that -// converts the token's DER signature into the consensus wire format. -type keyAlgo struct { - name string - mechanism func() []*pkcs11.Mechanism - decodePub func(ckaECPoint []byte) (crypto.PubKey, error) - fixSig func(raw []byte) ([]byte, error) -} - -// algos is the registry of supported key algorithms, keyed by the config -// "algorithm" string. Ed25519 is the only entry for now. -var algos = map[string]keyAlgo{ - algoEd25519: { - name: algoEd25519, - mechanism: func() []*pkcs11.Mechanism { return []*pkcs11.Mechanism{pkcs11.NewMechanism(ckmEDDSA, nil)} }, - decodePub: decodeEd25519Pub, - fixSig: func(raw []byte) ([]byte, error) { return raw, nil }, - }, -} - -// decodeEd25519Pub turns a CKA_EC_POINT value into an ed25519 crypto.PubKey. -// PKCS#11 v3.0 encodes the point as a DER OCTET STRING wrapping the 32-byte key -// (0x04 0x20 <32 bytes>); some tokens return the raw 32 bytes. Both are accepted. -func decodeEd25519Pub(ckaECPoint []byte) (crypto.PubKey, error) { - raw := ckaECPoint - // DER OCTET STRING (tag 0x04) of length 0x20 (32) wrapping the key. - if len(ckaECPoint) == ed25519.PubKeySize+2 && ckaECPoint[0] == 0x04 && ckaECPoint[1] == ed25519.PubKeySize { - raw = ckaECPoint[2:] - } - if len(raw) != ed25519.PubKeySize { - return nil, fmt.Errorf("ed25519 CKA_EC_POINT: expected %d-byte key, got %d bytes", ed25519.PubKeySize, len(raw)) - } - pub := make(ed25519.PubKey, ed25519.PubKeySize) - copy(pub, raw) - return pub, nil -} diff --git a/kms/internal/backend/pkcs11/algo_test.go b/kms/internal/backend/pkcs11/algo_test.go deleted file mode 100644 index c3963b9f505..00000000000 --- a/kms/internal/backend/pkcs11/algo_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package pkcs11 - -import ( - "testing" - - "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/stretchr/testify/require" -) - -func TestEd25519DecodePub_DERWrapped(t *testing.T) { - priv := ed25519.GenPrivKey() - raw := priv.PubKey().Bytes() // 32 bytes - // PKCS#11 v3.0 returns CKA_EC_POINT as a DER OCTET STRING: 0x04 0x20 <32 bytes>. - der := append([]byte{0x04, 0x20}, raw...) - - pub, err := algos["ed25519"].decodePub(der) - require.NoError(t, err) - require.True(t, pub.Equals(priv.PubKey())) -} - -func TestEd25519DecodePub_Raw(t *testing.T) { - priv := ed25519.GenPrivKey() - raw := priv.PubKey().Bytes() // some tokens return the raw 32-byte point - - pub, err := algos["ed25519"].decodePub(raw) - require.NoError(t, err) - require.True(t, pub.Equals(priv.PubKey())) -} - -func TestEd25519DecodePub_BadLength(t *testing.T) { - _, err := algos["ed25519"].decodePub([]byte{0x01, 0x02, 0x03}) - require.Error(t, err) -} - -func TestEd25519FixSig_Identity(t *testing.T) { - sig := []byte("a-64-byte-ed25519-signature-placeholder-value-for-testing-only!!") - out, err := algos["ed25519"].fixSig(sig) - require.NoError(t, err) - require.Equal(t, sig, out) -} diff --git a/kms/internal/backend/pkcs11/module.go b/kms/internal/backend/pkcs11/module.go deleted file mode 100644 index f9dd7b78998..00000000000 --- a/kms/internal/backend/pkcs11/module.go +++ /dev/null @@ -1,68 +0,0 @@ -package pkcs11 - -import ( - "fmt" - "sync" - - "github.com/miekg/pkcs11" -) - -// A PKCS#11 module is a shared library with process-global C_Initialize state: -// it must be initialized exactly once per process and finalized once. Multiple -// signers may target the same module (e.g. several chains on one HSM), so module -// contexts are ref-counted here and shared across signers. The context is -// finalized only when the last signer using it closes. -var ( - modulesMu sync.Mutex - modules = map[string]*moduleRef{} -) - -type moduleRef struct { - ctx *pkcs11.Ctx - refs int -} - -// acquireModule returns a shared, initialized context for the module at path, -// loading and initializing it on first use and bumping its ref count otherwise. -func acquireModule(path string) (*pkcs11.Ctx, error) { - modulesMu.Lock() - defer modulesMu.Unlock() - - if m := modules[path]; m != nil { - m.refs++ - return m.ctx, nil - } - - ctx := pkcs11.New(path) - if ctx == nil { - return nil, fmt.Errorf("pkcs11: failed to load module %q", path) - } - if err := ctx.Initialize(); err != nil { - // Another consumer in this process may have already initialized the - // underlying library; treat that as success and adopt it. - if ce, ok := err.(pkcs11.Error); !ok || ce != pkcs11.CKR_CRYPTOKI_ALREADY_INITIALIZED { - ctx.Destroy() - return nil, fmt.Errorf("pkcs11: initialize module %q: %w", path, err) - } - } - modules[path] = &moduleRef{ctx: ctx, refs: 1} - return ctx, nil -} - -// releaseModule drops one reference to the module at path, finalizing and -// unloading it when the last reference is released. -func releaseModule(path string) { - modulesMu.Lock() - defer modulesMu.Unlock() - - m := modules[path] - if m == nil { - return - } - m.refs-- - if m.refs <= 0 { - _ = m.ctx.Finalize() - m.ctx.Destroy() - delete(modules, path) - } -} diff --git a/kms/internal/backend/pkcs11/pin_test.go b/kms/internal/backend/pkcs11/pin_test.go deleted file mode 100644 index 5b478fce3d2..00000000000 --- a/kms/internal/backend/pkcs11/pin_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package pkcs11 - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestResolvePIN_Inline(t *testing.T) { - pin, err := resolvePIN(Config{PIN: "1234"}) - require.NoError(t, err) - require.Equal(t, "1234", pin) -} - -func TestResolvePIN_Env(t *testing.T) { - t.Setenv("COMETKMS_TEST_PIN", "envpin") - pin, err := resolvePIN(Config{PINEnv: "COMETKMS_TEST_PIN"}) - require.NoError(t, err) - require.Equal(t, "envpin", pin) -} - -func TestResolvePIN_EnvEmpty(t *testing.T) { - t.Setenv("COMETKMS_TEST_PIN", "") - _, err := resolvePIN(Config{PINEnv: "COMETKMS_TEST_PIN"}) - require.Error(t, err) -} - -func TestResolvePIN_File(t *testing.T) { - path := filepath.Join(t.TempDir(), "pin") - require.NoError(t, os.WriteFile(path, []byte("filepin\n"), 0o600)) - pin, err := resolvePIN(Config{PINFile: path}) - require.NoError(t, err) - require.Equal(t, "filepin", pin) // trailing newline trimmed -} - -func TestResolvePIN_FileEmpty(t *testing.T) { - path := filepath.Join(t.TempDir(), "pin") - require.NoError(t, os.WriteFile(path, []byte("\n"), 0o600)) - _, err := resolvePIN(Config{PINFile: path}) - require.Error(t, err) -} - -func TestResolvePIN_FileMissing(t *testing.T) { - _, err := resolvePIN(Config{PINFile: filepath.Join(t.TempDir(), "nope")}) - require.Error(t, err) -} - -func TestResolvePIN_NoSource(t *testing.T) { - _, err := resolvePIN(Config{}) - require.Error(t, err) -} diff --git a/kms/internal/backend/pkcs11/pkcs11.go b/kms/internal/backend/pkcs11/pkcs11.go deleted file mode 100644 index 3fce8f124f2..00000000000 --- a/kms/internal/backend/pkcs11/pkcs11.go +++ /dev/null @@ -1,261 +0,0 @@ -// Package pkcs11 implements a backend.Signer backed by a PKCS#11 token or HSM. -// The private key never leaves the token: signing is performed on-device via the -// PKCS#11 C_Sign operation. Ed25519 (CKM_EDDSA) is the only key algorithm today; -// see algo.go for the per-algorithm seam. -package pkcs11 - -import ( - "context" - "fmt" - "os" - "strings" - "sync" - - "github.com/miekg/pkcs11" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/kms/internal/backend" -) - -var _ backend.Signer = (*Signer)(nil) - -// Config describes how to open a key on a PKCS#11 token. Exactly one of -// TokenLabel/Slot selects the token, at least one of KeyLabel/KeyID selects the -// key, and exactly one of PIN/PINEnv/PINFile supplies the user PIN. Algorithm -// defaults to "ed25519" when empty. -type Config struct { - Module string - TokenLabel string - Slot *uint - KeyLabel string - KeyID []byte - PIN string - PINEnv string - PINFile string - Algorithm string -} - -// Signer is a backend.Signer that signs on a PKCS#11 token. It owns a single -// long-lived session; the mutex serializes signing (PKCS#11 sessions are not -// safe for concurrent use) and guards Close. -type Signer struct { - mod *pkcs11.Ctx - module string // module path, used to release the shared context on Close - session pkcs11.SessionHandle - privH pkcs11.ObjectHandle - pub crypto.PubKey - algo keyAlgo - - mu sync.Mutex - closed bool -} - -// Open loads the PKCS#11 module, logs into the selected token, locates the key, -// and caches its public key. Any failure is returned (fatal at startup for the -// chain). On success the returned Signer holds an open, logged-in session that -// must be released with Close. -func Open(cfg Config) (s *Signer, err error) { - algoName := cfg.Algorithm - if algoName == "" { - algoName = algoEd25519 - } - algo, ok := algos[algoName] - if !ok { - return nil, fmt.Errorf("pkcs11: unknown algorithm %q", algoName) - } - - pin, err := resolvePIN(cfg) - if err != nil { - return nil, err - } - - mod, err := acquireModule(cfg.Module) - if err != nil { - return nil, err - } - // Release our module reference on any error past this point. - defer func() { - if err != nil { - releaseModule(cfg.Module) - } - }() - - slot, err := selectSlot(mod, cfg) - if err != nil { - return nil, err - } - - session, err := mod.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION) - if err != nil { - return nil, fmt.Errorf("pkcs11: open session on slot %d: %w", slot, err) - } - defer func() { - if err != nil { - _ = mod.CloseSession(session) - } - }() - - // Login is per-application (shared across sessions on a slot): a concurrent - // signer on the same token may already hold the login, which is fine. - if err = mod.Login(session, pkcs11.CKU_USER, pin); err != nil { - if ce, ok := err.(pkcs11.Error); !ok || ce != pkcs11.CKR_USER_ALREADY_LOGGED_IN { - return nil, fmt.Errorf("pkcs11: login: %w", err) - } - err = nil - } - - privH, err := findObject(mod, session, pkcs11.CKO_PRIVATE_KEY, cfg) - if err != nil { - return nil, fmt.Errorf("pkcs11: find private key: %w", err) - } - pubH, err := findObject(mod, session, pkcs11.CKO_PUBLIC_KEY, cfg) - if err != nil { - return nil, fmt.Errorf("pkcs11: find public key: %w", err) - } - - attrs, err := mod.GetAttributeValue(session, pubH, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, nil), - }) - if err != nil { - return nil, fmt.Errorf("pkcs11: read public key: %w", err) - } - if len(attrs) == 0 { - return nil, fmt.Errorf("pkcs11: public key has no CKA_EC_POINT") - } - pub, err := algo.decodePub(attrs[0].Value) - if err != nil { - return nil, fmt.Errorf("pkcs11: decode public key: %w", err) - } - - return &Signer{mod: mod, module: cfg.Module, session: session, privH: privH, pub: pub, algo: algo}, nil -} - -// selectSlot returns the slot to use: the explicit Slot when set, otherwise the -// slot whose token CKA_LABEL matches TokenLabel. -func selectSlot(mod *pkcs11.Ctx, cfg Config) (uint, error) { - if cfg.Slot != nil { - return *cfg.Slot, nil - } - slots, err := mod.GetSlotList(true) - if err != nil { - return 0, fmt.Errorf("pkcs11: list slots: %w", err) - } - for _, slot := range slots { - info, err := mod.GetTokenInfo(slot) - if err != nil { - continue - } - // Token labels are space-padded to 32 bytes by the spec. - if strings.TrimRight(info.Label, " ") == cfg.TokenLabel { - return slot, nil - } - } - return 0, fmt.Errorf("pkcs11: no token with label %q", cfg.TokenLabel) -} - -// findObject locates exactly one key object of the given class matching the -// configured label and/or id. -func findObject(mod *pkcs11.Ctx, session pkcs11.SessionHandle, class uint, cfg Config) (pkcs11.ObjectHandle, error) { - template := []*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_CLASS, class)} - if cfg.KeyLabel != "" { - template = append(template, pkcs11.NewAttribute(pkcs11.CKA_LABEL, cfg.KeyLabel)) - } - if len(cfg.KeyID) > 0 { - template = append(template, pkcs11.NewAttribute(pkcs11.CKA_ID, cfg.KeyID)) - } - - if err := mod.FindObjectsInit(session, template); err != nil { - return 0, err - } - handles, _, err := mod.FindObjects(session, 2) - if finErr := mod.FindObjectsFinal(session); finErr != nil && err == nil { - err = finErr - } - if err != nil { - return 0, err - } - switch { - case len(handles) == 0: - return 0, fmt.Errorf("no object matching %s", keySelector(cfg)) - case len(handles) > 1: - return 0, fmt.Errorf("multiple objects match %s: refine key_label/key_id", keySelector(cfg)) - } - return handles[0], nil -} - -// keySelector describes the configured key search criteria for error messages. -func keySelector(cfg Config) string { - switch { - case cfg.KeyLabel != "" && len(cfg.KeyID) > 0: - return fmt.Sprintf("key_label=%q key_id=%x", cfg.KeyLabel, cfg.KeyID) - case cfg.KeyLabel != "": - return fmt.Sprintf("key_label=%q", cfg.KeyLabel) - default: - return fmt.Sprintf("key_id=%x", cfg.KeyID) - } -} - -// PubKey returns the validator public key cached at Open. -func (s *Signer) PubKey(context.Context) (crypto.PubKey, error) { return s.pub, nil } - -// Sign signs the canonical consensus sign-bytes on the token. -func (s *Signer) Sign(_ context.Context, signBytes []byte) ([]byte, error) { - s.mu.Lock() - defer s.mu.Unlock() - if s.closed { - return nil, fmt.Errorf("pkcs11: signer is closed") - } - if err := s.mod.SignInit(s.session, s.algo.mechanism(), s.privH); err != nil { - return nil, fmt.Errorf("pkcs11: sign init: %w", err) - } - raw, err := s.mod.Sign(s.session, signBytes) - if err != nil { - return nil, fmt.Errorf("pkcs11: sign: %w", err) - } - return s.algo.fixSig(raw) -} - -// Close logs out, closes the session, and tears down the module. It is -// idempotent. -func (s *Signer) Close() error { - s.mu.Lock() - defer s.mu.Unlock() - if s.closed { - return nil - } - s.closed = true - _ = s.mod.CloseSession(s.session) - // releaseModule finalizes and unloads the module once the last signer using - // it has closed (Finalize tears down login state and sessions). - releaseModule(s.module) - return nil -} - -// resolvePIN returns the user PIN from whichever source the config specifies. -// The PIN is read at open time (not stored in config files): an env var is read -// from the process environment; a file is read and stripped of trailing -// whitespace. An empty resolved PIN is an error. -func resolvePIN(cfg Config) (string, error) { - switch { - case cfg.PIN != "": - return cfg.PIN, nil - case cfg.PINEnv != "": - v := os.Getenv(cfg.PINEnv) - if v == "" { - return "", fmt.Errorf("pkcs11: pin_env %q is empty or unset", cfg.PINEnv) - } - return v, nil - case cfg.PINFile != "": - raw, err := os.ReadFile(cfg.PINFile) - if err != nil { - return "", fmt.Errorf("pkcs11: read pin_file %q: %w", cfg.PINFile, err) - } - v := strings.TrimRight(string(raw), " \t\r\n") - if v == "" { - return "", fmt.Errorf("pkcs11: pin_file %q is empty", cfg.PINFile) - } - return v, nil - default: - return "", fmt.Errorf("pkcs11: no PIN source configured (set pin, pin_env, or pin_file)") - } -} diff --git a/kms/internal/backend/pkcs11/pkcs11_test.go b/kms/internal/backend/pkcs11/pkcs11_test.go deleted file mode 100644 index 3e423618e00..00000000000 --- a/kms/internal/backend/pkcs11/pkcs11_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package pkcs11_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - pk "github.com/cometbft/cometbft/kms/internal/backend/pkcs11" - "github.com/cometbft/cometbft/kms/internal/backend/pkcs11/pkcs11test" -) - -func TestOpenPubKeySignVerify(t *testing.T) { - module := pkcs11test.FindModule(t) - want := pkcs11test.SetupToken(t, module) - - s, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: pkcs11test.KeyLabel, - PIN: pkcs11test.UserPIN, - }) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - pub, err := s.PubKey(context.Background()) - require.NoError(t, err) - require.True(t, pub.Equals(want), "backend pubkey must match the on-token key") - - msg := []byte("canonical-consensus-sign-bytes") - sig, err := s.Sign(context.Background(), msg) - require.NoError(t, err) - require.True(t, pub.VerifySignature(msg, sig), "signature must verify under the on-token pubkey") -} - -func TestOpenByKeyID(t *testing.T) { - module := pkcs11test.FindModule(t) - want := pkcs11test.SetupToken(t, module) - - s, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyID: pkcs11test.KeyID, - PIN: pkcs11test.UserPIN, - }) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) - - pub, err := s.PubKey(context.Background()) - require.NoError(t, err) - require.True(t, pub.Equals(want)) -} - -func TestOpenWrongPIN(t *testing.T) { - module := pkcs11test.FindModule(t) - pkcs11test.SetupToken(t, module) - - _, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: pkcs11test.KeyLabel, - PIN: "9999", - }) - require.Error(t, err) -} - -func TestOpenUnknownToken(t *testing.T) { - module := pkcs11test.FindModule(t) - pkcs11test.SetupToken(t, module) - - _, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: "no-such-token", - KeyLabel: pkcs11test.KeyLabel, - PIN: pkcs11test.UserPIN, - }) - require.Error(t, err) -} - -func TestOpenUnknownKey(t *testing.T) { - module := pkcs11test.FindModule(t) - pkcs11test.SetupToken(t, module) - - _, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: "no-such-key", - PIN: pkcs11test.UserPIN, - }) - require.Error(t, err) - require.ErrorContains(t, err, "no object matching") -} - -func TestOpenAmbiguousKeyLabel(t *testing.T) { - module := pkcs11test.FindModule(t) - pkcs11test.SetupToken(t, module) - // A second key pair with the same label but a different id makes a - // label-only lookup ambiguous. - pkcs11test.AddKeyPair(t, module, pkcs11test.KeyLabel, []byte{0x02}) - - _, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: pkcs11test.KeyLabel, - PIN: pkcs11test.UserPIN, - }) - require.Error(t, err) - require.ErrorContains(t, err, "multiple objects match") - - // Adding the unique key_id disambiguates. - s, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: pkcs11test.KeyLabel, - KeyID: pkcs11test.KeyID, - PIN: pkcs11test.UserPIN, - }) - require.NoError(t, err) - t.Cleanup(func() { _ = s.Close() }) -} - -func TestDoubleCloseSafe(t *testing.T) { - module := pkcs11test.FindModule(t) - pkcs11test.SetupToken(t, module) - - s, err := pk.Open(pk.Config{ - Module: module, - TokenLabel: pkcs11test.TokenLabel, - KeyLabel: pkcs11test.KeyLabel, - PIN: pkcs11test.UserPIN, - }) - require.NoError(t, err) - require.NoError(t, s.Close()) - require.NoError(t, s.Close()) -} diff --git a/kms/internal/backend/pkcs11/pkcs11test/pkcs11test.go b/kms/internal/backend/pkcs11/pkcs11test/pkcs11test.go deleted file mode 100644 index 1632d8db3ce..00000000000 --- a/kms/internal/backend/pkcs11/pkcs11test/pkcs11test.go +++ /dev/null @@ -1,193 +0,0 @@ -// Package pkcs11test provides SoftHSM2-backed helpers for exercising the PKCS#11 -// signer in tests. It initializes an isolated, throwaway token and generates an -// Ed25519 key in it. Tests that use it auto-skip when SoftHSM2 is not installed. -package pkcs11test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/miekg/pkcs11" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/crypto/ed25519" -) - -// PKCS#11 / SoftHSM2 constants not exported by miekg/pkcs11 v1.1.x. -const ( - ckmECEdwardsKeyPairGen = 0x00001055 - ckkECEdwards = 0x00000040 -) - -// ed25519 curve OID 1.3.101.112, DER-encoded for CKA_EC_PARAMS. -var oidEd25519 = []byte{0x06, 0x03, 0x2b, 0x65, 0x70} - -// Fixed token/key identifiers used by the helper. -const ( - TokenLabel = "comet" - KeyLabel = "validator" - UserPIN = "1234" - soPIN = "4321" -) - -// KeyID is the CKA_ID of the generated key. -var KeyID = []byte{0x01} - -// commonModulePaths lists where libsofthsm2.so is typically installed. -var commonModulePaths = []string{ - "/opt/homebrew/lib/softhsm/libsofthsm2.so", - "/usr/local/lib/softhsm/libsofthsm2.so", - "/usr/lib/softhsm/libsofthsm2.so", - "/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so", -} - -// findSlotByLabel returns the slot whose token label matches label. Token -// labels are space-padded to 32 bytes by the spec. -func findSlotByLabel(t *testing.T, ctx *pkcs11.Ctx, label string) uint { - t.Helper() - slots, err := ctx.GetSlotList(true) - require.NoError(t, err) - for _, s := range slots { - info, err := ctx.GetTokenInfo(s) - if err != nil { - continue - } - if strings.TrimRight(info.Label, " ") == label { - return s - } - } - t.Fatalf("no slot with token label %q", label) - return 0 -} - -// FindModule returns the SoftHSM2 module path, or skips the test if not found. -func FindModule(t *testing.T) string { - t.Helper() - if p := os.Getenv("COMETKMS_SOFTHSM_LIB"); p != "" { - return p - } - for _, p := range commonModulePaths { - if _, err := os.Stat(p); err == nil { - return p - } - } - t.Skip("SoftHSM2 module not found; set COMETKMS_SOFTHSM_LIB to run PKCS#11 integration tests") - return "" -} - -// SetupToken initializes a fresh SoftHSM2 token in an isolated temp directory and -// generates an Ed25519 key pair in it. It returns the on-token public key so the -// caller can verify what the signer reads and signs. SOFTHSM2_CONF is pointed at -// the temp dir for the duration of the test. -func SetupToken(t *testing.T, module string) ed25519.PubKey { - t.Helper() - - tokenDir := t.TempDir() - confPath := filepath.Join(t.TempDir(), "softhsm2.conf") - conf := "directories.tokendir = " + tokenDir + "\nobjectstore.backend = file\nlog.level = ERROR\n" - require.NoError(t, os.WriteFile(confPath, []byte(conf), 0o600)) - t.Setenv("SOFTHSM2_CONF", confPath) - - ctx := pkcs11.New(module) - require.NotNil(t, ctx, "load module") - require.NoError(t, ctx.Initialize()) - // Finalize this provisioning context before returning so the signer under - // test can initialize the (process-global) module cleanly. Token data - // persists on disk via the file objectstore. - defer func() { _ = ctx.Finalize(); ctx.Destroy() }() - - slots, err := ctx.GetSlotList(false) - require.NoError(t, err) - require.NotEmpty(t, slots) - slot := slots[0] - - require.NoError(t, ctx.InitToken(slot, soPIN, TokenLabel)) - - // Re-query after InitToken: the PKCS#11 spec allows the slot list to change - // (SoftHSM2 may assign the initialized token a new slot ID). Find our token - // by label rather than trusting the original slot ID. - slot = findSlotByLabel(t, ctx, TokenLabel) - - // Set the user PIN: open RW, login SO, InitPIN, logout. - sess, err := ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) - require.NoError(t, err) - require.NoError(t, ctx.Login(sess, pkcs11.CKU_SO, soPIN)) - require.NoError(t, ctx.InitPIN(sess, UserPIN)) - require.NoError(t, ctx.Logout(sess)) - - // Generate the Ed25519 key pair as the user. - require.NoError(t, ctx.Login(sess, pkcs11.CKU_USER, UserPIN)) - pubH := genKeyPair(t, ctx, sess, KeyLabel, KeyID) - - // Read the public point back so the caller can verify signatures. - attrs, err := ctx.GetAttributeValue(sess, pubH, []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, nil), - }) - require.NoError(t, err) - require.Len(t, attrs, 1) - - require.NoError(t, ctx.Logout(sess)) - require.NoError(t, ctx.CloseSession(sess)) - - raw := attrs[0].Value - // Unwrap a DER OCTET STRING (0x04 0x20 <32 bytes>) if present. - if len(raw) == ed25519.PubKeySize+2 && raw[0] == 0x04 && raw[1] == ed25519.PubKeySize { - raw = raw[2:] - } - require.Len(t, raw, ed25519.PubKeySize) - pub := make(ed25519.PubKey, ed25519.PubKeySize) - copy(pub, raw) - return pub -} - -// AddKeyPair generates an additional Ed25519 key pair with the given label and -// id on the token previously provisioned by SetupToken. -func AddKeyPair(t *testing.T, module, label string, id []byte) { - t.Helper() - - ctx := pkcs11.New(module) - require.NotNil(t, ctx, "load module") - require.NoError(t, ctx.Initialize()) - defer func() { _ = ctx.Finalize(); ctx.Destroy() }() - - slot := findSlotByLabel(t, ctx, TokenLabel) - sess, err := ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) - require.NoError(t, err) - require.NoError(t, ctx.Login(sess, pkcs11.CKU_USER, UserPIN)) - genKeyPair(t, ctx, sess, label, id) - require.NoError(t, ctx.Logout(sess)) - require.NoError(t, ctx.CloseSession(sess)) -} - -// genKeyPair generates a token-resident Ed25519 key pair with the given label -// and id, returning the public key handle. The session must be logged in as -// the user. -func genKeyPair(t *testing.T, ctx *pkcs11.Ctx, sess pkcs11.SessionHandle, label string, id []byte) pkcs11.ObjectHandle { - t.Helper() - - pubTemplate := []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), - pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, ckkECEdwards), - pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, oidEd25519), - pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), - pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true), - pkcs11.NewAttribute(pkcs11.CKA_LABEL, label), - pkcs11.NewAttribute(pkcs11.CKA_ID, id), - } - privTemplate := []*pkcs11.Attribute{ - pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), - pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, ckkECEdwards), - pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), - pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), - pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), - pkcs11.NewAttribute(pkcs11.CKA_LABEL, label), - pkcs11.NewAttribute(pkcs11.CKA_ID, id), - } - pubH, _, err := ctx.GenerateKeyPair(sess, - []*pkcs11.Mechanism{pkcs11.NewMechanism(ckmECEdwardsKeyPairGen, nil)}, - pubTemplate, privTemplate) - require.NoError(t, err) - return pubH -} diff --git a/kms/internal/backend/softsign/softsign.go b/kms/internal/backend/softsign/softsign.go deleted file mode 100644 index dae10f97ac9..00000000000 --- a/kms/internal/backend/softsign/softsign.go +++ /dev/null @@ -1,78 +0,0 @@ -// Package softsign implements an in-memory, file-backed Ed25519 backend.Signer. -// NOT for production custody: the private key is held in process memory. -package softsign - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "os" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - cmtjson "github.com/cometbft/cometbft/libs/json" - - "github.com/cometbft/cometbft/kms/internal/backend" -) - -// Signer is a softsign backend holding an Ed25519 private key in memory. -type Signer struct { - priv crypto.PrivKey - pub crypto.PubKey -} - -var _ backend.Signer = (*Signer)(nil) - -// Load reads a key file. It accepts either a CometBFT priv_validator_key.json -// (typed JSON with a "priv_key" field) or a file containing the base64-encoded -// 64-byte Ed25519 private key. -func Load(path string) (*Signer, error) { - raw, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("softsign: read key file %q: %w", path, err) - } - - priv, err := parseKey(raw) - if err != nil { - return nil, fmt.Errorf("softsign: parse key file %q: %w", path, err) - } - return &Signer{priv: priv, pub: priv.PubKey()}, nil -} - -func parseKey(raw []byte) (crypto.PrivKey, error) { - // Try priv_validator_key.json shape first (both concrete and interface-typed variants). - if bytes.Contains(raw, []byte("priv_key")) { - // Try interface-typed JSON first ({"type":"...","value":"..."} envelope). - var kfIface struct { - PrivKey crypto.PrivKey `json:"priv_key"` - } - if err := cmtjson.Unmarshal(raw, &kfIface); err == nil && kfIface.PrivKey != nil { - return kfIface.PrivKey, nil - } - // Try concrete ed25519 JSON (plain base64 string value). - var kfConcrete struct { - PrivKey ed25519.PrivKey `json:"priv_key"` - } - if err := cmtjson.Unmarshal(raw, &kfConcrete); err == nil && len(kfConcrete.PrivKey) == ed25519.PrivateKeySize { - return kfConcrete.PrivKey, nil - } - } - // Fall back to base64 raw 64-byte ed25519 key. - dec, err := base64.StdEncoding.DecodeString(string(bytes.TrimSpace(raw))) - if err != nil { - return nil, fmt.Errorf("not priv_validator_key.json and not base64: %w", err) - } - if len(dec) != ed25519.PrivateKeySize { - return nil, fmt.Errorf("expected %d-byte ed25519 key, got %d", ed25519.PrivateKeySize, len(dec)) - } - return ed25519.PrivKey(dec), nil -} - -// PubKey implements backend.Signer. -func (s *Signer) PubKey(context.Context) (crypto.PubKey, error) { return s.pub, nil } - -// Sign implements backend.Signer. -func (s *Signer) Sign(_ context.Context, signBytes []byte) ([]byte, error) { - return s.priv.Sign(signBytes) -} diff --git a/kms/internal/backend/softsign/softsign_test.go b/kms/internal/backend/softsign/softsign_test.go deleted file mode 100644 index 3499e09cbbe..00000000000 --- a/kms/internal/backend/softsign/softsign_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package softsign_test - -import ( - "context" - "encoding/base64" - "os" - "path/filepath" - "testing" - - "github.com/cometbft/cometbft/crypto/ed25519" - cmtjson "github.com/cometbft/cometbft/libs/json" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/backend/softsign" -) - -func TestLoadBase64AndSign(t *testing.T) { - priv := ed25519.GenPrivKey() - dir := t.TempDir() - path := filepath.Join(dir, "key.b64") - require.NoError(t, os.WriteFile(path, []byte(base64.StdEncoding.EncodeToString(priv.Bytes())), 0o600)) - - s, err := softsign.Load(path) - require.NoError(t, err) - - pub, err := s.PubKey(context.Background()) - require.NoError(t, err) - require.True(t, pub.Equals(priv.PubKey())) - - msg := []byte("canonical-sign-bytes") - sig, err := s.Sign(context.Background(), msg) - require.NoError(t, err) - require.True(t, pub.VerifySignature(msg, sig)) -} - -func TestLoadPrivValidatorKeyJSON(t *testing.T) { - priv := ed25519.GenPrivKey() - raw, err := cmtjson.MarshalIndent(struct { - PrivKey ed25519.PrivKey `json:"priv_key"` - }{PrivKey: priv}, "", " ") - require.NoError(t, err) - - dir := t.TempDir() - path := filepath.Join(dir, "priv_validator_key.json") - require.NoError(t, os.WriteFile(path, raw, 0o600)) - - s, err := softsign.Load(path) - require.NoError(t, err) - pub, err := s.PubKey(context.Background()) - require.NoError(t, err) - require.True(t, pub.Equals(priv.PubKey())) -} - -func TestLoadRejectsMissingFile(t *testing.T) { - _, err := softsign.Load(filepath.Join(t.TempDir(), "nope")) - require.Error(t, err) -} diff --git a/kms/internal/config/config.go b/kms/internal/config/config.go deleted file mode 100644 index ad6f296dc0c..00000000000 --- a/kms/internal/config/config.go +++ /dev/null @@ -1,98 +0,0 @@ -// Package config defines the cometkms TOML configuration and its validation. -package config - -import ( - "fmt" - "strings" - - "github.com/BurntSushi/toml" - "github.com/cometbft/cometbft/privval" - "github.com/libp2p/go-libp2p/core/peer" -) - -// Config is the top-level cometkms configuration. -type Config struct { - Chains []Chain `toml:"chain"` - Validators []Validator `toml:"validator"` - Providers Providers `toml:"providers"` -} - -// Chain declares a chain and its double-sign state file. -type Chain struct { - ID string `toml:"id"` - StateFile string `toml:"state_file"` // optional; defaulted by Validate -} - -// Validator declares one outbound connection to a validator's listener. -type Validator struct { - ChainID string `toml:"chain_id"` - Addr string `toml:"addr"` // tcp://host:port - IdentityKey string `toml:"identity_key"` // ed25519 node-key file for SecretConnection - Reconnect *bool `toml:"reconnect"` // default true -} - -// Providers groups the configured key backends. -type Providers struct { - Softsign []SoftsignProvider `toml:"softsign"` - PKCS11 []PKCS11Provider `toml:"pkcs11"` -} - -// SoftsignProvider binds a softsign key file to one or more chains. -type SoftsignProvider struct { - ChainIDs []string `toml:"chain_ids"` - KeyFile string `toml:"key_file"` -} - -// PKCS11Provider binds a key on a PKCS#11 token/HSM to one or more chains. -// Exactly one of TokenLabel/Slot selects the token, at least one of -// KeyLabel/KeyID selects the key, and exactly one of PIN/PINEnv/PINFile supplies -// the user PIN. Algorithm defaults to "ed25519" when empty. -type PKCS11Provider struct { - ChainIDs []string `toml:"chain_ids"` - Module string `toml:"module"` // path to the PKCS#11 .so - TokenLabel string `toml:"token_label"` // CKA_LABEL of the token (XOR slot) - Slot *uint `toml:"slot"` // slot number (XOR token_label) - KeyLabel string `toml:"key_label"` // CKA_LABEL of the key - KeyID string `toml:"key_id"` // hex-encoded CKA_ID of the key - PIN string `toml:"pin"` // inline PIN - PINEnv string `toml:"pin_env"` // name of env var holding the PIN - PINFile string `toml:"pin_file"` // path to a file holding the PIN - Algorithm string `toml:"algorithm"` // key algorithm; defaults to "ed25519" -} - -// Load parses a TOML config file. -func Load(path string) (*Config, error) { - var c Config - if _, err := toml.DecodeFile(path, &c); err != nil { - return nil, fmt.Errorf("config: decode %q: %w", path, err) - } - return &c, nil -} - -// ReconnectEnabled reports the effective reconnect setting (default true). -func (v Validator) ReconnectEnabled() bool { return v.Reconnect == nil || *v.Reconnect } - -// Transport identifies the privval connection transport selected by a validator -// address scheme. -type Transport int - -const ( - // TransportTCP is tcp:// with cometbft SecretConnection (the default). - TransportTCP Transport = iota - // TransportNoise is noise://@host:port with libp2p Noise. - TransportNoise -) - -// ParsedTransport classifies v.Addr. For TCP it returns the full address -// unchanged (DialTCPFn consumes the tcp:// form) and an empty peer ID. For Noise -// it returns the host:port and the pinned validator peer ID. -func (v Validator) ParsedTransport() (tr Transport, addr string, validatorPeer peer.ID, err error) { - if strings.HasPrefix(v.Addr, "noise://") { - pid, hostport, perr := privval.ParseNoiseAddr(v.Addr) - if perr != nil { - return TransportNoise, "", "", perr - } - return TransportNoise, hostport, pid, nil - } - return TransportTCP, v.Addr, "", nil -} diff --git a/kms/internal/config/config_pkcs11_test.go b/kms/internal/config/config_pkcs11_test.go deleted file mode 100644 index 540acdc013c..00000000000 --- a/kms/internal/config/config_pkcs11_test.go +++ /dev/null @@ -1,175 +0,0 @@ -package config_test - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/config" -) - -// writeModule creates a stand-in PKCS#11 module file so readability checks pass. -func writeModule(t *testing.T, home string) string { - t.Helper() - p := filepath.Join(home, "libsofthsm2.so") - require.NoError(t, os.WriteFile(p, []byte("not-a-real-module"), 0o600)) - return p -} - -func pkcs11Cfg(module, extra string) string { - return ` -[[chain]] -id = "c1" - -[[validator]] -chain_id = "c1" -addr = "tcp://127.0.0.1:1" -identity_key = "i" - -[[providers.pkcs11]] -chain_ids = ["c1"] -module = "` + module + `" -` + extra -} - -func TestPKCS11Good(t *testing.T) { - home := t.TempDir() - mod := writeModule(t, home) - body := pkcs11Cfg(mod, `token_label = "comet" -key_label = "validator" -pin = "1234" -`) - cfgPath := filepath.Join(home, "cometkms.toml") - require.NoError(t, os.WriteFile(cfgPath, []byte(body), 0o600)) - - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) -} - -func TestPKCS11SatisfiesBackendRequirement(t *testing.T) { - // A chain whose only provider is pkcs11 must not trip the "no backend" check. - home := t.TempDir() - mod := writeModule(t, home) - body := pkcs11Cfg(mod, `token_label = "comet" -key_id = "01ab" -pin = "1234" -`) - cfgPath := filepath.Join(home, "cometkms.toml") - require.NoError(t, os.WriteFile(cfgPath, []byte(body), 0o600)) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) -} - -func validatePKCS11(t *testing.T, extra string) error { - t.Helper() - home := t.TempDir() - mod := writeModule(t, home) - body := pkcs11Cfg(mod, extra) - cfgPath := filepath.Join(home, "cometkms.toml") - require.NoError(t, os.WriteFile(cfgPath, []byte(body), 0o600)) - c, err := config.Load(cfgPath) - require.NoError(t, err) - return c.Validate(home) -} - -func TestPKCS11TokenAndSlotMutuallyExclusive(t *testing.T) { - err := validatePKCS11(t, `token_label = "comet" -slot = 0 -key_label = "validator" -pin = "1234" -`) - require.ErrorContains(t, err, "token_label") -} - -func TestPKCS11RequiresTokenOrSlot(t *testing.T) { - err := validatePKCS11(t, `key_label = "validator" -pin = "1234" -`) - require.ErrorContains(t, err, "token_label") -} - -func TestPKCS11RequiresKeyLabelOrID(t *testing.T) { - err := validatePKCS11(t, `token_label = "comet" -pin = "1234" -`) - require.ErrorContains(t, err, "key_label") -} - -func TestPKCS11BadKeyIDHex(t *testing.T) { - err := validatePKCS11(t, `token_label = "comet" -key_id = "zz" -pin = "1234" -`) - require.ErrorContains(t, err, "key_id") -} - -func TestPKCS11MultiplePINSources(t *testing.T) { - err := validatePKCS11(t, `token_label = "comet" -key_label = "validator" -pin = "1234" -pin_env = "X" -`) - require.ErrorContains(t, err, "pin") -} - -func TestPKCS11NoPINSource(t *testing.T) { - err := validatePKCS11(t, `token_label = "comet" -key_label = "validator" -`) - require.ErrorContains(t, err, "pin") -} - -func TestPKCS11UnknownAlgorithm(t *testing.T) { - err := validatePKCS11(t, `token_label = "comet" -key_label = "validator" -pin = "1234" -algorithm = "rsa-9000" -`) - require.ErrorContains(t, err, "algorithm") -} - -func TestPKCS11MissingModule(t *testing.T) { - home := t.TempDir() - body := pkcs11Cfg(filepath.Join(home, "does-not-exist.so"), `token_label = "comet" -key_label = "validator" -pin = "1234" -`) - cfgPath := filepath.Join(home, "cometkms.toml") - require.NoError(t, os.WriteFile(cfgPath, []byte(body), 0o600)) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.ErrorContains(t, c.Validate(home), "module") -} - -func TestPKCS11RelativePathsResolvedAgainstHome(t *testing.T) { - home := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(home, "mod.so"), []byte("x"), 0o600)) - require.NoError(t, os.WriteFile(filepath.Join(home, "pin.txt"), []byte("1234"), 0o600)) - body := ` -[[chain]] -id = "c1" - -[[validator]] -chain_id = "c1" -addr = "tcp://127.0.0.1:1" -identity_key = "i" - -[[providers.pkcs11]] -chain_ids = ["c1"] -module = "mod.so" -token_label = "comet" -key_label = "validator" -pin_file = "pin.txt" -` - cfgPath := filepath.Join(home, "cometkms.toml") - require.NoError(t, os.WriteFile(cfgPath, []byte(body), 0o600)) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) - require.Equal(t, filepath.Join(home, "mod.so"), c.Providers.PKCS11[0].Module) - require.Equal(t, filepath.Join(home, "pin.txt"), c.Providers.PKCS11[0].PINFile) -} diff --git a/kms/internal/config/config_test.go b/kms/internal/config/config_test.go deleted file mode 100644 index d818dc07b99..00000000000 --- a/kms/internal/config/config_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package config_test - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/cometbft/cometbft/kms/internal/config" - "github.com/cometbft/cometbft/lp2p" -) - -const good = ` -[[chain]] -id = "cosmoshub-4" -state_file = "STATE" - -[[validator]] -chain_id = "cosmoshub-4" -addr = "tcp://127.0.0.1:26659" -identity_key = "IDENT" - -[[providers.softsign]] -chain_ids = ["cosmoshub-4"] -key_file = "KEY" -` - -func writeCfg(t *testing.T, body string) (cfgPath, home string) { - t.Helper() - home = t.TempDir() - cfgPath = filepath.Join(home, "cometkms.toml") - require.NoError(t, os.WriteFile(cfgPath, []byte(body), 0o600)) - return cfgPath, home -} - -func TestLoadAndValidateGood(t *testing.T) { - cfgPath, home := writeCfg(t, good) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) - - require.Len(t, c.Chains, 1) - require.NotEmpty(t, c.Chains[0].StateFile) - _, statErr := os.Stat(filepath.Dir(c.Chains[0].StateFile)) - require.NoError(t, statErr) -} - -func TestStateFileDefaultsWhenOmitted(t *testing.T) { - body := ` -[[chain]] -id = "c1" - -[[validator]] -chain_id = "c1" -addr = "tcp://127.0.0.1:1" -identity_key = "i" - -[[providers.softsign]] -chain_ids = ["c1"] -key_file = "k" -` - cfgPath, home := writeCfg(t, body) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) - require.Equal(t, filepath.Join(home, "state", "c1.json"), c.Chains[0].StateFile) -} - -func TestRelativeKeyPathsResolvedAgainstHome(t *testing.T) { - body := ` -[[chain]] -id = "c1" - -[[validator]] -chain_id = "c1" -addr = "tcp://127.0.0.1:1" -identity_key = "identity.json" - -[[providers.softsign]] -chain_ids = ["c1"] -key_file = "key.json" -` - cfgPath, home := writeCfg(t, body) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) - - require.Equal(t, filepath.Join(home, "identity.json"), c.Validators[0].IdentityKey) - require.Equal(t, filepath.Join(home, "key.json"), c.Providers.Softsign[0].KeyFile) -} - -func TestAbsoluteKeyPathsLeftUnchanged(t *testing.T) { - absIdent := filepath.Join(t.TempDir(), "abs-identity.json") - absKey := filepath.Join(t.TempDir(), "abs-key.json") - body := ` -[[chain]] -id = "c1" - -[[validator]] -chain_id = "c1" -addr = "tcp://127.0.0.1:1" -identity_key = "` + absIdent + `" - -[[providers.softsign]] -chain_ids = ["c1"] -key_file = "` + absKey + `" -` - cfgPath, home := writeCfg(t, body) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.NoError(t, c.Validate(home)) - - require.Equal(t, absIdent, c.Validators[0].IdentityKey) - require.Equal(t, absKey, c.Providers.Softsign[0].KeyFile) -} - -func TestValidatorReferencesUnknownChain(t *testing.T) { - body := ` -[[chain]] -id = "c1" -[[validator]] -chain_id = "nope" -addr = "tcp://127.0.0.1:1" -identity_key = "i" -[[providers.softsign]] -chain_ids = ["c1"] -key_file = "k" -` - cfgPath, home := writeCfg(t, body) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.ErrorContains(t, c.Validate(home), "unknown chain") -} - -func TestChainWithoutBackendRejected(t *testing.T) { - body := ` -[[chain]] -id = "c1" -[[validator]] -chain_id = "c1" -addr = "tcp://127.0.0.1:1" -identity_key = "i" -` - cfgPath, home := writeCfg(t, body) - c, err := config.Load(cfgPath) - require.NoError(t, err) - require.ErrorContains(t, c.Validate(home), "no backend") -} - -func TestValidatorTransportTCPDefault(t *testing.T) { - v := config.Validator{ChainID: "c1", Addr: "tcp://1.2.3.4:26659", IdentityKey: "i"} - tr, addr, pid, err := v.ParsedTransport() - require.NoError(t, err) - require.Equal(t, config.TransportTCP, tr) - require.Equal(t, "tcp://1.2.3.4:26659", addr) // tcp keeps full addr for DialTCPFn - require.Empty(t, pid) -} - -func TestValidatorTransportNoise(t *testing.T) { - validatorPeer, err := lp2p.IDFromPrivateKey(ed25519.GenPrivKey()) - require.NoError(t, err) - - v := config.Validator{ - ChainID: "c1", - Addr: "noise://" + validatorPeer.String() + "@1.2.3.4:26659", - IdentityKey: "i", - } - tr, addr, pid, err := v.ParsedTransport() - require.NoError(t, err) - require.Equal(t, config.TransportNoise, tr) - require.Equal(t, "1.2.3.4:26659", addr) - require.Equal(t, validatorPeer, pid) -} - -func TestValidatorTransportNoiseInvalid(t *testing.T) { - v := config.Validator{ChainID: "c1", Addr: "noise://1.2.3.4:26659", IdentityKey: "i"} // missing peer id - _, _, _, err := v.ParsedTransport() - require.Error(t, err) -} diff --git a/kms/internal/config/validate.go b/kms/internal/config/validate.go deleted file mode 100644 index cd159522a48..00000000000 --- a/kms/internal/config/validate.go +++ /dev/null @@ -1,185 +0,0 @@ -package config - -import ( - "encoding/hex" - "fmt" - "os" - "path/filepath" -) - -// supportedPKCS11Algorithms mirrors the algo registry in -// internal/backend/pkcs11. It is duplicated here so config validation does not -// have to import the cgo-backed pkcs11 package. Keep the two in sync when adding -// a key type. -var supportedPKCS11Algorithms = map[string]bool{"ed25519": true} - -// Validate resolves defaults and enforces fail-fast invariants. home is the base -// directory used to resolve relative paths and default state files. -func (c *Config) Validate(home string) error { - if len(c.Chains) == 0 { - return fmt.Errorf("config: no [[chain]] declared") - } - - chainIDs := make(map[string]int, len(c.Chains)) // id -> index - for i, ch := range c.Chains { - if ch.ID == "" { - return fmt.Errorf("config: [[chain]] #%d has empty id", i) - } - if _, dup := chainIDs[ch.ID]; dup { - return fmt.Errorf("config: duplicate chain id %q", ch.ID) - } - chainIDs[ch.ID] = i - } - - // Resolve + ensure writable state files. - for i := range c.Chains { - sf := c.Chains[i].StateFile - if sf == "" { - sf = filepath.Join(home, "state", c.Chains[i].ID+".json") - } else if !filepath.IsAbs(sf) { - sf = filepath.Join(home, sf) - } - if err := os.MkdirAll(filepath.Dir(sf), 0o700); err != nil { - return fmt.Errorf("config: state dir for chain %q: %w", c.Chains[i].ID, err) - } - if err := checkWritable(filepath.Dir(sf)); err != nil { - return fmt.Errorf("config: state file for chain %q not writable: %w", c.Chains[i].ID, err) - } - c.Chains[i].StateFile = sf - } - - // Every validator references a known chain. - for i := range c.Validators { - v := c.Validators[i] - if _, ok := chainIDs[v.ChainID]; !ok { - return fmt.Errorf("config: validator references unknown chain %q", v.ChainID) - } - if v.Addr == "" { - return fmt.Errorf("config: validator for chain %q has empty addr", v.ChainID) - } - if v.IdentityKey == "" { - return fmt.Errorf("config: validator for chain %q has empty identity_key", v.ChainID) - } - if _, _, _, perr := v.ParsedTransport(); perr != nil { - return fmt.Errorf("config: validator for chain %q has invalid addr: %w", v.ChainID, perr) - } - // Resolve relative identity_key against home so app.Build consumes the - // resolved path (CWD-relative resolution would silently mint a new key). - if !filepath.IsAbs(v.IdentityKey) { - c.Validators[i].IdentityKey = filepath.Join(home, v.IdentityKey) - } - } - - // Every provider references a known chain; collect which chains have a backend. - hasBackend := make(map[string]bool) - for i := range c.Providers.Softsign { - p := c.Providers.Softsign[i] - if p.KeyFile == "" { - return fmt.Errorf("config: softsign provider has empty key_file") - } - if len(p.ChainIDs) == 0 { - return fmt.Errorf("config: softsign provider with key_file %q has no chain_ids", p.KeyFile) - } - for _, id := range p.ChainIDs { - if _, ok := chainIDs[id]; !ok { - return fmt.Errorf("config: softsign provider references unknown chain %q", id) - } - hasBackend[id] = true - } - // Resolve relative key_file against home. - if !filepath.IsAbs(p.KeyFile) { - c.Providers.Softsign[i].KeyFile = filepath.Join(home, p.KeyFile) - } - } - - for i := range c.Providers.PKCS11 { - if err := c.validatePKCS11Provider(i, home, chainIDs, hasBackend); err != nil { - return err - } - } - - // Every chain must have at least one backend. - for _, ch := range c.Chains { - if !hasBackend[ch.ID] { - return fmt.Errorf("config: chain %q has no backend", ch.ID) - } - } - return nil -} - -// validatePKCS11Provider checks one [[providers.pkcs11]] block, resolves its -// relative paths against home, and records which chains it backs. -func (c *Config) validatePKCS11Provider(i int, home string, chainIDs map[string]int, hasBackend map[string]bool) error { - p := c.Providers.PKCS11[i] - - if p.Module == "" { - return fmt.Errorf("config: pkcs11 provider has empty module") - } - if len(p.ChainIDs) == 0 { - return fmt.Errorf("config: pkcs11 provider with module %q has no chain_ids", p.Module) - } - - // Token selector: exactly one of token_label / slot. - switch { - case p.TokenLabel != "" && p.Slot != nil: - return fmt.Errorf("config: pkcs11 provider sets both token_label and slot (use exactly one)") - case p.TokenLabel == "" && p.Slot == nil: - return fmt.Errorf("config: pkcs11 provider must set token_label or slot") - } - - // Key selector: at least one of key_label / key_id. - if p.KeyLabel == "" && p.KeyID == "" { - return fmt.Errorf("config: pkcs11 provider must set key_label or key_id") - } - if p.KeyID != "" { - if _, err := hex.DecodeString(p.KeyID); err != nil { - return fmt.Errorf("config: pkcs11 provider key_id %q is not valid hex: %w", p.KeyID, err) - } - } - - // PIN source: exactly one of pin / pin_env / pin_file. - n := 0 - for _, set := range []bool{p.PIN != "", p.PINEnv != "", p.PINFile != ""} { - if set { - n++ - } - } - if n != 1 { - return fmt.Errorf("config: pkcs11 provider must set exactly one of pin, pin_env, pin_file (got %d)", n) - } - - // Algorithm: empty defaults to ed25519; otherwise must be registered. - if p.Algorithm != "" && !supportedPKCS11Algorithms[p.Algorithm] { - return fmt.Errorf("config: pkcs11 provider has unknown algorithm %q", p.Algorithm) - } - - // Resolve relative paths against home before checking the module is readable. - if !filepath.IsAbs(p.Module) { - p.Module = filepath.Join(home, p.Module) - } - if p.PINFile != "" && !filepath.IsAbs(p.PINFile) { - p.PINFile = filepath.Join(home, p.PINFile) - } - if _, err := os.Stat(p.Module); err != nil { - return fmt.Errorf("config: pkcs11 provider module %q not readable: %w", p.Module, err) - } - c.Providers.PKCS11[i] = p - - for _, id := range p.ChainIDs { - if _, ok := chainIDs[id]; !ok { - return fmt.Errorf("config: pkcs11 provider references unknown chain %q", id) - } - hasBackend[id] = true - } - return nil -} - -func checkWritable(dir string) error { - f, err := os.CreateTemp(dir, ".writecheck-*") - if err != nil { - return err - } - name := f.Name() - _ = f.Close() - return os.Remove(name) -} diff --git a/kms/internal/identity/identity.go b/kms/internal/identity/identity.go deleted file mode 100644 index da757f48f50..00000000000 --- a/kms/internal/identity/identity.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package identity manages the Ed25519 key that authenticates the SecretConnection -// to a validator. It reuses CometBFT's node-key format and storage. -package identity - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/p2p" -) - -// LoadOrGen loads the identity key at path, generating and persisting one if it -// does not exist. -func LoadOrGen(path string) (crypto.PrivKey, error) { - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - return nil, fmt.Errorf("identity: mkdir: %w", err) - } - nk, err := p2p.LoadOrGenNodeKey(path) - if err != nil { - return nil, fmt.Errorf("identity: load/gen node key %q: %w", path, err) - } - return nk.PrivKey, nil -} diff --git a/kms/internal/identity/identity_test.go b/kms/internal/identity/identity_test.go deleted file mode 100644 index 1f2a1e88758..00000000000 --- a/kms/internal/identity/identity_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package identity_test - -import ( - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/identity" -) - -func TestLoadOrGenIsStable(t *testing.T) { - path := filepath.Join(t.TempDir(), "identity.json") - - k1, err := identity.LoadOrGen(path) - require.NoError(t, err) - require.NotNil(t, k1) - - k2, err := identity.LoadOrGen(path) - require.NoError(t, err) - require.True(t, k1.PubKey().Equals(k2.PubKey()), "reload must yield same key") -} diff --git a/kms/internal/manager/dialer.go b/kms/internal/manager/dialer.go deleted file mode 100644 index 9e0af7f0514..00000000000 --- a/kms/internal/manager/dialer.go +++ /dev/null @@ -1,45 +0,0 @@ -package manager - -import ( - "errors" - "net" - "time" - - "github.com/cometbft/cometbft/libs/log" - "github.com/cometbft/cometbft/privval" -) - -// errDialerStopped is returned by a backoff dialer after its stop channel closes. -var errDialerStopped = errors.New("cometkms: dialer stopped") - -// backoffDialer wraps a one-shot SocketDialer so it blocks, retrying with capped -// exponential backoff, until it obtains a connection or stop is closed. Because -// it blocks until success, privval's serviceLoop never exits on transient -// outages — giving cometkms full control over reconnect cadence. -func backoffDialer(base privval.SocketDialer, stop <-chan struct{}, logger log.Logger, initial, max time.Duration) privval.SocketDialer { - return func() (net.Conn, error) { - wait := initial - for { - select { - case <-stop: - return nil, errDialerStopped - default: - } - - conn, err := base() - if err == nil { - return conn, nil - } - logger.Error("cometkms: dial failed; backing off", "wait", wait, "err", err) - - select { - case <-stop: - return nil, errDialerStopped - case <-time.After(wait): - } - if wait *= 2; wait > max { - wait = max - } - } - } -} diff --git a/kms/internal/manager/dialer_test.go b/kms/internal/manager/dialer_test.go deleted file mode 100644 index 99ad36345a5..00000000000 --- a/kms/internal/manager/dialer_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package manager - -import ( - "errors" - "net" - "sync/atomic" - "testing" - "time" - - "github.com/cometbft/cometbft/libs/log" - "github.com/stretchr/testify/require" -) - -func TestBackoffDialerRetriesThenSucceeds(t *testing.T) { - var calls int32 - base := func() (net.Conn, error) { - if atomic.AddInt32(&calls, 1) < 3 { - return nil, errors.New("refused") - } - c, _ := net.Pipe() - return c, nil - } - - stop := make(chan struct{}) - d := backoffDialer(base, stop, log.TestingLogger(), time.Millisecond, 5*time.Millisecond) - - conn, err := d() - require.NoError(t, err) - require.NotNil(t, conn) - require.GreaterOrEqual(t, atomic.LoadInt32(&calls), int32(3)) -} - -func TestBackoffDialerUnblocksOnStop(t *testing.T) { - base := func() (net.Conn, error) { return nil, errors.New("always fails") } - stop := make(chan struct{}) - d := backoffDialer(base, stop, log.TestingLogger(), time.Millisecond, 5*time.Millisecond) - - done := make(chan struct{}) - go func() { - _, err := d() - require.Error(t, err) - close(done) - }() - - time.Sleep(10 * time.Millisecond) - close(stop) - - select { - case <-done: - case <-time.After(time.Second): - t.Fatal("dialer did not unblock on stop") - } -} diff --git a/kms/internal/manager/manager.go b/kms/internal/manager/manager.go deleted file mode 100644 index d2a66e5eb44..00000000000 --- a/kms/internal/manager/manager.go +++ /dev/null @@ -1,205 +0,0 @@ -package manager - -import ( - "net" - "sync" - "time" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/libs/log" - "github.com/cometbft/cometbft/libs/protoio" - "github.com/cometbft/cometbft/privval" - privvalproto "github.com/cometbft/cometbft/proto/tendermint/privval" - "github.com/cometbft/cometbft/types" -) - -const ( - defaultDialTimeout = 5 * time.Second - defaultReadTimeout = 10 * time.Second // must exceed the validator's ping interval (~3.3s at default) - defaultWriteTimeout = 5 * time.Second - defaultBackoffInitial = 200 * time.Millisecond - defaultBackoffMax = 10 * time.Second - - // maxRemoteSignerMsgSize mirrors privval's framing cap (10 KiB). - maxRemoteSignerMsgSize = 1024 * 10 -) - -// DefaultDialTimeout is the per-dial timeout used for validator connections. -const DefaultDialTimeout = defaultDialTimeout - -// ValidatorConn describes one outbound signer connection. -type ValidatorConn struct { - ChainID string - Addr string - IdentityKey crypto.PrivKey - Signer types.PrivValidator - // Reconnect controls whether the manager re-dials after an established - // connection drops. The initial connect always uses backoff regardless. - Reconnect bool - // Dialer is the base (already transport-specific) SocketDialer. If nil, the - // manager builds the default tcp:// SecretConnection dialer from Addr. - Dialer privval.SocketDialer -} - -// Manager supervises one validator connection per ValidatorConn, dialing out and -// re-dialing with backoff across outages (including graceful validator restarts). -type Manager struct { - logger log.Logger - conns []ValidatorConn - stop chan struct{} - stopOnce sync.Once - wg sync.WaitGroup -} - -// New builds a Manager. -func New(logger log.Logger, conns []ValidatorConn) *Manager { - return &Manager{logger: logger, conns: conns, stop: make(chan struct{})} -} - -// Start launches one supervised goroutine per connection. It never returns an -// error (kept in the signature for API stability and future validation). -func (m *Manager) Start() error { - for _, c := range m.conns { - m.wg.Add(1) - go m.run(c) - } - return nil -} - -// Stop signals all connections to close and waits for their goroutines to exit. -// Safe to call multiple times. -func (m *Manager) Stop() { - m.stopOnce.Do(func() { close(m.stop) }) - m.wg.Wait() -} - -// run maintains one validator connection: dial (blocking, with backoff), serve -// until the connection breaks (EOF or timeout), then redial — until Stop. -func (m *Manager) run(c ValidatorConn) { - defer m.wg.Done() - - logger := m.logger.With("chain", c.ChainID, "addr", c.Addr) - base := c.Dialer - if base == nil { - base = privval.DialTCPFn(c.Addr, defaultDialTimeout, c.IdentityKey) - } - dialer := backoffDialer(base, m.stop, logger, defaultBackoffInitial, defaultBackoffMax) - - for { - select { - case <-m.stop: - return - default: - } - - conn, err := dialer() - if err != nil { - return // errDialerStopped: shutting down - } - logger.Info("cometkms: connected") - - m.handleConnection(conn, c, logger) - - if !c.Reconnect { - logger.Info("cometkms: reconnect disabled; connection closed") - return - } - logger.Info("cometkms: connection closed; will redial") - } -} - -// handleConnection serves a single established connection until it breaks or the -// manager stops. It owns the connection lifecycle: a monitor goroutine closes the -// conn on shutdown (unblocking a pending read), and deferred cleanup guarantees -// both the conn and the monitor goroutine are released when this returns. Scoping -// this to its own function (rather than deferring inside run's loop) keeps the -// defers per-connection and ensures they still run if serveConn panics mid-unwind. -func (m *Manager) handleConnection(conn net.Conn, c ValidatorConn, logger log.Logger) { - closed := make(chan struct{}) - defer close(closed) - defer func() { _ = conn.Close() }() - - go func() { - select { - case <-m.stop: - _ = conn.Close() - case <-closed: - } - }() - - m.serveConn(conn, c.ChainID, c.Signer, logger) -} - -// serveConn reads validation requests off conn and answers them with the reused -// privval.DefaultValidationRequestHandler, until any read/write error (EOF on a -// graceful restart, or a read-deadline timeout on a silently dead peer) or until -// stop. Returning signals the caller to redial. -func (m *Manager) serveConn(conn net.Conn, chainID string, signer types.PrivValidator, logger log.Logger) { - reader := protoio.NewDelimitedReader(conn, maxRemoteSignerMsgSize) - writer := protoio.NewDelimitedWriter(conn) - - for { - select { - case <-m.stop: - return - default: - } - - if err := conn.SetReadDeadline(time.Now().Add(defaultReadTimeout)); err != nil { - logger.Error("cometkms: set read deadline", "err", err) - return - } - var req privvalproto.Message - if _, err := reader.ReadMsg(&req); err != nil { - logger.Info("cometkms: read failed; dropping connection", "err", err) - return - } - - resp, err := privval.DefaultValidationRequestHandler(signer, req, chainID) - kind, kv, ping := describeRequest(req) - switch { - case err != nil: - // resp already carries an embedded RemoteSignerError; log loudly and still send it. - logger.Error("cometkms: "+kind+" request rejected", append(kv, "err", err)...) - case ping: - logger.Debug("cometkms: ping") - case kind == "pubkey": - logger.Info("cometkms: served pubkey request") - default: - logger.Info("cometkms: signed "+kind, kv...) - } - - if err := conn.SetWriteDeadline(time.Now().Add(defaultWriteTimeout)); err != nil { - logger.Error("cometkms: set write deadline", "err", err) - return - } - if _, err := writer.WriteMsg(&resp); err != nil { - logger.Error("cometkms: write failed; dropping connection", "err", err) - return - } - } -} - -// describeRequest returns a short kind, structured log keyvals, and whether the -// request is a ping (which is logged at debug to avoid flooding the log every -// few seconds). It is used to emit one log line per served request. -func describeRequest(req privvalproto.Message) (kind string, kv []any, ping bool) { - switch r := req.Sum.(type) { - case *privvalproto.Message_SignVoteRequest: - if v := r.SignVoteRequest.GetVote(); v != nil { - return "vote", []any{"height", v.Height, "round", v.Round, "type", v.Type}, false - } - return "vote", nil, false - case *privvalproto.Message_SignProposalRequest: - if p := r.SignProposalRequest.GetProposal(); p != nil { - return "proposal", []any{"height", p.Height, "round", p.Round}, false - } - return "proposal", nil, false - case *privvalproto.Message_PubKeyRequest: - return "pubkey", nil, false - case *privvalproto.Message_PingRequest: - return "ping", nil, true - default: - return "unknown", nil, false - } -} diff --git a/kms/internal/signer/chain_signer.go b/kms/internal/signer/chain_signer.go deleted file mode 100644 index 2a23800f734..00000000000 --- a/kms/internal/signer/chain_signer.go +++ /dev/null @@ -1,94 +0,0 @@ -package signer - -import ( - "context" - "fmt" - "os" - "sync" - - "github.com/cometbft/cometbft/crypto" - cmtjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/privval" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cometbft/cometbft/types" - - "github.com/cometbft/cometbft/kms/internal/backend" -) - -// ChainSigner signs consensus messages for one chain, enforcing double-sign -// protection by delegating to a CometBFT *privval.FilePV (identical guard + -// crash-recovery logic). It is safe for concurrent use across multiple validator -// connections for the same chain. -type ChainSigner struct { - chainID string - mu sync.Mutex - fpv *privval.FilePV -} - -var _ types.PrivValidator = (*ChainSigner)(nil) - -// NewChainSigner builds the signer. The backend is wrapped as a crypto.PrivKey -// and handed to privval.NewFilePV; any pre-existing sign-state at stateFile is -// reloaded so double-sign protection survives restarts. The directory containing -// stateFile must already exist (config validation guarantees this). -func NewChainSigner(chainID string, be backend.Signer, stateFile string) (*ChainSigner, error) { - adapter, err := newBackendPrivKey(context.Background(), be) - if err != nil { - return nil, fmt.Errorf("chain %q: load pubkey: %w", chainID, err) - } - - fpv := privval.NewFilePV(adapter, "", stateFile) - - if err := reloadState(fpv, stateFile); err != nil { - return nil, fmt.Errorf("chain %q: reload sign-state: %w", chainID, err) - } - - return &ChainSigner{chainID: chainID, fpv: fpv}, nil -} - -// reloadState loads persisted FilePVLastSignState JSON into fpv.LastSignState, -// preserving the private filePath set by NewFilePV (JSON has no such field). -func reloadState(fpv *privval.FilePV, stateFile string) error { - raw, err := os.ReadFile(stateFile) - if os.IsNotExist(err) { - return nil // fresh start; FilePV begins at height 0 - } - if err != nil { - return err - } - if len(raw) == 0 { - return nil - } - return cmtjson.Unmarshal(raw, &fpv.LastSignState) -} - -// GetPubKey implements types.PrivValidator. -func (c *ChainSigner) GetPubKey() (crypto.PubKey, error) { - c.mu.Lock() - defer c.mu.Unlock() - return c.fpv.GetPubKey() -} - -// SignVote implements types.PrivValidator. -func (c *ChainSigner) SignVote(chainID string, vote *cmtproto.Vote) (err error) { - c.mu.Lock() - defer c.mu.Unlock() - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("chain %q: sign vote failed (state persistence): %v", c.chainID, r) - } - }() - return c.fpv.SignVote(chainID, vote) -} - -// SignProposal implements types.PrivValidator. -func (c *ChainSigner) SignProposal(chainID string, proposal *cmtproto.Proposal) (err error) { - c.mu.Lock() - defer c.mu.Unlock() - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("chain %q: sign proposal failed (state persistence): %v", c.chainID, r) - } - }() - return c.fpv.SignProposal(chainID, proposal) -} diff --git a/kms/internal/signer/chain_signer_test.go b/kms/internal/signer/chain_signer_test.go deleted file mode 100644 index 1aa47223b77..00000000000 --- a/kms/internal/signer/chain_signer_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package signer_test - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cometbft/cometbft/types" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/signer" -) - -type memBackend struct{ priv crypto.PrivKey } - -func (m memBackend) PubKey(context.Context) (crypto.PubKey, error) { return m.priv.PubKey(), nil } -func (m memBackend) Sign(_ context.Context, b []byte) ([]byte, error) { return m.priv.Sign(b) } - -const chainID = "test-chain-1" - -func newSigner(t *testing.T) (*signer.ChainSigner, crypto.PubKey, string) { - t.Helper() - priv := ed25519.GenPrivKey() - state := filepath.Join(t.TempDir(), "state.json") - cs, err := signer.NewChainSigner(chainID, memBackend{priv: priv}, state) - require.NoError(t, err) - return cs, priv.PubKey(), state -} - -func precommit(h int64, r int32) *cmtproto.Vote { - return &cmtproto.Vote{Type: cmtproto.PrecommitType, Height: h, Round: r} -} - -func TestSignVoteProducesVerifiableSignature(t *testing.T) { - cs, pub, _ := newSigner(t) - v := precommit(10, 0) - require.NoError(t, cs.SignVote(chainID, v)) - require.True(t, pub.VerifySignature(types.VoteSignBytes(chainID, v), v.Signature)) -} - -func TestDoubleSignRegressionRejected(t *testing.T) { - cs, _, _ := newSigner(t) - require.NoError(t, cs.SignVote(chainID, precommit(10, 0))) - err := cs.SignVote(chainID, precommit(9, 0)) - require.Error(t, err) -} - -func TestStatePersistsAcrossReload(t *testing.T) { - priv := ed25519.GenPrivKey() - state := filepath.Join(t.TempDir(), "state.json") - - cs1, err := signer.NewChainSigner(chainID, memBackend{priv: priv}, state) - require.NoError(t, err) - require.NoError(t, cs1.SignVote(chainID, precommit(100, 0))) - - cs2, err := signer.NewChainSigner(chainID, memBackend{priv: priv}, state) - require.NoError(t, err) - require.Error(t, cs2.SignVote(chainID, precommit(50, 0))) - - _, statErr := os.Stat(state) - require.NoError(t, statErr) -} - -func TestVoteExtensionSignedForNonNilPrecommit(t *testing.T) { - cs, pub, _ := newSigner(t) - v := &cmtproto.Vote{ - Type: cmtproto.PrecommitType, - Height: 10, - Round: 0, - BlockID: cmtproto.BlockID{Hash: []byte("01234567890123456789012345678901")}, - } - require.NoError(t, cs.SignVote(chainID, v)) - require.NotEmpty(t, v.ExtensionSignature) - require.True(t, pub.VerifySignature(types.VoteExtensionSignBytes(chainID, v), v.ExtensionSignature)) -} - -func TestConflictingBlockSameHRSRejected(t *testing.T) { - cs, _, _ := newSigner(t) - - blockA := &cmtproto.Vote{ - Type: cmtproto.PrecommitType, - Height: 10, - Round: 0, - BlockID: cmtproto.BlockID{Hash: []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")}, - } - require.NoError(t, cs.SignVote(chainID, blockA)) - - // Same H/R/step, DIFFERENT block -> must be refused (conflicting data). - blockB := &cmtproto.Vote{ - Type: cmtproto.PrecommitType, - Height: 10, - Round: 0, - BlockID: cmtproto.BlockID{Hash: []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")}, - } - require.Error(t, cs.SignVote(chainID, blockB)) -} - -func TestSignProposalVerifiableAndRegressionRejected(t *testing.T) { - cs, pub, _ := newSigner(t) - - prop := &cmtproto.Proposal{Type: cmtproto.ProposalType, Height: 20, Round: 0} - require.NoError(t, cs.SignProposal(chainID, prop)) - require.True(t, pub.VerifySignature(types.ProposalSignBytes(chainID, prop), prop.Signature)) - - // Lower height must be refused. - lower := &cmtproto.Proposal{Type: cmtproto.ProposalType, Height: 19, Round: 0} - require.Error(t, cs.SignProposal(chainID, lower)) -} - -func TestStateSaveFailureReturnsErrorNotPanic(t *testing.T) { - priv := ed25519.GenPrivKey() - dir := t.TempDir() - statePath := filepath.Join(dir, "state.json") - - cs, err := signer.NewChainSigner(chainID, memBackend{priv: priv}, statePath) - require.NoError(t, err) - - // Sabotage persistence: remove any state file and create a DIRECTORY at the - // state path, so the atomic write (temp file + rename onto statePath) fails. - _ = os.Remove(statePath) - require.NoError(t, os.Mkdir(statePath, 0o755)) - - // SignVote must return an error (recovered panic), and must NOT panic the test. - err = cs.SignVote(chainID, precommit(30, 0)) - require.Error(t, err) -} diff --git a/kms/internal/signer/privkey_adapter.go b/kms/internal/signer/privkey_adapter.go deleted file mode 100644 index 6aa967514aa..00000000000 --- a/kms/internal/signer/privkey_adapter.go +++ /dev/null @@ -1,44 +0,0 @@ -package signer - -import ( - "context" - - "github.com/cometbft/cometbft/crypto" - - "github.com/cometbft/cometbft/kms/internal/backend" -) - -// backendPrivKey adapts a backend.Signer to crypto.PrivKey so it can be handed -// to privval.NewFilePV. Only Sign, PubKey, and Type are exercised by the FilePV -// signing path; Bytes and Equals are intentionally unsupported for remote keys. -type backendPrivKey struct { - ctx context.Context - be backend.Signer - pub crypto.PubKey -} - -var _ crypto.PrivKey = (*backendPrivKey)(nil) - -// newBackendPrivKey caches the public key (so PubKey is cheap and FilePV's -// address computation works) and returns the adapter. -func newBackendPrivKey(ctx context.Context, be backend.Signer) (crypto.PrivKey, error) { - pub, err := be.PubKey(ctx) - if err != nil { - return nil, err - } - return &backendPrivKey{ctx: ctx, be: be, pub: pub}, nil -} - -func (k *backendPrivKey) Sign(msg []byte) ([]byte, error) { return k.be.Sign(k.ctx, msg) } -func (k *backendPrivKey) PubKey() crypto.PubKey { return k.pub } -func (k *backendPrivKey) Type() string { return k.pub.Type() } - -// Bytes is unsupported: remote/HSM keys never expose private material. It returns -// nil rather than panicking because crypto.PrivKey requires the method; it is not -// called on the FilePV signing path. -func (k *backendPrivKey) Bytes() []byte { return nil } - -// Equals compares by public key (private material is unavailable). -func (k *backendPrivKey) Equals(other crypto.PrivKey) bool { - return k.pub.Equals(other.PubKey()) -} diff --git a/kms/internal/signer/privkey_adapter_test.go b/kms/internal/signer/privkey_adapter_test.go deleted file mode 100644 index 44aea760efb..00000000000 --- a/kms/internal/signer/privkey_adapter_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package signer - -import ( - "context" - "testing" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/stretchr/testify/require" -) - -// stubBackend is a minimal backend.Signer for tests. -type stubBackend struct{ priv crypto.PrivKey } - -func (s stubBackend) PubKey(context.Context) (crypto.PubKey, error) { return s.priv.PubKey(), nil } -func (s stubBackend) Sign(_ context.Context, b []byte) ([]byte, error) { return s.priv.Sign(b) } - -func TestAdapterSatisfiesPrivKeyAndSigns(t *testing.T) { - priv := ed25519.GenPrivKey() - be := stubBackend{priv: priv} - - pk, err := newBackendPrivKey(context.Background(), be) - require.NoError(t, err) - - // The compile-time interface assertion lives in privkey_adapter.go - // (var _ crypto.PrivKey = (*backendPrivKey)(nil)); here we exercise behavior. - require.True(t, pk.PubKey().Equals(priv.PubKey())) - require.Equal(t, "ed25519", pk.Type()) - - msg := []byte("hello") - sig, err := pk.Sign(msg) - require.NoError(t, err) - require.True(t, pk.PubKey().VerifySignature(msg, sig)) -} diff --git a/kms/internal/transport/noise.go b/kms/internal/transport/noise.go deleted file mode 100644 index fc23cf1951e..00000000000 --- a/kms/internal/transport/noise.go +++ /dev/null @@ -1,44 +0,0 @@ -// Package transport provides privval connection transports for cometkms. -package transport - -import ( - "context" - "fmt" - "net" - "time" - - cmtcrypto "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/lp2p" - "github.com/cometbft/cometbft/privval" - "github.com/libp2p/go-libp2p/core/peer" - libp2pnoise "github.com/libp2p/go-libp2p/p2p/security/noise" -) - -// NoiseDialer returns a privval.SocketDialer that TCP-dials addr (host:port) and -// secures the connection with libp2p Noise, using identity as the KMS's libp2p -// key and pinning the validator's peer ID. The returned net.Conn is a -// sec.SecureConn and feeds the existing serve loop unchanged. -func NoiseDialer(addr string, identity cmtcrypto.PrivKey, validator peer.ID, timeout time.Duration) (privval.SocketDialer, error) { - lpk, err := lp2p.PrivateKeyFromCosmosKey(identity) - if err != nil { - return nil, fmt.Errorf("cometkms: noise identity: %w", err) - } - tr, err := libp2pnoise.New(libp2pnoise.ID, lpk, nil) - if err != nil { - return nil, fmt.Errorf("cometkms: noise transport: %w", err) - } - return func() (net.Conn, error) { - raw, err := net.DialTimeout("tcp", addr, timeout) - if err != nil { - return nil, err - } - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - secured, err := tr.SecureOutbound(ctx, raw, validator) - if err != nil { - _ = raw.Close() - return nil, fmt.Errorf("cometkms: noise handshake with %s: %w", validator, err) - } - return secured, nil - }, nil -} diff --git a/kms/internal/transport/noise_test.go b/kms/internal/transport/noise_test.go deleted file mode 100644 index 8ddf39370de..00000000000 --- a/kms/internal/transport/noise_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package transport_test - -import ( - "net" - "testing" - "time" - - "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/cometbft/cometbft/lp2p" - "github.com/cometbft/cometbft/privval" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/kms/internal/transport" -) - -func TestNoiseDialerRoundTrip(t *testing.T) { - serverKey := ed25519.GenPrivKey() - clientKey := ed25519.GenPrivKey() - serverPeer, err := lp2p.IDFromPrivateKey(serverKey) - require.NoError(t, err) - clientPeer, err := lp2p.IDFromPrivateKey(clientKey) - require.NoError(t, err) - - tcpLn, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - nl, err := privval.NewNoiseListener(tcpLn, serverKey, []peer.ID{clientPeer}) - require.NoError(t, err) - defer nl.Close() - - go func() { - c, aerr := nl.Accept() - if aerr == nil { - _, _ = c.Write([]byte("hi")) - _ = c.Close() - } - }() - - dial, err := transport.NoiseDialer(tcpLn.Addr().String(), clientKey, serverPeer, 2*time.Second) - require.NoError(t, err) - conn, err := dial() - require.NoError(t, err) - defer conn.Close() - - buf := make([]byte, 2) - _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) - _, err = conn.Read(buf) - require.NoError(t, err) - require.Equal(t, "hi", string(buf)) -} - -func TestNoiseDialerWrongPeerRejected(t *testing.T) { - serverKey := ed25519.GenPrivKey() - clientKey := ed25519.GenPrivKey() - wrongPeer, err := lp2p.IDFromPrivateKey(ed25519.GenPrivKey()) - require.NoError(t, err) - - tcpLn, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - nl, err := privval.NewNoiseListener(tcpLn, serverKey, nil) // allow any - require.NoError(t, err) - defer nl.Close() - go func() { _, _ = nl.Accept() }() - - dial, err := transport.NoiseDialer(tcpLn.Addr().String(), clientKey, wrongPeer, 2*time.Second) - require.NoError(t, err) - _, err = dial() // must fail: server's key != wrongPeer - require.Error(t, err) -} diff --git a/kms/internal/version/version.go b/kms/internal/version/version.go deleted file mode 100644 index 5a13ce52e2e..00000000000 --- a/kms/internal/version/version.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package version exposes the cometkms build version. -package version - -// Version is the cometkms semantic version. Overridable at build time via -// -ldflags "-X github.com/cometbft/cometbft/kms/internal/version.Version=...". -var Version = "0.1.0-dev" - -// String returns the cometkms version string. -func String() string { return Version } diff --git a/kms/internal/version/version_test.go b/kms/internal/version/version_test.go deleted file mode 100644 index 12ef4959767..00000000000 --- a/kms/internal/version/version_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package version - -import "testing" - -func TestStringNotEmpty(t *testing.T) { - if String() == "" { - t.Fatal("version.String() must not be empty") - } -} From 70dbf6506b269284a8cadfd3e7fa1a96c2f9806e Mon Sep 17 00:00:00 2001 From: Thomas <81727899+thomas-nguy@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:22:08 +0900 Subject: [PATCH 10/32] fix(mempool): Implement `MsgBytesFilter` in Reactor to prevent heap amplification (#5946) Implements the optional p2p.MsgBytesFilter interface (defined in p2p/base_reactor.go) for this reactor. When provided, this filter runs against the raw message bytes before unmarshalling, allowing oversized or malformed messages to be rejected up front. This closes a heap-amplification vector where a small, count-packed wire payload could be unmarshalled into a disproportionately large in-memory structure, exhausting memory. Motivation: Previously the message bytes went straight to unmarshal with no pre-decode size/validity check. A crafted gossip message could exploit this to trigger excessive heap allocation. Rejecting bad messages before unmarshal eliminates the attack surface. --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- CHANGELOG.md | 2 + internal/protowire/protowire.go | 127 ++++++++++++++++++++++++ internal/protowire/protowire_test.go | 92 +++++++++++++++++ mempool/app_reactor.go | 10 ++ mempool/filter.go | 129 ++++++++++++++++++++++++ mempool/filter_test.go | 143 +++++++++++++++++++++++++++ mempool/reactor.go | 10 ++ 7 files changed, 513 insertions(+) create mode 100644 internal/protowire/protowire.go create mode 100644 internal/protowire/protowire_test.go create mode 100644 mempool/filter.go create mode 100644 mempool/filter_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f7b9b5cfc..79c258fa941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,8 @@ ([\#5860](https://github.com/cometbft/cometbft/pull/5860)) - `[autofile]` skip fsync in `FlushAndSync` when no new data was written ([\#5866](https://github.com/cometbft/cometbft/pull/5866)) +- `[mempool]` Implement `MsgBytesFilter` in Reactor to prevent heap amplification attack + ([\#5946](https://github.com/cometbft/cometbft/pull/5946)) ### FEATURES diff --git a/internal/protowire/protowire.go b/internal/protowire/protowire.go new file mode 100644 index 00000000000..4983cbc8130 --- /dev/null +++ b/internal/protowire/protowire.go @@ -0,0 +1,127 @@ +// Package protowire provides a minimal, allocation-free reader for walking +// raw protobuf wire bytes. Every read is bounds-checked and advances a cursor. +package protowire + +import ( + "errors" + "fmt" +) + +// Wire types (the low 3 bits of a field tag). +const ( + WireVarint = 0 + WireFixed64 = 1 + WireBytes = 2 + WireFixed32 = 5 +) + +var ( + // ErrVarintOverflow is returned when a varint does not terminate within + // the 10 bytes that can hold a 64-bit value. + ErrVarintOverflow = errors.New("varint overflow") + // ErrTruncatedVarint is returned when the buffer ends mid-varint. + ErrTruncatedVarint = errors.New("truncated varint") + // ErrOutOfBounds is returned when a length prefix or fixed-width field + // would run past the end of the buffer. + ErrOutOfBounds = errors.New("length out of bounds") + // ErrIllegalFieldNumber is returned when a tag decodes to a field number + // that is not a legal protobuf field number (>= 1). + ErrIllegalFieldNumber = errors.New("illegal field number") + // ErrUnsupportedWireType is returned when a tag carries a wire type the + // cursor does not know how to skip. + ErrUnsupportedWireType = errors.New("unsupported wire type") +) + +// WireCursor walks a protobuf buffer left to right. Every read is bounds-checked +// and advances the cursor; a read past the end returns an error rather than +// panicking. +type WireCursor struct { + buf []byte + pos int +} + +// NewWireCursor returns a cursor positioned at the start of buf. +func NewWireCursor(buf []byte) WireCursor { + return WireCursor{buf: buf} +} + +// AtEnd reports whether the cursor has consumed the whole buffer. +func (c *WireCursor) AtEnd() bool { return c.pos >= len(c.buf) } + +// ReadVarint decodes a base-128 varint at the cursor and advances past it. +func (c *WireCursor) ReadVarint() (uint64, error) { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrVarintOverflow + } + if c.pos >= len(c.buf) { + return 0, ErrTruncatedVarint + } + b := c.buf[c.pos] + c.pos++ + v |= uint64(b&0x7f) << shift + if b < 0x80 { + return v, nil + } + } +} + +// ReadTag decodes a field tag, splitting it into field number and wire type. +func (c *WireCursor) ReadTag() (fieldNum, wireType int, err error) { + v, err := c.ReadVarint() + if err != nil { + return 0, 0, err + } + fieldNum = int(v >> 3) + wireType = int(v & 0x7) + if fieldNum <= 0 { + return 0, 0, fmt.Errorf("%w: %d", ErrIllegalFieldNumber, fieldNum) + } + return fieldNum, wireType, nil +} + +// ReadLengthDelimited reads a length-prefixed run of bytes (wire type 2) and +// returns it as a sub-slice of the underlying buffer, advancing past it. +func (c *WireCursor) ReadLengthDelimited() ([]byte, error) { + length, err := c.ReadVarint() + if err != nil { + return nil, err + } + start := c.pos + end := start + int(length) + if end < start || end > len(c.buf) { + return nil, ErrOutOfBounds + } + c.pos = end + return c.buf[start:end], nil +} + +// SkipField advances the cursor past the body of a field with the given wire +// type, used to ignore fields the caller does not care about. +func (c *WireCursor) SkipField(wireType int) error { + switch wireType { + case WireVarint: + _, err := c.ReadVarint() + return err + case WireFixed64: + return c.advance(8) + case WireBytes: + _, err := c.ReadLengthDelimited() + return err + case WireFixed32: + return c.advance(4) + default: + return fmt.Errorf("%w: %d", ErrUnsupportedWireType, wireType) + } +} + +// advance moves the cursor forward by n bytes, bounds-checked. +func (c *WireCursor) advance(n int) error { + end := c.pos + n + if end < c.pos || end > len(c.buf) { + return ErrOutOfBounds + } + c.pos = end + return nil +} diff --git a/internal/protowire/protowire_test.go b/internal/protowire/protowire_test.go new file mode 100644 index 00000000000..a258640b879 --- /dev/null +++ b/internal/protowire/protowire_test.go @@ -0,0 +1,92 @@ +package protowire + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestWireCursor_ReadVarint(t *testing.T) { + // 300 encodes as 0xac 0x02 in base-128 varint. + c := NewWireCursor([]byte{0xac, 0x02}) + v, err := c.ReadVarint() + require.NoError(t, err) + require.Equal(t, uint64(300), v) + require.True(t, c.AtEnd()) +} + +func TestWireCursor_ReadVarint_Truncated(t *testing.T) { + c := NewWireCursor([]byte{0xff}) + _, err := c.ReadVarint() + require.ErrorIs(t, err, ErrTruncatedVarint) +} + +func TestWireCursor_ReadVarint_Overflow(t *testing.T) { + // 11 continuation bytes never terminate within 64 bits. + c := NewWireCursor([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + _, err := c.ReadVarint() + require.ErrorIs(t, err, ErrVarintOverflow) +} + +func TestWireCursor_ReadTag(t *testing.T) { + // Field 1, wire type 2 (length-delimited) => tag byte 0x0a. + c := NewWireCursor([]byte{0x0a}) + fieldNum, wireType, err := c.ReadTag() + require.NoError(t, err) + require.Equal(t, 1, fieldNum) + require.Equal(t, WireBytes, wireType) +} + +func TestWireCursor_ReadTag_IllegalFieldNumber(t *testing.T) { + // Tag 0x00 => field number 0, which is illegal. + c := NewWireCursor([]byte{0x00}) + _, _, err := c.ReadTag() + require.ErrorIs(t, err, ErrIllegalFieldNumber) +} + +func TestWireCursor_ReadLengthDelimited(t *testing.T) { + c := NewWireCursor([]byte{0x03, 'a', 'b', 'c'}) + b, err := c.ReadLengthDelimited() + require.NoError(t, err) + require.Equal(t, []byte("abc"), b) + require.True(t, c.AtEnd()) +} + +func TestWireCursor_ReadLengthDelimited_OutOfBounds(t *testing.T) { + // Declares length 5 but only 1 byte follows. + c := NewWireCursor([]byte{0x05, 'a'}) + _, err := c.ReadLengthDelimited() + require.ErrorIs(t, err, ErrOutOfBounds) +} + +func TestWireCursor_SkipField(t *testing.T) { + testCases := []struct { + name string + buf []byte + wireType int + }{ + {"varint", []byte{0xac, 0x02}, WireVarint}, + {"fixed64", []byte{1, 2, 3, 4, 5, 6, 7, 8}, WireFixed64}, + {"bytes", []byte{0x02, 'x', 'y'}, WireBytes}, + {"fixed32", []byte{1, 2, 3, 4}, WireFixed32}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c := NewWireCursor(tc.buf) + require.NoError(t, c.SkipField(tc.wireType)) + require.True(t, c.AtEnd()) + }) + } +} + +func TestWireCursor_SkipField_Unsupported(t *testing.T) { + c := NewWireCursor([]byte{0x00}) + // Wire type 3 (start group) is not supported. + require.ErrorIs(t, c.SkipField(3), ErrUnsupportedWireType) +} + +func TestWireCursor_SkipField_OutOfBounds(t *testing.T) { + // Fixed64 needs 8 bytes; only 2 are present. + c := NewWireCursor([]byte{1, 2}) + require.ErrorIs(t, c.SkipField(WireFixed64), ErrOutOfBounds) +} diff --git a/mempool/app_reactor.go b/mempool/app_reactor.go index b73ac8c2393..0fa30788000 100644 --- a/mempool/app_reactor.go +++ b/mempool/app_reactor.go @@ -12,6 +12,8 @@ import ( "github.com/pkg/errors" ) +var _ p2p.MsgBytesFilter = (*AppReactor)(nil) + // AppReactor for interacting with AppMempool type AppReactor struct { p2p.BaseReactor @@ -134,6 +136,14 @@ func (r *AppReactor) EnableInOutTxs() { close(r.waitForSwitchingOnCh) } +// FilterMsgBytes implements p2p.MsgBytesFilter. +func (r *AppReactor) FilterMsgBytes(chID byte, _ p2p.Peer, msgBytes []byte) error { + if chID != MempoolChannel || len(msgBytes) == 0 { + return nil + } + return filterMempoolMsgBytes(msgBytes, r.config.MaxTxBytes, r.config.MaxBatchBytes) +} + func (r *AppReactor) Receive(e p2p.Envelope) { if !r.enabled() { r.Logger.Debug("Ignored mempool message received while syncing") diff --git a/mempool/filter.go b/mempool/filter.go new file mode 100644 index 00000000000..2cd56704c33 --- /dev/null +++ b/mempool/filter.go @@ -0,0 +1,129 @@ +package mempool + +import ( + "errors" + "fmt" + + "github.com/cometbft/cometbft/internal/protowire" +) + +// Field numbers we care about. Both happen to be 1: the Message oneof's only +// member is Txs (field 1), and within Txs the repeated tx entries are field 1. +const ( + fieldMessageTxs = 1 // Message.sum: `Txs txs = 1` + fieldTxsEntry = 1 // Txs: `repeated bytes txs = 1` +) + +var ( + errNoTransactions = errors.New("mempool message contains no transactions") + errEmptyTransaction = errors.New("mempool batch contains an empty transaction") +) + +// batchTally accumulates what has been seen so far while scanning a batch. +type batchTally struct { + count int // number of transaction entries + totalBytes int // sum of their sizes +} + +// filterMempoolMsgBytes enforces the filtering rules above against +// the raw wire bytes of a tendermint.mempool.Message. +// +// A single tx can be as large as maxTxBytes even when maxBatchBytes is smaller, +// so the whole-message ceiling is the larger of the two. A non-positive value +// for either limit disables that check. +// +// Filtering rules: +// +// 1. Well-formed: every varint, tag and length prefix must stay within the +// buffer. Truncated, overflowing or out-of-bounds encodings are rejected. +// 2. No empty entries: every transaction must have at least one byte. An empty +// entry carries no payload yet still costs an allocation, so it is never +// legitimate — this is the core of the amplification attack. +// 3. Per-tx bound: no single transaction may exceed maxTxBytes. +// 4. Per-batch bound: the sum of all transaction sizes may not exceed +// max(maxTxBytes, maxBatchBytes). +// 5. Non-empty message: the message must carry at least one transaction. +func filterMempoolMsgBytes(msgBytes []byte, maxTxBytes, maxBatchBytes int) error { + batchBudget := max(maxTxBytes, maxBatchBytes) + var tally batchTally + + msg := protowire.NewWireCursor(msgBytes) + for !msg.AtEnd() { + fieldNum, wireType, err := msg.ReadTag() + if err != nil { + return err + } + + // Anything that is not the Txs submessage is skipped, so unknown or + // future fields do not break the scan. + if fieldNum != fieldMessageTxs || wireType != protowire.WireBytes { + if err := msg.SkipField(wireType); err != nil { + return err + } + continue + } + + txsBytes, err := msg.ReadLengthDelimited() + if err != nil { + return err + } + if err := scanTxsSubmessage(txsBytes, maxTxBytes, batchBudget, &tally); err != nil { + return err + } + } + + if tally.count == 0 { + return errNoTransactions + } + return nil +} + +// scanTxsSubmessage walks a Txs submessage (`repeated bytes txs = 1`), +// validating each transaction entry against the limits and folding it into the +// tally. +func scanTxsSubmessage(txsBytes []byte, maxTxBytes, batchBudget int, tally *batchTally) error { + txs := protowire.NewWireCursor(txsBytes) + for !txs.AtEnd() { + fieldNum, wireType, err := txs.ReadTag() + if err != nil { + return err + } + + if fieldNum != fieldTxsEntry || wireType != protowire.WireBytes { + if err := txs.SkipField(wireType); err != nil { + return err + } + continue + } + + tx, err := txs.ReadLengthDelimited() + if err != nil { + return err + } + if err := applyRules(tx, maxTxBytes, batchBudget, tally); err != nil { + return err + } + } + return nil +} + +// applyRules applies the per-entry rules (2-4) to a single transaction. +func applyRules(tx []byte, maxTxBytes, batchBudget int, tally *batchTally) error { + // No empty transaction + if len(tx) == 0 { + return errEmptyTransaction + } + // Transaction cannot exceed maxTxBytes + if maxTxBytes > 0 && len(tx) > maxTxBytes { + return fmt.Errorf("transaction size %d exceeds max_tx_bytes %d", len(tx), maxTxBytes) + } + + tally.count++ + tally.totalBytes += len(tx) + + // The sum of all transaction sizes may not exceed the batch budget. + if batchBudget > 0 && tally.totalBytes > batchBudget { + return fmt.Errorf("mempool batch exceeds %d byte budget", batchBudget) + } + return nil +} diff --git a/mempool/filter_test.go b/mempool/filter_test.go new file mode 100644 index 00000000000..3a29554cce8 --- /dev/null +++ b/mempool/filter_test.go @@ -0,0 +1,143 @@ +package mempool + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cometbft/cometbft/config" + protomem "github.com/cometbft/cometbft/proto/tendermint/mempool" +) + +// marshalTxsMsg builds the wire bytes for a wrapped tendermint.mempool.Message +// carrying the given raw transactions, exactly as it would appear on the wire. +func marshalTxsMsg(t *testing.T, txs [][]byte) []byte { + t.Helper() + msg := &protomem.Message{Sum: &protomem.Message_Txs{Txs: &protomem.Txs{Txs: txs}}} + b, err := msg.Marshal() + require.NoError(t, err) + return b +} + +func TestFilterMempoolMsgBytes(t *testing.T) { + const maxTxBytes = 1024 + const maxBatchBytes = 4096 + + // A batch packed with empty entries: cheap on the wire, but len(txs) is huge. + emptyEntries := make([][]byte, 10000) + for i := range emptyEntries { + emptyEntries[i] = []byte{} + } + + // A batch of one-byte entries whose declared sizes blow the byte budget. + overBudget := make([][]byte, maxBatchBytes+1) + for i := range overBudget { + overBudget[i] = []byte{0x01} + } + + testCases := []struct { + name string + txs [][]byte + wantErr bool + }{ + { + name: "valid single tx", + txs: [][]byte{[]byte("hello world")}, + wantErr: false, + }, + { + name: "valid batch", + txs: [][]byte{[]byte("tx-one"), []byte("tx-two"), []byte("tx-three")}, + wantErr: false, + }, + { + name: "valid tx at max size", + txs: [][]byte{make([]byte, maxTxBytes)}, + wantErr: false, + }, + { + name: "empty txs list", + txs: [][]byte{}, + wantErr: true, + }, + { + name: "single empty entry", + txs: [][]byte{{}}, + wantErr: true, + }, + { + name: "empty-entry packing attack", + txs: emptyEntries, + wantErr: true, + }, + { + name: "tx exceeds max_tx_bytes", + txs: [][]byte{make([]byte, maxTxBytes+1)}, + wantErr: true, + }, + { + name: "batch exceeds byte budget", + txs: overBudget, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + msgBytes := marshalTxsMsg(t, tc.txs) + err := filterMempoolMsgBytes(msgBytes, maxTxBytes, maxBatchBytes) + if tc.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestFilterMempoolMsgBytes_Unlimited(t *testing.T) { + // With maxTxBytes == 0 and maxBatchBytes == 0 the size checks are disabled, + // but empty entries are still rejected (they are never valid txs). + big := marshalTxsMsg(t, [][]byte{make([]byte, 1<<20)}) + require.NoError(t, filterMempoolMsgBytes(big, 0, 0)) + + require.Error(t, filterMempoolMsgBytes(marshalTxsMsg(t, [][]byte{{}}), 0, 0)) +} + +func TestFilterMempoolMsgBytes_Malformed(t *testing.T) { + // Truncated varint for the outer Message field length. + require.Error(t, filterMempoolMsgBytes([]byte{0x0a, 0xff}, 1024, 4096)) + + // Length that runs past the end of the buffer. + require.Error(t, filterMempoolMsgBytes([]byte{0x0a, 0x7f}, 1024, 4096)) +} + +// The per-batch budget is the larger of maxTxBytes and maxBatchBytes, so a +// single tx bigger than maxBatchBytes but within maxTxBytes must still pass. +func TestFilterMempoolMsgBytes_BatchBudgetIsMax(t *testing.T) { + const maxTxBytes = 4096 + const maxBatchBytes = 1024 + + // One tx larger than maxBatchBytes but within maxTxBytes: allowed. + big := marshalTxsMsg(t, [][]byte{make([]byte, maxBatchBytes+1)}) + require.NoError(t, filterMempoolMsgBytes(big, maxTxBytes, maxBatchBytes)) + + // A batch whose total exceeds the larger limit is still rejected. + over := marshalTxsMsg(t, [][]byte{make([]byte, maxTxBytes), make([]byte, 1)}) + require.Error(t, filterMempoolMsgBytes(over, maxTxBytes, maxBatchBytes)) +} + +func TestReactorFilterMsgBytes_ChannelGuard(t *testing.T) { + cfg := config.DefaultMempoolConfig() + r := &AppReactor{config: cfg} + + bad := marshalTxsMsg(t, [][]byte{{}, {}, {}}) + + // Non-mempool channel: not our concern, must pass through untouched. + assert.NoError(t, r.FilterMsgBytes(byte(0x00), nil, bad)) + // Empty payload: nothing to validate. + assert.NoError(t, r.FilterMsgBytes(MempoolChannel, nil, nil)) + // Mempool channel with abusive payload: rejected. + assert.Error(t, r.FilterMsgBytes(MempoolChannel, nil, bad)) +} diff --git a/mempool/reactor.go b/mempool/reactor.go index 2548cd28db9..c5951dc494e 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -16,6 +16,8 @@ import ( "golang.org/x/sync/semaphore" ) +var _ p2p.MsgBytesFilter = (*Reactor)(nil) + // Reactor handles mempool tx broadcasting amongst peers. // It maintains a map from peer ID to counter, to prevent gossiping txs to the // peers you received it from. @@ -146,6 +148,14 @@ func (memR *Reactor) RemovePeer(peer p2p.Peer, _ any) { // broadcast routine checks if peer is gone and returns } +// FilterMsgBytes implements p2p.MsgBytesFilter. +func (memR *Reactor) FilterMsgBytes(chID byte, _ p2p.Peer, msgBytes []byte) error { + if chID != MempoolChannel || len(msgBytes) == 0 { + return nil + } + return filterMempoolMsgBytes(msgBytes, memR.config.MaxTxBytes, memR.config.MaxBatchBytes) +} + // Receive implements Reactor. // It adds any received transactions to the mempool. func (memR *Reactor) Receive(e p2p.Envelope) { From 81d1db4205b5bff9919ec352dadde02f790505c4 Mon Sep 17 00:00:00 2001 From: JayT106 Date: Sun, 5 Jul 2026 14:28:40 -0400 Subject: [PATCH 11/32] fix(lp2p): remove MaxStreamSize clamp in StreamReadSized (#5954) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit \`StreamReadSized\` enforces a per-read size limit passed in by the caller (the reactor's \`RecvMessageCapacity\`). The 4 MB \`MaxStreamSize\` constant is intended as a default fallback for callers that don't specify a limit. ## Problem \`StreamReadSized\` applied \`payloadLimit := min(maxSize, MaxStreamSize)\`, clamping the caller-supplied limit down to 4 MB regardless of what the reactor configured. Blocksync sets \`RecvMessageCapacity = MaxBlockSizeBytes + 5 ≈ 100 MB\`; any block >4 MB was rejected with "payload is too large" and the stream was reset. The send side (\`StreamWrite\`) has no cap and serialises the full block, so a peer could send a 50 MB block that the receiver would always reject — a liveness failure for any lp2p node behind on a chain producing >4 MB blocks. Statesync's 16 MB chunks were similarly affected. ## Fix Use \`maxSize\` directly. \`MaxStreamSize\` is now a fallback only when \`maxSize == 0\` (unset), not a hard global cap. Adds a regression test for \`maxSize > MaxStreamSize\`. ## Remaining issue \`readExactly\` does \`make([]byte, payloadSize)\` immediately after reading the length-prefix header, so a malicious peer can force a receiver to allocate up to \`RecvMessageCapacity\` bytes before any payload data arrives. The old 4 MB clamp was inadvertently limiting this to 4 MB per stream. The correct fix is to replace \`readExactly\` with \`io.ReadAll(io.LimitReader(...))\` so allocation grows only as data arrives. This is left as a follow-up to keep this PR minimal; the real guard in the meantime is the libp2p resource manager and \`RecvMessageCapacity\` being set to the consensus-enforced max block size. --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in \`CHANGELOG.md\` - [ ] Updated relevant documentation (\`docs/\` or \`spec/\`) and code comments --- CHANGELOG.md | 2 ++ lp2p/stream.go | 6 +++++- lp2p/stream_test.go | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c258fa941..f5165649be8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ ([\#5868](https://github.com/cometbft/cometbft/pull/5868)) - `[node]` close partial listeners on startRPC failure ([\#5869](https://github.com/cometbft/cometbft/pull/5869)) +- `[lp2p]` remove `MaxStreamSize` clamp in `StreamReadSized` + ([\#5954](https://github.com/cometbft/cometbft/pull/5954)) - `[lp2p]` fallback to conn remote addr when resolving inbound peer ([\#5879](https://github.com/cometbft/cometbft/pull/5879)) - `[consensus]` release cs.mtx before sending to statsMsgQueue diff --git a/lp2p/stream.go b/lp2p/stream.go index 0c146cc5b08..07332d96097 100644 --- a/lp2p/stream.go +++ b/lp2p/stream.go @@ -103,7 +103,11 @@ func StreamReadSized(s network.Stream, maxSize uint64) ([]byte, error) { return nil, errors.Wrap(err, "failed to read payload size") } - payloadLimit := min(maxSize, MaxStreamSize) + // Honor the caller's limit; MaxStreamSize is a fallback, not a hard cap. + payloadLimit := maxSize + if payloadLimit == 0 { + payloadLimit = MaxStreamSize + } if payloadSize > payloadLimit { return nil, errors.Errorf("payload is too large (got %d, max %d)", payloadSize, payloadLimit) diff --git a/lp2p/stream_test.go b/lp2p/stream_test.go index 99eb139269c..5c564c43497 100644 --- a/lp2p/stream_test.go +++ b/lp2p/stream_test.go @@ -168,6 +168,22 @@ func TestStream(t *testing.T) { require.Nil(t, out) }) + t.Run("LargerThanMaxStreamSize", func(t *testing.T) { + // maxSize > MaxStreamSize must NOT be clamped — blocksync passes + // RecvMessageCapacity (~100MB) and must be able to receive large blocks. + payload := bytes.Repeat([]byte("x"), MaxStreamSize+1) + frame := append(uint64ToUvarint(uint64(len(payload))), payload...) + + stream := &streamStub{ + conn: &connStub{closed: false}, + readFn: bytes.NewReader(frame).Read, + } + + out, err := StreamReadSized(stream, uint64(len(payload))) + require.NoError(t, err) + require.Equal(t, payload, out) + }) + t.Run("InvalidHeader", func(t *testing.T) { // ARRANGE invalidHeader := []byte{0x80} From eabe04b9bfc4867fe491f9fced21fb04553c3fd5 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Mon, 6 Jul 2026 06:14:34 -0500 Subject: [PATCH 12/32] fix: Fix race condition in privval shutdown (#5934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Updates the privval RetrySignerClient to check on shutdown before retrying. Without this change it is possible to get into the following shutdown state: * Node is requesting a signature from a remote signer * Shutdown happens on remote signer * Shutdown happens on node In this case we have to wait `retries × (timeoutAccept + timeout) ≈ 155s` before the node will shut down. With these changes we will wait a maximum of a single retry timeout before seeing that `Close` has been called (~3s) and shutdown will happen after that. --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 ++ node/node.go | 11 +++++++ privval/retry_signer_client.go | 32 +++++++++++++++++--- privval/retry_signer_client_test.go | 46 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 privval/retry_signer_client_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f5165649be8..324587d9ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ ([\#5879](https://github.com/cometbft/cometbft/pull/5879)) - `[consensus]` release cs.mtx before sending to statsMsgQueue ([\#5813](https://github.com/cometbft/cometbft/pull/5813)) +- `[privval]` preempt sleep retries in privval signer client + ([\#5934](https://github.com/cometbft/cometbft/pull/5934)) ### IMPROVEMENTS diff --git a/node/node.go b/node/node.go index f1a66a72c2e..896d4128aea 100644 --- a/node/node.go +++ b/node/node.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "net" "net/http" "os" @@ -744,6 +745,16 @@ func (n *Node) OnStop() { n.Logger.Error("Error closing indexerService", "err", err) } } + // Close the priv validator before stopping the reactors: sw.Stop waits on + // the consensus receiveRoutine, which can be stuck retrying a gone remote + // signer. Closing aborts that retry loop. (RetrySignerClient is not a + // service.Service, so the assertion below never fires for the socket client.) + if c, ok := n.privValidator.(io.Closer); ok { + if err := c.Close(); err != nil { + n.Logger.Error("Error closing private validator", "err", err) + } + } + // now stop the reactors if err := n.sw.Stop(); err != nil { n.Logger.Error("Error closing switch", "err", err) diff --git a/privval/retry_signer_client.go b/privval/retry_signer_client.go index 271e146474c..606f1b0876b 100644 --- a/privval/retry_signer_client.go +++ b/privval/retry_signer_client.go @@ -2,6 +2,7 @@ package privval import ( "fmt" + "sync" "time" "github.com/cometbft/cometbft/crypto" @@ -15,20 +16,37 @@ type RetrySignerClient struct { next *SignerClient retries int timeout time.Duration + + quitOnce sync.Once + quit chan struct{} // closed by Close to abort in-flight retry loops } // NewRetrySignerClient returns RetrySignerClient. If +retries+ is 0, the // client will be retrying each operation indefinitely. func NewRetrySignerClient(sc *SignerClient, retries int, timeout time.Duration) *RetrySignerClient { - return &RetrySignerClient{sc, retries, timeout} + return &RetrySignerClient{next: sc, retries: retries, timeout: timeout, quit: make(chan struct{})} } var _ types.PrivValidator = (*RetrySignerClient)(nil) +// Close aborts any in-flight retry loop and closes the underlying client. Safe +// to call more than once. The abort unblocks node shutdown, which waits on the +// consensus receiveRoutine that signs synchronously. func (sc *RetrySignerClient) Close() error { + sc.quitOnce.Do(func() { close(sc.quit) }) return sc.next.Close() } +// sleep waits for timeout, returning false if Close was called first. +func (sc *RetrySignerClient) sleep() bool { + select { + case <-sc.quit: + return false + case <-time.After(sc.timeout): + return true + } +} + func (sc *RetrySignerClient) IsConnected() bool { return sc.next.IsConnected() } @@ -58,7 +76,9 @@ func (sc *RetrySignerClient) GetPubKey() (crypto.PubKey, error) { if _, ok := err.(*RemoteSignerError); ok { return nil, err } - time.Sleep(sc.timeout) + if !sc.sleep() { + return nil, fmt.Errorf("aborted getting pubkey: %w", err) + } } return nil, fmt.Errorf("exhausted all attempts to get pubkey: %w", err) } @@ -74,7 +94,9 @@ func (sc *RetrySignerClient) SignVote(chainID string, vote *cmtproto.Vote) error if _, ok := err.(*RemoteSignerError); ok { return err } - time.Sleep(sc.timeout) + if !sc.sleep() { + return fmt.Errorf("aborted signing vote: %w", err) + } } return fmt.Errorf("exhausted all attempts to sign vote: %w", err) } @@ -90,7 +112,9 @@ func (sc *RetrySignerClient) SignProposal(chainID string, proposal *cmtproto.Pro if _, ok := err.(*RemoteSignerError); ok { return err } - time.Sleep(sc.timeout) + if !sc.sleep() { + return fmt.Errorf("aborted signing proposal: %w", err) + } } return fmt.Errorf("exhausted all attempts to sign proposal: %w", err) } diff --git a/privval/retry_signer_client_test.go b/privval/retry_signer_client_test.go new file mode 100644 index 00000000000..30999fc2953 --- /dev/null +++ b/privval/retry_signer_client_test.go @@ -0,0 +1,46 @@ +package privval + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/cometbft/cometbft/crypto/ed25519" + "github.com/cometbft/cometbft/libs/log" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" +) + +// TestRetrySignerClientCloseAborts checks Close aborts an in-flight retry loop +// instead of draining the full retry budget (which would wedge node shutdown). +func TestRetrySignerClientCloseAborts(t *testing.T) { + // Listener with no remote signer ever dialing in: every attempt blocks in + // WaitConnection until timeoutAccept, then errors. + endpoint := newSignerListenerEndpoint(log.TestingLogger(), "tcp://127.0.0.1:0", testTimeoutReadWrite) + require.NoError(t, endpoint.Start()) + t.Cleanup(func() { _ = endpoint.Stop() }) + + sc, err := NewSignerClient(endpoint, "chain-id") + require.NoError(t, err) + + // Large budget: if Close didn't abort, this would take many seconds. + rsc := NewRetrySignerClient(sc, 100, 50*time.Millisecond) + + done := make(chan error, 1) + go func() { + done <- rsc.SignVote("chain-id", &cmtproto.Vote{ValidatorAddress: ed25519.GenPrivKey().PubKey().Address()}) + }() + + // Let one attempt get underway, then close. + time.Sleep(100 * time.Millisecond) + require.NoError(t, rsc.Close()) + + select { + case err := <-done: + require.Error(t, err) // aborted, not signed + case <-time.After(testTimeoutAccept + 2*time.Second): + t.Fatal("SignVote did not abort after Close") + } + + require.NotPanics(t, func() { _ = rsc.Close() }) // idempotent +} From d4252d78f2bad52ca1f38f3ffa691790ad1572ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:16:10 +0200 Subject: [PATCH 13/32] build(deps): Bump actions/checkout from 6 to 7 (#5940) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
Release notes

Sourced from actions/checkout's releases.

v7.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6.0.3...v7.0.0

v6.0.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.3

v6.0.2

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v6.0.1...v6.0.2

v6.0.1

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v6...v6.0.1

Changelog

Sourced from actions/checkout's changelog.

Changelog

v7.0.0

v6.0.3

v6.0.2

v6.0.1

v6.0.0

v5.0.1

v5.0.0

v4.3.1

v4.3.0

v4.2.2

v4.2.1

... (truncated)

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/check-generated.yml | 4 ++-- .github/workflows/docker-build-cometbft.yml | 2 +- .github/workflows/docker-build-e2e-node.yml | 2 +- .github/workflows/docs-toc.yml | 2 +- .github/workflows/e2e-manual-multiversion.yml | 2 +- .github/workflows/e2e-manual.yml | 2 +- .github/workflows/e2e-nightly-main.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/integration_tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/markdown-linter.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/proto-lint.yml | 2 +- .github/workflows/release-version.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- 18 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4329b49c9d8..077568e78c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: goos: ["linux"] timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - id: filter uses: dorny/paths-filter@v4 diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index c94dbc1fb2d..646755087a6 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -20,7 +20,7 @@ jobs: check-mocks-metrics: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV @@ -48,7 +48,7 @@ jobs: check-proto: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 1 # we need a .git directory to run git diff diff --git a/.github/workflows/docker-build-cometbft.yml b/.github/workflows/docker-build-cometbft.yml index a51fc0f5a3e..462832e76b7 100644 --- a/.github/workflows/docker-build-cometbft.yml +++ b/.github/workflows/docker-build-cometbft.yml @@ -60,7 +60,7 @@ jobs: TAGS=$(echo "${{ needs.vars.outputs.tags }}" | sed "s/[^,]*/&-${{ matrix.arch }}/g") echo "tags=${TAGS}" >> $GITHUB_OUTPUT - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/.github/workflows/docker-build-e2e-node.yml b/.github/workflows/docker-build-e2e-node.yml index 3e2dc2d32fd..b50ea0abd51 100644 --- a/.github/workflows/docker-build-e2e-node.yml +++ b/.github/workflows/docker-build-e2e-node.yml @@ -60,7 +60,7 @@ jobs: TAGS=$(echo "${{ needs.vars.outputs.tags }}" | sed "s/[^,]*/&-${{ matrix.arch }}/g") echo "tags=${TAGS}" >> $GITHUB_OUTPUT - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/.github/workflows/docs-toc.yml b/.github/workflows/docs-toc.yml index 7022c13b3d2..91f6ac10815 100644 --- a/.github/workflows/docs-toc.yml +++ b/.github/workflows/docs-toc.yml @@ -10,7 +10,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: technote-space/get-diff-action@v6 with: PATTERNS: | diff --git a/.github/workflows/e2e-manual-multiversion.yml b/.github/workflows/e2e-manual-multiversion.yml index 7b209dc9c42..0da018ef3f2 100644 --- a/.github/workflows/e2e-manual-multiversion.yml +++ b/.github/workflows/e2e-manual-multiversion.yml @@ -19,7 +19,7 @@ jobs: with: go-version: '1.22' - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index 4dfb7dab26a..e2203756d64 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -19,7 +19,7 @@ jobs: with: go-version: '1.22' - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Build working-directory: test/e2e diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index a3750d3f58e..45239279870 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -20,7 +20,7 @@ jobs: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 6411379b5bd..912ce349a16 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -25,7 +25,7 @@ jobs: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 15 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - id: filter uses: dorny/paths-filter@v4 diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index 46c4b5ca59b..b2a454d2485 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -18,7 +18,7 @@ jobs: fuzz-nightly-test: runs-on: depot-ubuntu-24.04-4 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - uses: actions/setup-go@v6 diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index a1a24cde785..55a6f1a6de0 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -16,7 +16,7 @@ jobs: integration_tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - id: filter uses: dorny/paths-filter@v4 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f12c7d92cdf..754c006cbd7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: python-version: '3.x' diff --git a/.github/workflows/markdown-linter.yml b/.github/workflows/markdown-linter.yml index 2bfbb8cecef..962a0b2f781 100644 --- a/.github/workflows/markdown-linter.yml +++ b/.github/workflows/markdown-linter.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Lint Code Base uses: docker://github/super-linter:v4 env: diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index ea40d9c5d30..34486f37cd1 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index d6fe302448e..b9735a866bd 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: bufbuild/buf-action@v1 with: input: 'proto' diff --git a/.github/workflows/release-version.yml b/.github/workflows/release-version.yml index 51f5eb032a6..fa7a164c84c 100644 --- a/.github/workflows/release-version.yml +++ b/.github/workflows/release-version.yml @@ -11,7 +11,7 @@ jobs: check-version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80215c0e357..99b9573f071 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 94f87405818..0037eb74d61 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: matrix: part: ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - id: filter uses: dorny/paths-filter@v4 From a18dee4a89f26638f940b1643786c0253d6dd98d Mon Sep 17 00:00:00 2001 From: JayT106 Date: Mon, 6 Jul 2026 07:47:22 -0400 Subject: [PATCH 14/32] fix(abci): add InsertTx/ReapTxs to socket transport (#5958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Problem The socket server (`handleRequest`) and client (`resMatchesReq`) were missing cases for `Request_InsertTx` and `Request_ReapTxs`, which were added in the Krakatoa merge (#5615) but never wired into the socket type-switches. With `mempool.type = "app"` and the default `abci = "socket"` transport (out-of-process app): - Every `ReapTxs` response fails the `resMatchesReq` type-match check - Returns `ErrUnexpectedResponse` → `stopForError` → `killTMOnClientError` → `cmtos.Kill()` (SIGTERM) - `ReapTxs` fires every `ReapInterval` (~500ms), so the node self-kills within ~500ms of startup The local and gRPC transports were correctly implemented; only socket was missed. `ToResponseInsertTx`/`ToResponseReapTxs` helpers were also missing from `messages.go` (the `ToRequest*` counterparts existed). ### Fix - Add `Request_InsertTx` and `Request_ReapTxs` cases to `resMatchesReq` in `abci/client/socket_client.go` - Add `Request_InsertTx` and `Request_ReapTxs` cases to `handleRequest` in `abci/server/socket_server.go` - Add `ToResponseInsertTx` and `ToResponseReapTxs` helpers to `abci/types/messages.go` - Add `TestInsertTxReapTxsSocket` round-trip test over a real socket server/client pair --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ abci/client/socket_client.go | 4 ++++ abci/client/socket_client_test.go | 13 +++++++++++++ abci/server/socket_server.go | 12 ++++++++++++ abci/types/messages.go | 12 ++++++++++++ 5 files changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 324587d9ceb..1b7588e7918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ ### BUG FIXES +- `[abci]` fix socket transport missing `InsertTx` and `ReapTxs` cases in + `handleRequest` and `resMatchesReq`, causing `ErrUnexpectedResponse` and + node self-kill when `mempool.type = "app"` with the default socket transport + ([\#5958](https://github.com/cometbft/cometbft/pull/5958)) - `[flowrate]` fix flaky `TestWriter` by comparing `Idle` with a duration tolerance instead of exact equality ([\#5929](https://github.com/cometbft/cometbft/pull/5929)) diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index 9dea892a370..45c3d5d16e6 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -494,6 +494,10 @@ func resMatchesReq(req *types.Request, res *types.Response) (ok bool) { _, ok = res.Value.(*types.Response_Info) case *types.Request_CheckTx: _, ok = res.Value.(*types.Response_CheckTx) + case *types.Request_InsertTx: + _, ok = res.Value.(*types.Response_InsertTx) + case *types.Request_ReapTxs: + _, ok = res.Value.(*types.Response_ReapTxs) case *types.Request_Commit: _, ok = res.Value.(*types.Response_Commit) case *types.Request_Query: diff --git a/abci/client/socket_client_test.go b/abci/client/socket_client_test.go index 5c6ded36223..a4ae9fb0676 100644 --- a/abci/client/socket_client_test.go +++ b/abci/client/socket_client_test.go @@ -153,6 +153,19 @@ func setupClientServer(t *testing.T, app types.Application) ( return s, c } +func TestInsertTxReapTxsSocket(t *testing.T) { + ctx := t.Context() + _, c := setupClientServer(t, types.BaseApplication{}) + + insertRes, err := c.InsertTx(ctx, &types.RequestInsertTx{Tx: []byte("test")}) + require.NoError(t, err) + require.NotNil(t, insertRes) + + reapRes, err := c.ReapTxs(ctx, &types.RequestReapTxs{}) + require.NoError(t, err) + require.NotNil(t, reapRes) +} + type slowApp struct { types.BaseApplication } diff --git a/abci/server/socket_server.go b/abci/server/socket_server.go index 5fb1ecd68b0..096227c7d9a 100644 --- a/abci/server/socket_server.go +++ b/abci/server/socket_server.go @@ -246,6 +246,18 @@ func (s *SocketServer) handleRequest(ctx context.Context, req *types.Request) (* return nil, err } return types.ToResponseCheckTx(res), nil + case *types.Request_InsertTx: + res, err := s.app.InsertTx(ctx, r.InsertTx) + if err != nil { + return nil, err + } + return types.ToResponseInsertTx(res), nil + case *types.Request_ReapTxs: + res, err := s.app.ReapTxs(ctx, r.ReapTxs) + if err != nil { + return nil, err + } + return types.ToResponseReapTxs(res), nil case *types.Request_Commit: res, err := s.app.Commit(ctx, r.Commit) if err != nil { diff --git a/abci/types/messages.go b/abci/types/messages.go index d8d3c69a9a5..102140a6550 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -168,6 +168,18 @@ func ToResponseCheckTx(res *ResponseCheckTx) *Response { } } +func ToResponseInsertTx(res *ResponseInsertTx) *Response { + return &Response{ + Value: &Response_InsertTx{res}, + } +} + +func ToResponseReapTxs(res *ResponseReapTxs) *Response { + return &Response{ + Value: &Response_ReapTxs{res}, + } +} + func ToResponseCommit(res *ResponseCommit) *Response { return &Response{ Value: &Response_Commit{res}, From 096ac149f3f8b0c7d939da058a6a35feccef74f0 Mon Sep 17 00:00:00 2001 From: Thomas <81727899+thomas-nguy@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:22:20 +0900 Subject: [PATCH 15/32] fix(mempool): truncate proto field number to int32 in filter's ReadTag (#5948) Fixing a small bug in PR https://github.com/cometbft/cometbft/pull/5946 ReadTag decoded protobuf field numbers as a platform-width int `int(v>>3)`, but the generated gogoproto unmarshaller uses `int32(wire>>3)`. On 64-bit platforms this let a crafted tag whose high bits differ (e.g. 8a 80 80 80 80 01, field 2^32+1) be classified as a different field by the pre-scan than by proto.Unmarshal As a result, the filter skips the entry while Unmarshal still decoded it as the real repeated`txs` field, slipping empty entries past the empty-entry rejection as part of the filter rule As an evidence https://github.com/cometbft/cometbft/blob/main/proto/tendermint/mempool/types.pb.go#L327 #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [x] Updated relevant documentation (`docs/` or `spec/`) and code comments Co-authored-by: dianab-cl --- CHANGELOG.md | 2 ++ internal/protowire/protowire.go | 2 +- internal/protowire/protowire_test.go | 16 ++++++++++++++++ mempool/filter_test.go | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b7588e7918..99d72696a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,8 @@ ([\#5879](https://github.com/cometbft/cometbft/pull/5879)) - `[consensus]` release cs.mtx before sending to statsMsgQueue ([\#5813](https://github.com/cometbft/cometbft/pull/5813)) +- `[mempool]` truncate proto field number to int32 in filter's ReadTag + ([\#5948](https://github.com/cometbft/cometbft/pull/5948)) - `[privval]` preempt sleep retries in privval signer client ([\#5934](https://github.com/cometbft/cometbft/pull/5934)) diff --git a/internal/protowire/protowire.go b/internal/protowire/protowire.go index 4983cbc8130..ccccb111fd1 100644 --- a/internal/protowire/protowire.go +++ b/internal/protowire/protowire.go @@ -73,7 +73,7 @@ func (c *WireCursor) ReadTag() (fieldNum, wireType int, err error) { if err != nil { return 0, 0, err } - fieldNum = int(v >> 3) + fieldNum = int(int32(v >> 3)) wireType = int(v & 0x7) if fieldNum <= 0 { return 0, 0, fmt.Errorf("%w: %d", ErrIllegalFieldNumber, fieldNum) diff --git a/internal/protowire/protowire_test.go b/internal/protowire/protowire_test.go index a258640b879..cc93a12ec70 100644 --- a/internal/protowire/protowire_test.go +++ b/internal/protowire/protowire_test.go @@ -44,6 +44,22 @@ func TestWireCursor_ReadTag_IllegalFieldNumber(t *testing.T) { require.ErrorIs(t, err, ErrIllegalFieldNumber) } +func TestWireCursor_ReadTag_FieldNumberTruncatesTo32Bit(t *testing.T) { + // Tag varint 8a 80 80 80 80 01 decodes to wire = 2^35 + 10, so + // wire>>3 == 2^32 + 1 and wire&7 == 2 (WireBytes). int32(2^32+1) == 1. + c := NewWireCursor([]byte{0x8a, 0x80, 0x80, 0x80, 0x80, 0x01}) + fieldNum, wireType, err := c.ReadTag() + require.NoError(t, err) + require.Equal(t, 1, fieldNum, "high bits above 32 must be truncated, matching int32(wire>>3)") + require.Equal(t, WireBytes, wireType) + + // Tag varint 80 80 80 80 40 decodes to wire>>3 == 2^31, and + // int32(2^31) is negative => illegal tag, exactly as gogoproto rejects it. + c = NewWireCursor([]byte{0x80, 0x80, 0x80, 0x80, 0x40}) + _, _, err = c.ReadTag() + require.ErrorIs(t, err, ErrIllegalFieldNumber) +} + func TestWireCursor_ReadLengthDelimited(t *testing.T) { c := NewWireCursor([]byte{0x03, 'a', 'b', 'c'}) b, err := c.ReadLengthDelimited() diff --git a/mempool/filter_test.go b/mempool/filter_test.go index 3a29554cce8..dbbaaddc966 100644 --- a/mempool/filter_test.go +++ b/mempool/filter_test.go @@ -113,6 +113,24 @@ func TestFilterMempoolMsgBytes_Malformed(t *testing.T) { require.Error(t, filterMempoolMsgBytes([]byte{0x0a, 0x7f}, 1024, 4096)) } +func TestFilterMempoolMsgBytes_DisguisedFieldNumberNotBypassed(t *testing.T) { + inner := []byte{ + 0x0a, 0x01, 0x41, // field 1, len 1, "A" -> real tx + 0x8a, 0x80, 0x80, 0x80, 0x80, 0x01, 0x00, // disguised field 1, len 0 -> empty tx + } + msgBytes := append([]byte{0x0a, byte(len(inner))}, inner...) // Message.txs = inner + + // Sanity check: the real parser really does decode two entries, the second + // empty + var msg protomem.Message + require.NoError(t, msg.Unmarshal(msgBytes)) + require.Len(t, msg.GetTxs().GetTxs(), 2) + require.Empty(t, msg.GetTxs().GetTxs()[1]) + + // The filter must reject it (empty transaction), matching the parser. + require.Error(t, filterMempoolMsgBytes(msgBytes, 1024, 4096)) +} + // The per-batch budget is the larger of maxTxBytes and maxBatchBytes, so a // single tx bigger than maxBatchBytes but within maxTxBytes must still pass. func TestFilterMempoolMsgBytes_BatchBudgetIsMax(t *testing.T) { From 2d1a24c6d69ea673e878a72629fe93fa7ce9b9fe Mon Sep 17 00:00:00 2001 From: JayT106 Date: Fri, 10 Jul 2026 10:56:46 -0400 Subject: [PATCH 16/32] docs(blocksync): document adaptive_sync equivocation risk for validators (#5953) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With `adaptive_sync=true`, consensus starts immediately (`consensusWaitForSync=false`) and `signAddVote` fires whenever the node is in the validator set. A catching-up validator can sign a vote for block B at height H while the blocksync ingestor concurrently commits a different block B' at H (already decided by the network). The HRS file and KMS are not sufficient backstops — the conflicting messages are at different (H,R,step) tuples so `CheckHRS` never fires. This is a known tradeoff: validators on high-performance chains (sub-1000ms blocks, high TPS) benefit from adaptive sync to avoid falling behind and being jailed. The risk is deliberate and operator-owned. ## Fix Documentation only — no behavior change: - `docs/core/block-sync.md`: add explicit equivocation risk warning to the Adaptive Sync section - `config/toml.go`: add the same warning to the `adaptive_sync` config comment --- #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `CHANGELOG.md` - [x] Updated relevant documentation (`docs/` or `spec/`) and code comments --- CHANGELOG.md | 2 ++ config/toml.go | 4 ++++ docs/core/block-sync.md | 2 ++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d72696a65..83dfff61347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### BUG FIXES +- `[blocksync]` document `adaptive_sync` equivocation risk for validator nodes + ([\#5953](https://github.com/cometbft/cometbft/pull/5953)) - `[abci]` fix socket transport missing `InsertTx` and `ReapTxs` cases in `handleRequest` and `resMatchesReq`, causing `ErrUnexpectedResponse` and node self-kill when `mempool.type = "app"` with the default socket transport diff --git a/config/toml.go b/config/toml.go index 68b03b3f41b..493a06355db 100644 --- a/config/toml.go +++ b/config/toml.go @@ -555,6 +555,10 @@ version = "{{ .BlockSync.Version }}" # Experimental Adaptive sync (bool): # # Run both BLOCKSYNC and CONSENSUS for improved liveness, connectivity, and performance. +# NOTE: On validator nodes, running consensus concurrently with blocksync while catching up +# risks equivocation — consensus can sign votes for heights where the ingestor has not yet +# committed the already-decided block. The HRS file and KMS are not sufficient backstops +# for this scenario. Only enable on validators if you understand and accept this risk. adaptive_sync = {{ .BlockSync.AdaptiveSync }} ####################################################### diff --git a/docs/core/block-sync.md b/docs/core/block-sync.md index 6595e8671d8..f32225c9e22 100644 --- a/docs/core/block-sync.md +++ b/docs/core/block-sync.md @@ -52,5 +52,7 @@ adaptive_sync = false When `adaptive_sync` is enabled, the node runs BLOCKSYNC and CONSENSUS simultaneously instead of the traditional flow where blocksync runs first and then hands off to consensus. This can improve liveness, connectivity, and performance during catch-up. Blocks are ingested through consensus internals as they are received. +> **Warning — validator equivocation risk:** With adaptive sync enabled, consensus starts immediately and `signAddVote` fires whenever the node is in the validator set. This means a catching-up validator can sign a vote for block B at height H while the blocksync ingestor concurrently commits a different block B' at H (already decided by the network). The HRS file and KMS double-sign protection are not sufficient backstops for this scenario — the conflicting messages are at different (H,R,step) tuples so `CheckHRS` never fires. Only enable `adaptive_sync` on validator nodes if you understand and accept this risk; non-validator full nodes are not affected. + If we're lagging sufficiently, we should go back to block syncing, but this is an [open issue](https://github.com/tendermint/tendermint/issues/129). From ac0361ecb9bbb5d3730d1fc2d4884f8833fe7632 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 12:23:35 +0400 Subject: [PATCH 17/32] build(deps): Bump golang.org/x/sync from 0.21.0 to 0.22.0 (#5972) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.21.0 to 0.22.0.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/sync&package-manager=go_modules&previous-version=0.21.0&new-version=0.22.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 11e8cecb0b6..b8e05cda452 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/crypto v0.53.0 golang.org/x/net v0.56.0 - golang.org/x/sync v0.21.0 + golang.org/x/sync v0.22.0 gonum.org/v1/gonum v0.17.0 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 diff --git a/go.sum b/go.sum index 95854f362af..80c6669d9e5 100644 --- a/go.sum +++ b/go.sum @@ -576,8 +576,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From cf9b254ec20c94b0e000a9ccc50714efe3305222 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:58:57 +0200 Subject: [PATCH 18/32] build(deps): Bump golang.org/x/crypto from 0.53.0 to 0.54.0 (#5970) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.53.0 to 0.54.0.
Commits
  • cdce021 go.mod: update golang.org/x dependencies
  • d9474cc openpgp: make the deprecation message more explicit
  • 7626c50 ssh: verify declared key type matches decoded key in authorized_keys
  • 0471e79 ssh/agent: enforce strict limits on DSA key parameters
  • 6435c37 ssh: sanitize client disconnect messages
  • 7d695da ssh/agent: drain channel stderr in agent forwarders
  • 5b7f841 acme/autocert: fix data race in Manager.createCert
  • 0b316e7 argon2: update RFC 9106 parameter recommendations
  • 55aec0a x509roots/fallback: update bundle
  • 5f2de1a internal: remove wycheproof tests
  • See full diff in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index b8e05cda452..179d37fd981 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/supranational/blst v0.3.16 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - golang.org/x/crypto v0.53.0 + golang.org/x/crypto v0.54.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.22.0 gonum.org/v1/gonum v0.17.0 @@ -186,12 +186,12 @@ require ( go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.45.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 80c6669d9e5..06ae9f4c9bc 100644 --- a/go.sum +++ b/go.sum @@ -530,8 +530,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= @@ -542,8 +542,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -606,10 +606,10 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE= -golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 h1:nwGZBCt+FnXUrGsj5vjzAsEmkcaFvd82BbOjECiFYZc= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -617,8 +617,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -628,8 +628,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -642,8 +642,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 6f03c7c59574b28792f72ff03b15493a61cba175 Mon Sep 17 00:00:00 2001 From: JayT106 Date: Mon, 13 Jul 2026 09:09:16 -0400 Subject: [PATCH 19/32] fix(mempool): include proto framing overhead in AppReactor batch size to prevent peer teardown (#5956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Problem `AppReactor.chunkTxs` sized batches by raw `len(tx)` bytes, ignoring the 2–4 bytes of protobuf wire overhead each entry adds (1-byte field tag + varint length prefix). A batch of small txs near `MaxTxBytes` serializes to `MaxTxBytes + N*(2–4)` bytes, exceeding the receiver's `RecvMessageCapacity`. The connection layer returns "received message exceeds available capacity" and tears down the peer. Example overflows (default `MaxTxBytes = 1 MB`): - 1 000-byte txs: ~+2 564 bytes over capacity - 1-byte txs: ~+2 097 149 bytes over capacity Second issue: `GetChannels` sized `RecvMessageCapacity` for a single `MaxTxBytes`-sized tx while `OnStart` used `MaxBatchBytes` when set, so configs with `MaxBatchBytes > MaxTxBytes` would overflow at the capacity check itself. ### Fix - Add `internal/protowire.RepeatedBytesEntrySize(dataLen int) int` — exact wire bytes proto adds per `repeated bytes` entry (1-byte tag + varint(len)), verified against `proto.Size` at all varint boundary values; panics on negative input. - `chunkTxs` accounts for this overhead per tx so no encoded batch exceeds `RecvMessageCapacity`. - Extract `effectiveMaxBatchBytes()` to deduplicate the `MaxBatchBytes`/`MaxTxBytes` fallback logic shared by `OnStart` and `GetChannels`. - `GetChannels` derives `RecvMessageCapacity` from `effectiveMaxBatchBytes()`, keeping the advertised capacity in sync with the actual send ceiling. --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: Dmitry S <11892559+swift1337@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 + internal/protowire/protowire.go | 16 +++++ internal/protowire/protowire_test.go | 19 ++++++ mempool/app_reactor.go | 26 +++++--- mempool/app_reactor_test.go | 90 +++++++++++++++++++++++++++- 5 files changed, 143 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83dfff61347..9aee8c87987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### BUG FIXES +- `[mempool]` include proto framing overhead in AppReactor batch size to prevent peer teardown + ([\#5956](https://github.com/cometbft/cometbft/pull/5956)) - `[blocksync]` document `adaptive_sync` equivocation risk for validator nodes ([\#5953](https://github.com/cometbft/cometbft/pull/5953)) - `[abci]` fix socket transport missing `InsertTx` and `ReapTxs` cases in diff --git a/internal/protowire/protowire.go b/internal/protowire/protowire.go index ccccb111fd1..e320b7542db 100644 --- a/internal/protowire/protowire.go +++ b/internal/protowire/protowire.go @@ -116,6 +116,22 @@ func (c *WireCursor) SkipField(wireType int) error { } } +// RepeatedBytesEntrySize returns the number of wire bytes proto encodes for one +// entry in a repeated bytes field: 1-byte field tag + varint(dataLen). +// dataLen must be >= 0; Go's len() guarantees this for all practical callers. +func RepeatedBytesEntrySize(dataLen int) int { + if dataLen < 0 { + panic("protowire: negative dataLen") + } + n := 1 /* tag */ + 1 /* min varint byte */ + v := uint64(dataLen) >> 7 + for v > 0 { + n++ + v >>= 7 + } + return n +} + // advance moves the cursor forward by n bytes, bounds-checked. func (c *WireCursor) advance(n int) error { end := c.pos + n diff --git a/internal/protowire/protowire_test.go b/internal/protowire/protowire_test.go index cc93a12ec70..1f6559d01d8 100644 --- a/internal/protowire/protowire_test.go +++ b/internal/protowire/protowire_test.go @@ -106,3 +106,22 @@ func TestWireCursor_SkipField_OutOfBounds(t *testing.T) { c := NewWireCursor([]byte{1, 2}) require.ErrorIs(t, c.SkipField(WireFixed64), ErrOutOfBounds) } + +func TestRepeatedBytesEntrySize(t *testing.T) { + // Verified against proto.Size on Txs{Txs: [][]byte{make([]byte, n)}}. + for _, tt := range []struct { + dataLen int + want int + }{ + {0, 2}, // tag(1) + varint(0)=1 + {1, 2}, // tag(1) + varint(1)=1 + {127, 2}, // last 1-byte varint + {128, 3}, // first 2-byte varint + {1000, 3}, + {16383, 3}, // last 2-byte varint + {16384, 4}, // first 3-byte varint + {1048576, 4}, + } { + require.Equal(t, tt.want, RepeatedBytesEntrySize(tt.dataLen), "dataLen=%d", tt.dataLen) + } +} diff --git a/mempool/app_reactor.go b/mempool/app_reactor.go index 0fa30788000..155956a4a84 100644 --- a/mempool/app_reactor.go +++ b/mempool/app_reactor.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/internal/protowire" "github.com/cometbft/cometbft/p2p" protomem "github.com/cometbft/cometbft/proto/tendermint/mempool" "github.com/cometbft/cometbft/types" @@ -75,10 +76,7 @@ func (r *AppReactor) OnStart() error { // fallback to max tx bytes if max batch bytes is not set // most chains use 1MB which will definitely fit many small txs - maxBatchSizeBytes := r.config.MaxTxBytes - if r.config.MaxBatchBytes > 0 { - maxBatchSizeBytes = r.config.MaxBatchBytes - } + maxBatchSizeBytes := r.effectiveMaxBatchBytes() if !r.switchedOn.Load() { select { @@ -103,10 +101,10 @@ func (r *AppReactor) OnStop() { // GetChannels implements p2p.BaseReactor. func (r *AppReactor) GetChannels() []*p2p.ChannelDescriptor { - largestTx := make([]byte, r.config.MaxTxBytes) + // Mirror effectiveMaxBatchBytes so RecvMessageCapacity is always >= the largest Message we send. batchMsg := protomem.Message{ Sum: &protomem.Message_Txs{ - Txs: &protomem.Txs{Txs: [][]byte{largestTx}}, + Txs: &protomem.Txs{Txs: [][]byte{make([]byte, r.effectiveMaxBatchBytes())}}, }, } @@ -120,6 +118,14 @@ func (r *AppReactor) GetChannels() []*p2p.ChannelDescriptor { } } +// effectiveMaxBatchBytes returns MaxBatchBytes when set, else MaxTxBytes. +func (r *AppReactor) effectiveMaxBatchBytes() int { + if r.config.MaxBatchBytes > 0 { + return r.config.MaxBatchBytes + } + return r.config.MaxTxBytes +} + // WaitSync used for backward compatibility with external callers func (r *AppReactor) WaitSync() bool { return !r.enabled() @@ -236,7 +242,11 @@ func txsFromEnvelope(e p2p.Envelope) ([]types.Tx, error) { } } -// chunkTxs chunks transactions into batches of maxBatchSizeBytes +// chunkTxs chunks transactions into batches of maxBatchSizeBytes. +// Each tx contributes len(tx) + proto framing (field tag + varint length prefix) +// to the serialized Txs message size; we account for that overhead here so the +// encoded batch never exceeds the receiver's RecvMessageCapacity. +// // example: [tx1, tx2, tx3, tx4, tx5, ...] -> [[tx1, tx2], [tx3, tx4], [tx5], ...] // // note: we can optimize []types.Txs to [][][]byte + have less allocs, @@ -253,7 +263,7 @@ func chunkTxs(txs types.Txs, maxBatchSizeBytes int) []types.Txs { lastChunk := types.Txs{} for _, tx := range txs { - txSizeBytes := len(tx) + txSizeBytes := len(tx) + protowire.RepeatedBytesEntrySize(len(tx)) // tx won't fit into chunk, add current chunk to chunks and start a new one if (lastChunkSizeBytes + txSizeBytes) > maxBatchSizeBytes { diff --git a/mempool/app_reactor_test.go b/mempool/app_reactor_test.go index d77ebc05a20..c18030b3d1e 100644 --- a/mempool/app_reactor_test.go +++ b/mempool/app_reactor_test.go @@ -9,15 +9,85 @@ import ( abcimock "github.com/cometbft/cometbft/abci/client/mocks" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/internal/protowire" "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/libs/rand" "github.com/cometbft/cometbft/p2p" + protomem "github.com/cometbft/cometbft/proto/tendermint/mempool" "github.com/cometbft/cometbft/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestAppReactor(t *testing.T) { + t.Run("effectiveMaxBatchBytes", func(t *testing.T) { + for _, tt := range []struct { + name string + maxTxBytes int + maxBatchBytes int + want int + }{ + {"MaxBatchBytes unset falls back to MaxTxBytes", 1000, 0, 1000}, + {"MaxBatchBytes set and larger than MaxTxBytes", 1000, 5000, 5000}, + {"MaxBatchBytes set and smaller than MaxTxBytes", 1000, 500, 500}, + } { + t.Run(tt.name, func(t *testing.T) { + r := &AppReactor{config: &config.MempoolConfig{ + MaxTxBytes: tt.maxTxBytes, + MaxBatchBytes: tt.maxBatchBytes, + }} + require.Equal(t, tt.want, r.effectiveMaxBatchBytes()) + }) + } + }) + + // Regression test for the peer-teardown bug: every batch produced by chunkTxs + // must serialize to a Message that fits within RecvMessageCapacity. + t.Run("GetChannels", func(t *testing.T) { + for _, tt := range []struct { + name string + maxTxBytes int + maxBatchBytes int + txSize int + txCount int + }{ + // Many 1-byte txs: worst-case proto framing ratio (2 overhead bytes per 1 data byte). + {"1-byte txs default batch limit", 10000, 0, 1, 10000}, + // Txs at the 127-byte boundary (varint transitions from 1 to 2 bytes at 128). + {"127-byte txs default batch limit", 10000, 0, 127, 100}, + {"128-byte txs default batch limit", 10000, 0, 128, 100}, + // Large txs near MaxTxBytes. + {"large txs default batch limit", 10000, 0, 9000, 5}, + // MaxBatchBytes set larger than MaxTxBytes. + {"small txs explicit batch limit", 1000, 5000, 1, 5000}, + } { + t.Run(tt.name, func(t *testing.T) { + r := &AppReactor{config: &config.MempoolConfig{ + MaxTxBytes: tt.maxTxBytes, + MaxBatchBytes: tt.maxBatchBytes, + }} + + txs := make(types.Txs, tt.txCount) + for i := range txs { + txs[i] = make(types.Tx, tt.txSize) + } + + recvCap := r.GetChannels()[0].RecvMessageCapacity + batches := chunkTxs(txs, r.effectiveMaxBatchBytes()) + + for i, batch := range batches { + msg := &protomem.Message{ + Sum: &protomem.Message_Txs{ + Txs: &protomem.Txs{Txs: batch.ToSliceOfBytes()}, + }, + } + msgSize := msg.Size() + require.LessOrEqual(t, msgSize, recvCap, + "batch %d serialized size %d exceeds RecvMessageCapacity %d", i, msgSize, recvCap) + } + }) + } + }) const ( timeout = 5 * time.Second interval = 200 * time.Millisecond @@ -172,9 +242,11 @@ func TestChunkTxs(t *testing.T) { output: [][]int{{100}}, }, { + // Two 100-byte txs each cost 102 effective bytes (2 bytes proto framing). + // 204 fits exactly two; a third would overflow. name: "basic", input: []int{100, 100, 100}, - size: 200, + size: 204, output: [][]int{{100, 100}, {100}}, }, { @@ -184,10 +256,12 @@ func TestChunkTxs(t *testing.T) { output: [][]int{{100}, {100}, {100}}, }, { + // With 2-byte proto framing per tx, [20,30] sums to 54 effective bytes; + // adding 50 would reach 106 > 100, so [50] starts a new chunk with [2]. name: "edge-case", input: []int{101, 20, 30, 50, 2, 102, 3}, size: 100, - output: [][]int{{101}, {20, 30, 50}, {2}, {102}, {3}}, + output: [][]int{{101}, {20, 30}, {50, 2}, {102}, {3}}, }, } { t.Run(tt.name, func(t *testing.T) { @@ -207,6 +281,18 @@ func TestChunkTxs(t *testing.T) { for i, chunk := range actual { require.Equal(t, len(expected[i]), len(chunk), "chunk length mismatch (#%d)", i) + + // Verify each multi-tx chunk's effective proto size stays within the limit. + // Single-tx chunks are exempt: an oversized lone tx must still be forwarded. + if len(chunk) <= 1 { + continue + } + effectiveSize := 0 + for _, tx := range chunk { + effectiveSize += len(tx) + protowire.RepeatedBytesEntrySize(len(tx)) + } + require.LessOrEqual(t, effectiveSize, tt.size, + "chunk #%d effective size %d exceeds limit %d", i, effectiveSize, tt.size) } }) } From 1734b3d7f3878c57ddf58e7c44776a942872bbae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:47:51 +0200 Subject: [PATCH 20/32] build(deps): Bump golang.org/x/net from 0.56.0 to 0.57.0 (#5974) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.56.0 to 0.57.0.
Commits
  • b8f09f6 go.mod: update golang.org/x dependencies
  • f05f21b idna: reject all-ASCII xn-- labels on all Go versions
  • 0f748cf internal/http3: clean up stream I/O methods usages in tests
  • 0bb961e internal/http3: add net/http.ResponseController support
  • 0ca694d webdav: document Dir's lack of defense against filesystem modification
  • bd5f1dc http2: initialize Transport on NewClientConn
  • 488ff63 bpf: add security considerations to package docs
  • 93d1f25 xsrftoken: avoid token collisions
  • 5a3baee internal/http3: prevent panic in QPACK decoder due to overflow
  • See full diff in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 179d37fd981..0699ca6d179 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/supranational/blst v0.3.16 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 golang.org/x/crypto v0.54.0 - golang.org/x/net v0.56.0 + golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 gonum.org/v1/gonum v0.17.0 google.golang.org/grpc v1.81.1 diff --git a/go.sum b/go.sum index 06ae9f4c9bc..d1fc1986ff4 100644 --- a/go.sum +++ b/go.sum @@ -565,8 +565,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 6ac238be8472f465c87460c0bc674d73ffff77d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:49:09 +0200 Subject: [PATCH 21/32] build(deps): Bump github.com/prometheus/common from 0.68.1 to 0.70.0 (#5973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/prometheus/common](https://github.com/prometheus/common) from 0.68.1 to 0.70.0.
Release notes

Sourced from github.com/prometheus/common's releases.

v0.70.0

What's Changed

New Contributors

Full Changelog: https://github.com/prometheus/common/compare/v0.69.0...v0.70.0

v0.69.0

What's Changed

Full Changelog: https://github.com/prometheus/common/compare/v0.68.1...v0.69.0

Changelog

Sourced from github.com/prometheus/common's changelog.

v0.70.0 / 2026-07-10

Enhancements

  • route: add support for the QUERY HTTP method. #932

Bugfixes

  • config: fix TLSVersion.String() printing a pointer address instead of the numeric version for unknown TLS versions. #929

Internal

  • expfmt: add BenchmarkConvertMetricFamily comparing the Prometheus text and OpenMetrics 1.0 encoders. #943
  • Update Go dependencies. #933 #934
  • Synchronize common files from prometheus/prometheus. #923 #927 #930 #937
  • Update GitHub Actions. #938 #939 #940 #941 #942

Full Changelog: https://github.com/prometheus/common/compare/v0.69.0...v0.70.0

v0.69.0 / 2026-06-17

Security / behavior changes

  • config: credentials are no longer forwarded across cross-host redirects. When FollowRedirects is enabled, the HTTP client now strips Authorization, Cookie, Proxy-Authorization and other sensitive headers, and skips basic-auth, bearer-token and OAuth2 credentials, when a redirect points to a different host. This aligns with Go's net/http behavior. Callers that relied on credentials being sent to a redirect target on another host will need to target that host directly. #901 #920 #921
  • config: LoadHTTPConfigFile now resolves relative file paths (e.g. *_file credentials, http_headers files) against the config file's own directory instead of its parent directory. Configs that worked around the old behavior by prefixing paths with the config's directory name must drop that prefix. #925

Bugfixes

  • expfmt: fix nil pointer panic when parsing empty braces {}. #922
  • model: fix Time.UnmarshalJSON for larger negative numbers. #918

Performance

  • model: reduce allocations in Time.UnmarshalJSON. #918

Internal

  • Synchronize common files from prometheus/prometheus. #917
  • Modernize Go. #919

Full Changelog: https://github.com/prometheus/common/compare/v0.68.1...v0.69.0

v0.67.2 / 2025-10-28

What's Changed

New Contributors

... (truncated)

Commits
  • 5eff7a8 Merge pull request #941 from prometheus/dependabot/github_actions/actions/che...
  • a23c5b3 Merge pull request #939 from prometheus/dependabot/github_actions/ossf/scorec...
  • 9e1eb87 Merge pull request #942 from prometheus/dependabot/github_actions/actions/upl...
  • ab2736d Merge pull request #940 from prometheus/dependabot/github_actions/actions/set...
  • e596873 Merge pull request #938 from prometheus/dependabot/github_actions/codeql-ac07...
  • 97c066a Add BenchmarkConvertMetricFamily comparing Prometheus text and OpenMetrics 1....
  • 62e9d0f Merge pull request #932 from roidelapluie/roidelapluie/route-query
  • 6c31239 build(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1
  • 755e9d6 build(deps): bump actions/checkout from 3.1.0 to 7.0.0
  • 556d740 build(deps): bump actions/setup-go from 6.2.0 to 6.5.0
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/prometheus/common&package-manager=go_modules&previous-version=0.68.1&new-version=0.70.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0699ca6d179..8e6f389bf63 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.68.1 + github.com/prometheus/common v0.70.0 github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 github.com/rs/cors v1.11.1 github.com/sasha-s/go-deadlock v0.3.9 @@ -158,7 +158,7 @@ require ( github.com/pion/webrtc/v4 v4.1.2 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.21.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.1 // indirect github.com/quic-go/webtransport-go v0.10.0 // indirect diff --git a/go.sum b/go.sum index d1fc1986ff4..c3f87de374d 100644 --- a/go.sum +++ b/go.sum @@ -402,10 +402,10 @@ github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UH github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY= -github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= +github.com/prometheus/procfs v0.21.0 h1:Qh/e6TlBjZf+XLLqNCqFGmCU6Kj/2Bu7kj3oAc0UnXc= +github.com/prometheus/procfs v0.21.0/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= From 8715446eae412602c6e5fccd96317452d2943929 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 09:08:47 +0200 Subject: [PATCH 22/32] build(deps): Bump slackapi/slack-github-action from 3.0.3 to 4.0.0 (#5980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 3.0.3 to 4.0.0.
Release notes

Sourced from slackapi/slack-github-action's releases.

Slack GitHub Action v4.0.0

Major Changes

  • b1974f0: build: parse yaml with more strict multiline indentation rules

    Internal dependencies of js-yaml@v5 make YAML parsing more strict and compliant with the YAML specification. Indentation is now required for values that span multiple lines against the base value.

    See the YAML line prefixes spec for the expected indentation rule:

      channel: "C0123"
      text: "first line
    
    • second line"
    • second line"

Patch Changes

  • 654bb72: chore: provide global fetch proxied configurations with updates to web api and webhook packages

Slack GitHub Action v3.0.5

Patch Changes

  • 96fddbe: fix: revert multiline yaml parsing indentation change

Slack GitHub Action v3.0.4

Patch Changes

Changelog

Sourced from slackapi/slack-github-action's changelog.

4.0.0

Major Changes

  • b1974f0: build: parse yaml with more strict multiline indentation rules

    Internal dependencies of js-yaml@v5 make YAML parsing more strict and compliant with the YAML specification. Indentation is now required for values that span multiple lines against the base value.

    See the YAML line prefixes spec for the expected indentation rule:

      channel: "C0123"
      text: "first line
    
    • second line"
    • second line"

Patch Changes

  • 654bb72: chore: provide global fetch proxied configurations with updates to web api and webhook packages

3.0.5

Patch Changes

  • 96fddbe: fix: revert multiline yaml parsing indentation change

3.0.4

Patch Changes

Commits
  • dcb1066 chore: release
  • 53861e0 chore: release (#645)
  • b1974f0 build!: parse yaml with more strict multiline indentation rules (#640)
  • 947ed06 build(deps): bump undici from 7.28.0 to 8.7.0 (#653)
  • 03922a9 chore: track undici-types to the resolved undici version (#652)
  • 31d473e build(deps-dev): bump typescript from 6.0.3 to 7.0.2 (#651)
  • 3ca6997 build(deps-dev): bump sinon and @​types/sinon (#649)
  • 26a5ad3 build(deps): bump actions/setup-node from 6.4.0 to 7.0.0 (#647)
  • 5092efb build(deps-dev): bump @​biomejs/biome from 2.5.3 to 2.5.4 (#650)
  • 3548c3e build(deps): bump slackapi/slack-github-action from 3.0.3 to 3.0.5 (#646)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=slackapi/slack-github-action&package-manager=github_actions&previous-version=3.0.3&new-version=4.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e-nightly-main.yml | 2 +- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index 45239279870..cb685dc5a13 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v3.0.3 + uses: slackapi/slack-github-action@v4.0.0 env: BRANCH: ${{ github.ref_name }} RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index b2a454d2485..02990d70888 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -82,7 +82,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack on failure - uses: slackapi/slack-github-action@v3.0.3 + uses: slackapi/slack-github-action@v4.0.0 env: BRANCH: ${{ github.ref_name }} CRASHERS: ${{ needs.fuzz-nightly-test.outputs.crashers-count }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 34486f37cd1..72f4d5c69d7 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack upon pre-release - uses: slackapi/slack-github-action@v3.0.3 + uses: slackapi/slack-github-action@v4.0.0 env: RELEASE_URL: "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}" with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99b9573f071..3084cc8e46b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify Slack upon release - uses: slackapi/slack-github-action@v3.0.3 + uses: slackapi/slack-github-action@v4.0.0 env: RELEASE_URL: "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}" with: From 17bb3853e4e374aa5bc84341588a346f6e8f9b2e Mon Sep 17 00:00:00 2001 From: JayT106 Date: Tue, 21 Jul 2026 03:38:58 -0400 Subject: [PATCH 23/32] fix(blocksync): tolerate late BlockResponse from honest peers after switching to consensus (#5959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Problem `FilterMsgBytes` returned an error for any `BlockResponse` once the block pool had stopped (`!r.pool.IsRunning()`). That error panicked inside `p2p/peer.go`'s `onReceive` and was recovered into `StopPeerForError` — a hard disconnect. Because requests for the last blocks are typically still in-flight when the catch-up ticker fires and `pool.Stop()` is called, both request targets (primary and secondary peer) were disconnected on every normal sync completion, exactly when the node needs them for consensus gossip. Root cause introduced by #5860 (`FilterMsgBytes`). Not covered by #5950, which handles the second-peer race while the pool is still running. ### Fix Split the single `||` guard into two branches: - `!r.enabled.Load()` — node never ran blocksync; any `BlockResponse` is genuinely unsolicited → reject (unchanged behaviour) - `!r.pool.IsRunning()` — pool stopped for consensus switch; requests we sent before the transition are still in-flight; the peers are honest → allow, but still run `validateMaxVotes` on the response The `AddBlock` call that follows returns `"already committed block"` and logs it; no peer state is mutated and no disconnect occurs. `pool.sendError` is already a no-op once the pool is stopped (it guards on `!pool.IsRunning()`), so the pre-fix punishment for genuinely out-of-window responses in this window was already silently dropped the moment `poolEventsRoutine` exited. The post-stop branch skips the `HasPendingRequestFrom` peer-identity check (any peer, not just the one we solicited, may legitimately answer a stale request) but still enforces `validateMaxVotes` — once the pool stops, any connected peer can reach this path, and the commit-signature cap exists to prevent a heap-amplification DoS regardless of sender. Caught by review on this PR; added a dedicated regression test (`rejects oversized BlockResponse after pool stops for consensus switch`) so an oversized response is still rejected in this window. --- #### PR checklist - [x] Tests written/updated - [x] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --------- Co-authored-by: dianab-cl --- CHANGELOG.md | 2 + blocksync/reactor.go | 11 ++++- blocksync/reactor_test.go | 95 +++++++++++++++++++++++++++++++++------ 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aee8c87987..adcb8529fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### BUG FIXES +- `[blocksync]` tolerate late BlockResponse from honest peers after switching to consensus + ([\#5959](https://github.com/cometbft/cometbft/pull/5959)) - `[mempool]` include proto framing overhead in AppReactor batch size to prevent peer teardown ([\#5956](https://github.com/cometbft/cometbft/pull/5956)) - `[blocksync]` document `adaptive_sync` equivocation risk for validator nodes diff --git a/blocksync/reactor.go b/blocksync/reactor.go index 9cac8978a22..510d7225df8 100644 --- a/blocksync/reactor.go +++ b/blocksync/reactor.go @@ -367,10 +367,17 @@ func (r *Reactor) FilterMsgBytes(chID byte, src p2p.Peer, msgBytes []byte) error return nil } - // blocksync not running, we should not be getting a BlockResponse - if !r.enabled.Load() || !r.pool.IsRunning() { + // Never ran blocksync on this node — any BlockResponse is unsolicited. + if !r.enabled.Load() { return errors.New("unsolicited BlockResponse: blocksync not active") } + // Pool has stopped (switched to consensus). Requests we sent before the + // transition are still in flight; the peers are honest and must not be + // disconnected for answering our own requests. Still enforce the sig-count + // guard below, since any connected peer can reach this path now. + if !r.pool.IsRunning() { + return validateMaxVotes(stub.BlockResponse) + } // ensure we have an outstanding request to this peer if !r.pool.HasPendingRequestFrom(src.ID()) { diff --git a/blocksync/reactor_test.go b/blocksync/reactor_test.go index 4ff06418bf9..962e9723bfd 100644 --- a/blocksync/reactor_test.go +++ b/blocksync/reactor_test.go @@ -555,6 +555,34 @@ func TestFilterMsgBytes(t *testing.T) { bytesFn: blockResponseBytes, expectErr: "blocksync not active", }, + { + // After catching up, the pool stops but in-flight responses from our + // own requests arrive after the switch to consensus. The peer is + // honest and must not be disconnected. + name: "allows late BlockResponse after pool stops for consensus switch", + setup: func(t *testing.T) *Reactor { + r := newFilterReactor(t, true) + require.NoError(t, r.pool.Stop()) + return r + }, + chID: BlocksyncChannel, + peer: unexpected, + bytesFn: blockResponseBytes, + }, + { + // Any connected peer can reach this path once the pool is stopped, + // so the sig-count guard must still apply here. + name: "rejects oversized BlockResponse after pool stops for consensus switch", + setup: func(t *testing.T) *Reactor { + r := newFilterReactor(t, true) + require.NoError(t, r.pool.Stop()) + return r + }, + chID: BlocksyncChannel, + peer: unexpected, + bytesFn: func(t *testing.T) []byte { return blockResponseBytesWithSigs(t, types.MaxVotesCount+1, 0) }, + expectErr: "too many commit signatures", + }, { name: "rejects unsolicited BlockResponse with no requesters", setup: func(t *testing.T) *Reactor { return newFilterReactor(t, true) }, @@ -607,19 +635,6 @@ func TestFilterMsgBytes(t *testing.T) { peer: "any", bytesFn: func(*testing.T) []byte { return nil }, }, - { - name: "rejects BlockResponse after pool stopped", - setup: func(t *testing.T) *Reactor { - r := newFilterReactor(t, true) - seedRequester(r, 1, expected) - require.NoError(t, r.pool.Stop()) - return r - }, - chID: BlocksyncChannel, - peer: expected, - bytesFn: blockResponseBytes, - expectErr: "blocksync not active", - }, { name: "allows BlockResponse at MaxVotesCount commit signatures", setup: func(t *testing.T) *Reactor { @@ -1000,3 +1015,57 @@ func TestEnableRecomputesMaxPeerHeight(t *testing.T) { _ = r.Enable(sm.State{LastBlockHeight: 100}) require.Equal(t, int64(200), pool.MaxPeerHeight()) } + +func TestPeerNotDisconnectedOnLateBlockResponseAfterConsensusSwitch(t *testing.T) { + config = test.ResetTestRoot("blocksync_reactor_test") + defer os.RemoveAll(config.RootDir) + + const nBlocks = int64(10) + genDoc, privVals := genesisDocWithValsPowers([]int64{30}) + + servingPair := newReactor(t, log.TestingLogger(), genDoc, privVals, nBlocks) + defer func() { + require.NoError(t, servingPair.reactor.Stop()) + require.NoError(t, servingPair.app.Stop()) + }() + + syncingPair := newReactor(t, log.TestingLogger(), genDoc, privVals, 0) + syncingPair.reactor.intervalSwitchToConsensus = 20 * time.Millisecond + defer func() { + require.NoError(t, syncingPair.reactor.Stop()) + require.NoError(t, syncingPair.app.Stop()) + }() + + switches := p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { + if i == 0 { + s.AddReactor("BLOCKSYNC", syncingPair.reactor) + } else { + s.AddReactor("BLOCKSYNC", servingPair.reactor) + } + return s + }, p2p.Connect2Switches) + + require.Eventually(t, func() bool { + return !syncingPair.reactor.pool.IsRunning() + }, 30*time.Second, 5*time.Millisecond, "syncing pool did not stop") + + // Pool stopped (consensus switch). Simulate a late in-flight response by + // sending block 1 from the serving node — exercises the onReceive → + // FilterMsgBytes path in p2p/peer.go. + block := servingPair.reactor.store.LoadBlock(1) + require.NotNil(t, block) + bl, err := block.ToProto() + require.NoError(t, err) + + peers := switches[1].Peers().Copy() + require.Len(t, peers, 1) + require.True(t, peers[0].TrySend(p2p.Envelope{ + ChannelID: BlocksyncChannel, + Message: &bcproto.BlockResponse{Block: bl}, + }), "message must be queued to exercise the filter path") + + require.Never(t, func() bool { + return switches[0].Peers().Size() != 1 + }, 200*time.Millisecond, 5*time.Millisecond, + "serving peer was incorrectly disconnected by a late in-flight BlockResponse") +} From c9111462e85fd0fadecc9e272339dbccf42ab7fc Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 21 Jul 2026 11:38:44 -0500 Subject: [PATCH 24/32] feat: Update DefaultBlockParams (#5987) --- Changes DefaultBlockParams to a higher MaxBytes value in order to account for the larger size of mldsa65 signatures. Also adds comments to ensure users are aware that incorrect values can lead to chain halts and this is the expected behaviour. --- CHANGELOG.md | 2 ++ types/block.go | 9 +++++++-- types/params.go | 18 ++++++++++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adcb8529fa0..ac68b525674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,8 @@ ([\#5866](https://github.com/cometbft/cometbft/pull/5866)) - `[mempool]` Implement `MsgBytesFilter` in Reactor to prevent heap amplification attack ([\#5946](https://github.com/cometbft/cometbft/pull/5946)) +- `[types]` Update default max block bytes param to account for increased signature size of mldsa65. + ([\#5987](https://github.com/cometbft/cometbft/pull/5987)) ### FEATURES diff --git a/types/block.go b/types/block.go index f8aeb5eaa09..efdd4b6fcac 100644 --- a/types/block.go +++ b/types/block.go @@ -280,7 +280,11 @@ func BlockFromProto(bp *cmtproto.Block) (*Block, error) { // MaxDataBytes returns the maximum size of block's data. // -// XXX: Panics on negative result. +// XXX: Panics on negative result. This halts chains whose Block.MaxBytes is +// too small to fit the worst-case commit — reachable with large-signature key +// types such as ml_dsa_65 (~3.4KB per validator) — and that is the expected +// behavior: the misconfiguration must be fixed by raising Block.MaxBytes, not +// papered over by producing blocks with no room for data. func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxOverheadForBlock - @@ -302,7 +306,8 @@ func MaxDataBytes(maxBytes, evidenceBytes int64, valsCount int) int64 { // MaxDataBytesNoEvidence returns the maximum size of block's data when // evidence count is unknown (will be assumed to be 0). // -// XXX: Panics on negative result. +// XXX: Panics on negative result. Halts misconfigured chains; expected +// behavior — see MaxDataBytes. func MaxDataBytesNoEvidence(maxBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxOverheadForBlock - diff --git a/types/params.go b/types/params.go index 3d3af2bb812..5278ff141d4 100644 --- a/types/params.go +++ b/types/params.go @@ -57,6 +57,13 @@ type ConsensusParams struct { // BlockParams define limits on the block size and gas plus minimum time // between blocks. +// +// MaxBytes must cover the block's fixed overhead, header, evidence, and the +// worst-case commit (MaxCommitBytes), which grows with validator count and +// signature size. With large-signature key types such as ml_dsa_65 (3309-byte +// signatures, ~3.4KB per validator), an undersized MaxBytes leaves no room for +// data and block construction panics. Size it for the largest expected +// validator set when enabling such key types. type BlockParams struct { MaxBytes int64 `json:"max_bytes"` MaxGas int64 `json:"max_gas"` @@ -71,6 +78,10 @@ type EvidenceParams struct { // ValidatorParams restrict the public key types validators can use. // NOTE: uses ABCI pubkey naming, not Amino names. +// +// Enabling large-signature key types (e.g. ml_dsa_65) inflates the +// per-validator commit reserve; adjust Block.MaxBytes accordingly (see +// BlockParams). type ValidatorParams struct { PubKeyTypes []string `json:"pub_key_types"` } @@ -116,10 +127,13 @@ func DefaultConsensusParams() *ConsensusParams { } } -// DefaultBlockParams returns a default BlockParams. +// DefaultBlockParams returns a default BlockParams. MaxBytes budgets 21MiB +// for data plus the worst-case commit for a maximum-size validator set +// (MaxVotesCount validators at MaxSignatureSize), so the default stays valid +// for any pub key type and validator count. func DefaultBlockParams() BlockParams { return BlockParams{ - MaxBytes: 22020096, // 21MB + MaxBytes: 22020096 + MaxCommitBytes(MaxVotesCount), // ~53MiB MaxGas: -1, } } From 1c63985b272163309312dff5fc92f85270df689d Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 21 Jul 2026 18:04:12 -0500 Subject: [PATCH 25/32] fix: Update privval max remote signer size (#5985) --- Updates the privval's max message size to account for larger signature size of mldsa keys. #### PR checklist - [ ] Tests written/updated - [ ] Changelog entry added in `CHANGELOG.md` - [ ] Updated relevant documentation (`docs/` or `spec/`) and code comments --- CHANGELOG.md | 2 ++ privval/signer_client_test.go | 42 +++++++++++++++++++++++++++++++++++ privval/signer_endpoint.go | 8 ++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac68b525674..3bf1c1ca689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,8 @@ ([\#5866](https://github.com/cometbft/cometbft/pull/5866)) - `[mempool]` Implement `MsgBytesFilter` in Reactor to prevent heap amplification attack ([\#5946](https://github.com/cometbft/cometbft/pull/5946)) +- `[privval]` Dynamically calculate privval maxRemoteSignerMsgSize. + ([\#5985](https://github.com/cometbft/cometbft/pull/5985)) - `[types]` Update default max block bytes param to account for increased signature size of mldsa65. ([\#5987](https://github.com/cometbft/cometbft/pull/5987)) diff --git a/privval/signer_client_test.go b/privval/signer_client_test.go index 91fe69c3094..6a8d66d98aa 100644 --- a/privval/signer_client_test.go +++ b/privval/signer_client_test.go @@ -197,6 +197,48 @@ func TestSignerVote(t *testing.T) { } } +// A precommit carrying a max-size vote extension (plus two signatures) far +// exceeds the old 10 KiB remote signer message cap; it must round-trip. +func TestSignerVoteMaxSizeExtension(t *testing.T) { + for _, tc := range getSignerTestCases(t) { + ts := time.Now() + hash := cmtrand.Bytes(tmhash.Size) + valAddr := cmtrand.Bytes(crypto.AddressSize) + ext := cmtrand.Bytes(types.MaxVoteExtensionSize) + newVote := func() *types.Vote { + return &types.Vote{ + Type: cmtproto.PrecommitType, + Height: 1, + Round: 2, + BlockID: types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: 2}}, + Timestamp: ts, + ValidatorAddress: valAddr, + ValidatorIndex: 1, + Extension: ext, + } + } + want, have := newVote(), newVote() + + t.Cleanup(func() { + if err := tc.signerServer.Stop(); err != nil { + t.Error(err) + } + }) + t.Cleanup(func() { + if err := tc.signerClient.Close(); err != nil { + t.Error(err) + } + }) + + wantpb, havepb := want.ToProto(), have.ToProto() + require.NoError(t, tc.mockPV.SignVote(tc.chainID, wantpb)) + require.NoError(t, tc.signerClient.SignVote(tc.chainID, havepb)) + + assert.Equal(t, wantpb.Signature, havepb.Signature) + assert.Equal(t, wantpb.ExtensionSignature, havepb.ExtensionSignature) + } +} + func TestSignerVoteResetDeadline(t *testing.T) { for _, tc := range getSignerTestCases(t) { ts := time.Now() diff --git a/privval/signer_endpoint.go b/privval/signer_endpoint.go index 84dbde8f355..6489c9925b8 100644 --- a/privval/signer_endpoint.go +++ b/privval/signer_endpoint.go @@ -9,12 +9,19 @@ import ( "github.com/cometbft/cometbft/libs/service" cmtsync "github.com/cometbft/cometbft/libs/sync" privvalproto "github.com/cometbft/cometbft/proto/tendermint/privval" + "github.com/cometbft/cometbft/types" ) const ( defaultTimeoutReadWriteSeconds = 5 ) +// maxRemoteSignerMsgSize bounds messages read from the remote signer +// connection. The largest legitimate message is a precommit vote carrying a +// max-size extension and two max-size signatures (e.g. ML-DSA-65); 1 KiB of +// slack covers the remaining vote fields and proto framing. +var maxRemoteSignerMsgSize = types.MaxVoteExtensionSize + 2*types.MaxSignatureSize + 1024 + type signerEndpoint struct { service.BaseService @@ -92,7 +99,6 @@ func (se *signerEndpoint) ReadMessage() (msg privvalproto.Message, err error) { if err != nil { return } - const maxRemoteSignerMsgSize = 1024 * 10 protoReader := protoio.NewDelimitedReader(se.conn, maxRemoteSignerMsgSize) _, err = protoReader.ReadMsg(&msg) if _, ok := err.(timeoutError); ok { From 7f9b2e55e06694035d9e9f7af39478593067609a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:16:42 +0200 Subject: [PATCH 26/32] build(deps): Bump pillow from 12.2.0 to 12.3.0 in /scripts/qa/reporting in the pip group across 1 directory (#5986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the pip group with 1 update in the /scripts/qa/reporting directory: [pillow](https://github.com/python-pillow/Pillow). Updates `pillow` from 12.2.0 to 12.3.0
Release notes

Sourced from pillow's releases.

12.3.0

https://pillow.readthedocs.io/en/stable/releasenotes/12.3.0.html

Removals

Documentation

Dependencies

Testing

... (truncated)

Commits
  • bb1d8e8 12.3.0 version bump
  • e63fc48 Add release notes for SBOM and performance improvements (#9747)
  • 13b701b Add release notes for #9679
  • 5564ca7 List methods
  • a0920fd Speed up ImageChops operations (#9738)
  • 07e9a6c Speed up Image.filter() (#9736)
  • a94578c Speed up Image.getchannel(), Image.merge(), Image.putalpha() and `Image...
  • 53e02c4 Speed up Image.fill(), Image.linear_gradient() and `Image.radial_gradient...
  • af03747 Speed up Image.resample() (#9739)
  • 5c9ca56 Speed up alpha_composite, matrix, negative, quantize (#9740)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pillow&package-manager=pip&previous-version=12.2.0&new-version=12.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/cometbft/cometbft/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- scripts/qa/reporting/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/qa/reporting/requirements.txt b/scripts/qa/reporting/requirements.txt index 6e1149443fc..c35bf42f610 100644 --- a/scripts/qa/reporting/requirements.txt +++ b/scripts/qa/reporting/requirements.txt @@ -5,7 +5,7 @@ kiwisolver==1.4.4 matplotlib==3.6.3 numpy==1.24.2 packaging==21.3 -Pillow==12.2.0 +Pillow==12.3.0 pyparsing==3.0.9 python-dateutil==2.8.2 six==1.16.0 From e94961b0982436d0557cc1e87230e0acbd20b456 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 22 Jul 2026 10:05:56 -0500 Subject: [PATCH 27/32] chore: Bump max tx size default (#5989) --- Bumps max tx bytes to account for the potential of larger txs due to the need to incorporate validator signatures such as IBC txns or evidence. In terms of picking a sane default, a chain w/ 100 validators all using mldsa65 signatures a `MsgSubmitMisbehaviour` which carries 2 full block headers would take ~1.5MB. 4MB here gives us plenty of overhead. --- CHANGELOG.md | 2 ++ config/config.go | 2 +- docs/core/configuration.md | 2 +- docs/references/config/config.toml.md | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf1c1ca689..3d35970ed62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,8 @@ ([\#5985](https://github.com/cometbft/cometbft/pull/5985)) - `[types]` Update default max block bytes param to account for increased signature size of mldsa65. ([\#5987](https://github.com/cometbft/cometbft/pull/5987)) +- `[config]` Update the default max_tx_bytes to account for increased signature size of mlsdsa65. + ([\#5989](https://github.com/cometbft/cometbft/pull/5989)) ### FEATURES diff --git a/config/config.go b/config/config.go index c917b5427a6..aa5a806d6b5 100644 --- a/config/config.go +++ b/config/config.go @@ -1030,7 +1030,7 @@ func DefaultMempoolConfig() *MempoolConfig { Size: 5000, MaxTxsBytes: 1024 * 1024 * 1024, // 1GB CacheSize: 10000, - MaxTxBytes: 1024 * 1024, // 1MB + MaxTxBytes: 4 * 1024 * 1024, // 4MB ExperimentalMaxGossipConnectionsToNonPersistentPeers: 0, ExperimentalMaxGossipConnectionsToPersistentPeers: 0, // App mempool defaults diff --git a/docs/core/configuration.md b/docs/core/configuration.md index 88aae7355e1..60836e54ee4 100644 --- a/docs/core/configuration.md +++ b/docs/core/configuration.md @@ -350,7 +350,7 @@ keep-invalid-txs-in-cache = false # Maximum size of a single transaction. # NOTE: the max size of a tx transmitted over the network is {max_tx_bytes}. -max_tx_bytes = 1048576 +max_tx_bytes = 4194304 # Maximum size of a batch of transactions to send to a peer # Including space needed by encoding (one varint per transaction). diff --git a/docs/references/config/config.toml.md b/docs/references/config/config.toml.md index a5b65d5653a..f7880504b28 100644 --- a/docs/references/config/config.toml.md +++ b/docs/references/config/config.toml.md @@ -1318,7 +1318,7 @@ The value `0` is undefined. ### mempool.max_tx_bytes Maximum size in bytes of a single transaction accepted into the mempool. ```toml -max_tx_bytes = 1048576 +max_tx_bytes = 4194304 ``` | Value type | integer | From 21e5bac92b7b714cb7dcb3757a1560ded3e478ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:15:42 +0200 Subject: [PATCH 28/32] build(deps): Bump the go_modules group across 1 directory with 2 updates (#5988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the go_modules group with 2 updates in the / directory: [google.golang.org/grpc](https://github.com/grpc/grpc-go) and [github.com/opencontainers/runc](https://github.com/opencontainers/runc). Updates `google.golang.org/grpc` from 1.81.1 to 1.82.1
Release notes

Sourced from google.golang.org/grpc's releases.

Release 1.82.1

Security

  • server: Stop reading from the connection when flooded by HTTP/2 frames. The default value for this limit is 100 frames, excluding DATA and HEADERS, and may be changed by setting environment variable GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT.
  • xds/rbac: Support Metadata and RequestedServerName permissions matcher fields. If present in a DENY rule, previously these would be ignored and fail-open.
  • xds/rbac: Fix panic when parsing unsupported fields in NotRule/NotId permissions.
  • xds/rbac: Support the deprecated source_ip principal identifier by treating it as equivalent to direct_remote_ip.

Release 1.82.0

Behavior Changes

  • server: Remove support for GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING environment varibale. Strict incoming RPC path validation (which has been the default since v1.79.3) can no longer be disabled. (#9112)
  • transport: Add environment variable to change the default max header list size from 16MB to 8KB. This may be enabled by setting GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE=true. This will be enabled by default in a subsequent release. (#9019)
  • balancer: Load Balancing policy registry is now case-sensitive. Set GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES=false (and file an issue) to revert to case-insensitive behavior. (#9017)

New Features

  • experimental/stats: Expose a new API, NewContextWithLabelCallback, to register a callback that is invoked when telemetry labels are added. (#8877)
  • client: Return a portion of the response body in the error message, when the client receives an unexpected non-gRPC HTTP response, to make debugging easier. (#8929)
  • server: Add environment variable GRPC_GO_SERVER_GOROUTINE_LABELS that controls setting runtime/pprof.Labels on goroutines spawned by the server. Set GRPC_GO_SERVER_GOROUTINE_LABELS=grpc.method=true to add the grpc.method label on goroutines spawned to handle incoming requests. (#9082)

Bug Fixes

  • xds/server: Fix a memory leak of HTTP filter instances occurring when route configurations are updated in-place during a Route Discovery Service (RDS) update. (#9138)
  • grpc: In the deprecated gzip Compressor (used via the deprecated WithCompressor dial option), enforce the MaxRecvMsgSize limit on the decompressed message buffer, preventing excessive memory allocation from highly compressed payloads. (#9114)
  • stats/opentelemetry: Record retry attempts, grpc.previous-rpc-attempts, at the call level and not the attempt level. (#8923)
  • encoding: Ensure Close() is always called on readers returned from Compressor.Decompress if possible. (#9135)
  • channelz: Fix the LastMessageSentTimestamp and LastMessageReceivedTimestamp fields in SocketMetrics to ensure they contain correct timestamp values. (#9109)
Commits

Updates `github.com/opencontainers/runc` from 1.2.8 to 1.3.6
Release notes

Sourced from github.com/opencontainers/runc's releases.

runc v1.3.5 -- "Lo viejo funciona!"

This is the fifth patch release of the 1.3.z release series of runc, and primarily contains a few fixes for issues found in 1.3.4.

Fixed

  • Recursive atime-related mount flags (rrelatime et al.) are now applied properly. (#5115, #5098)
  • PR #4757 caused a regression that resulted in spurious cannot start a container that has stopped errors when running runc create and has thus been reverted. (#5158, #5153, #5151, #4645, #4757)

Changed

Static Linking Notices

The runc binary distributed with this release are statically linked with the following GNU LGPL-2.1 licensed libraries, with runc acting as a "work that uses the Library":

The versions of these libraries were not modified from their upstream versions, but in order to comply with the LGPL-2.1 (§6(a)), we have attached the complete source code for those libraries which (when combined with the attached runc source code) may be used to exercise your rights under the LGPL-2.1.

However we strongly suggest that you make use of your distribution's packages or download them from the authoritative upstream sources, especially since these libraries are related to the security of your containers.


Thanks to the following contributors for making this release possible:

runc v1.3.3 -- "奴らに支配されていた恐怖を"

[!NOTE] Some vendors were given a pre-release version of this release. This public release includes two extra patches to fix regressions discovered very late during the embargo period and were thus not included in the pre-release versions. Please update to this version.

... (truncated)

Changelog

Sourced from github.com/opencontainers/runc's changelog.

[1.3.6] - 2026-06-13

On no account should you allow a Vogon to read poetry at you.

Security

This release includes a fix for the following low-severity security issue:

  • CVE-2026-41579 allowed a malicious image with a /dev symlink to have limited write access to the host filesystem in ways that our analysis indicates was too limited to be problematic in practice. This bug was very similar to those fixed in [CVE-2025-31133][], [CVE-2025-52565][], [CVE-2025-31133][] and was simply missed at the time when we hardened the rootfs preparation code. We have conducted a deeper audit and not found any other problematic cases.

    This patchset required backports for #5190 and #5285, which were primarily code reorganisations that were already backported to runc 1.4 and 1.5.

Fixed

Changed

  • When masking directories with maskPaths, runc will now reuse a single tmpfs instance (which is not writable) to reduce the number tmpfs superblocks that need to be reaped when containers die (in particular, Kubernetes applies masks to per-CPU sysfs directories which get expensive quickly). (#5275, #5281)

[1.5.0-rc.2] - 2026-04-02

いざやいざや、見に行かん

[!NOTE] runc v1.5.0-rc.2 includes all of the patches backported to runc v1.4.2.

Fixed

  • Building with libpathrs for systems that use non-GNU awk, e.g. Debian. (#5196, #5194)

Added

  • Installation notes for libpathrs. (#5199, #5195)
  • Support for specs.LinuxSeccompFlagWaitKillableRecv. (#5183, #5172)
  • When building runc, RUNC_BUILDTAGS make or shell environment variable can

... (truncated)

Commits
  • 491b69b VERSION: release v1.3.6
  • d934454 merge CVE-2026-41579 fixes into release-1.3
  • 9432ad3 rootfs: make cgroupv1 subsystem symlinks fd-based
  • a8e53f2 rootfs: make /dev initialisation code fd-based
  • 78c50d4 rootfs: switch createDevices argument order
  • 083e21e libct: use preopened rootfs more
  • 42cfcbe Pre-open container root directory
  • 2e9b6a8 libct: minor refactor in mountToRootfs
  • edf5328 libct: mountCgroupV1: address TODO
  • 3661a9d integration: add some tests for bind mount through dangling symlinks
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/cometbft/cometbft/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8e6f389bf63..ab612cc176f 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 gonum.org/v1/gonum v0.17.0 - google.golang.org/grpc v1.81.1 + google.golang.org/grpc v1.82.1 google.golang.org/protobuf v1.36.11 ) @@ -132,7 +132,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/opencontainers/runc v1.2.8 // indirect + github.com/opencontainers/runc v1.3.6 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect @@ -192,7 +192,7 @@ require ( golang.org/x/text v0.40.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.47.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools v2.2.0+incompatible // indirect diff --git a/go.sum b/go.sum index c3f87de374d..0cd1ba7cf50 100644 --- a/go.sum +++ b/go.sum @@ -331,8 +331,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q= -github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI= +github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk= +github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= @@ -655,15 +655,15 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 6ba279560e3aa6a1eb0739c6af9bc3b8bbb24563 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:18:41 +0200 Subject: [PATCH 29/32] build(deps): Bump actions/setup-go from 6 to 7 (#5978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to 7.
Release notes

Sourced from actions/setup-go's releases.

v7.0.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/setup-go/compare/v6...v7.0.0

v6.5.0

What's Changed

Dependency update

New Contributors

Full Changelog: https://github.com/actions/setup-go/compare/v6...v6.5.0

v6.4.0

What's Changed

Enhancement

Dependency update

Documentation update

New Contributors

Full Changelog: https://github.com/actions/setup-go/compare/v6...v6.4.0

v6.3.0

What's Changed

Full Changelog: https://github.com/actions/setup-go/compare/v6...v6.3.0

v6.2.0

What's Changed

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-go&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/check-generated.yml | 4 ++-- .github/workflows/e2e-manual-multiversion.yml | 2 +- .github/workflows/e2e-manual.yml | 2 +- .github/workflows/e2e-nightly-main.yml | 2 +- .github/workflows/e2e.yml | 2 +- .github/workflows/fuzz-nightly.yml | 2 +- .github/workflows/integration_tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/pre-release.yml | 2 +- .github/workflows/release-version.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 077568e78c5..cb5f765e0a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV if: steps.filter.outputs.code == 'true' - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 if: steps.filter.outputs.code == 'true' with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/check-generated.yml b/.github/workflows/check-generated.yml index 646755087a6..e298717fd5c 100644 --- a/.github/workflows/check-generated.yml +++ b/.github/workflows/check-generated.yml @@ -24,7 +24,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} @@ -54,7 +54,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/e2e-manual-multiversion.yml b/.github/workflows/e2e-manual-multiversion.yml index 0da018ef3f2..24bdcb6cdc2 100644 --- a/.github/workflows/e2e-manual-multiversion.yml +++ b/.github/workflows/e2e-manual-multiversion.yml @@ -15,7 +15,7 @@ jobs: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 60 steps: - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: '1.22' diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index e2203756d64..d4c7479e025 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -15,7 +15,7 @@ jobs: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 60 steps: - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: '1.22' diff --git a/.github/workflows/e2e-nightly-main.yml b/.github/workflows/e2e-nightly-main.yml index cb685dc5a13..f6e88edf94f 100644 --- a/.github/workflows/e2e-nightly-main.yml +++ b/.github/workflows/e2e-nightly-main.yml @@ -24,7 +24,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 912ce349a16..00cfb0581b6 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -39,7 +39,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV if: steps.filter.outputs.code == 'true' - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 if: steps.filter.outputs.code == 'true' with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/fuzz-nightly.yml b/.github/workflows/fuzz-nightly.yml index 02990d70888..69d7d73672b 100644 --- a/.github/workflows/fuzz-nightly.yml +++ b/.github/workflows/fuzz-nightly.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v7 - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 55a6f1a6de0..0697d86d5ba 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -30,7 +30,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV if: steps.filter.outputs.code == 'true' - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 if: steps.filter.outputs.code == 'true' with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 754c006cbd7..31a08af7c20 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 72f4d5c69d7..95b8697be04 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -16,7 +16,7 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: '1.22' diff --git a/.github/workflows/release-version.yml b/.github/workflows/release-version.yml index fa7a164c84c..8c1961a6170 100644 --- a/.github/workflows/release-version.yml +++ b/.github/workflows/release-version.yml @@ -15,7 +15,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3084cc8e46b..63f477966ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0037eb74d61..8604ed92e29 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,7 +32,7 @@ jobs: - run: echo "GO_VERSION=$(cat .github/workflows/go-version.env | grep GO_VERSION | cut -d '=' -f2)" >> $GITHUB_ENV if: steps.filter.outputs.code == 'true' - - uses: actions/setup-go@v6 + - uses: actions/setup-go@v7 if: steps.filter.outputs.code == 'true' with: go-version: ${{ env.GO_VERSION }} From 5e3308bef09b7a1b19329316463930d00d657b99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:43:39 +0200 Subject: [PATCH 30/32] build(deps): Bump actions/setup-python from 6 to 7 (#5979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
Release notes

Sourced from actions/setup-python's releases.

v7.0.0

What's Changed

Enhancements

Bug Fix

Dependency Upgrade

New Contributors

Full Changelog: https://github.com/actions/setup-python/compare/v6...v7.0.0

v6.3.0

What's Changed

Enhancement

Dependency update

Documentation

New Contributors

Full Changelog: https://github.com/actions/setup-python/compare/v6.2.0...v6.3.0

v6.2.0

What's Changed

Dependency Upgrades

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-python&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 31a08af7c20..ae11b580325 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -23,7 +23,7 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: '3.x' From 0ca6260621c8e6cb38d5e3450307d91d3193af70 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 24 Jul 2026 09:16:29 -0500 Subject: [PATCH 31/32] feat: Add unmarhsal json to secp256k1eth (#5990) --- Adds json decoder to secp256k1eth key types which will end up being called during genesis validation. This change will result in genesis validation errors as opposed to panics during startup on a genesis that otherwise would not fail validation. --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 ++ crypto/secp256k1eth/key.go | 42 +++++++++++++++++++++++++++++++++ crypto/secp256k1eth/key_test.go | 35 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d35970ed62..2116e644e65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,8 @@ ([\#5987](https://github.com/cometbft/cometbft/pull/5987)) - `[config]` Update the default max_tx_bytes to account for increased signature size of mlsdsa65. ([\#5989](https://github.com/cometbft/cometbft/pull/5989)) +- `[crypto]` Add UnmarshalJSON to secp256k1eth key type. + ([\#5990](https://github.com/cometbft/cometbft/pull/5990)) ### FEATURES diff --git a/crypto/secp256k1eth/key.go b/crypto/secp256k1eth/key.go index ebd8c3bb1e1..caa79f6dd5e 100644 --- a/crypto/secp256k1eth/key.go +++ b/crypto/secp256k1eth/key.go @@ -12,6 +12,7 @@ import ( "bytes" "crypto/sha256" "crypto/subtle" + "encoding/json" "errors" "fmt" "io" @@ -138,6 +139,31 @@ func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { return sig, nil } +// UnmarshalJSON unmarshals the private key from JSON, rejecting bytes of the +// wrong length or a scalar outside (0, N) so a malformed key errors at decode +// time. An out-of-range scalar would otherwise derive a pubkey for the +// reduced-mod-N value in PubKey() while Sign refuses to use it. +func (privKey *PrivKey) UnmarshalJSON(bz []byte) error { + var rawBytes []byte + if err := json.Unmarshal(bz, &rawBytes); err != nil { + return err + } + if len(rawBytes) != PrivKeySize { + return fmt.Errorf( + "secp256k1eth: invalid private key size, expected %d bytes, got %d", + PrivKeySize, len(rawBytes), + ) + } + var d secp256k1.ModNScalar + overflow := d.SetByteSlice(rawBytes) + zero := d.IsZero() + if overflow || zero { + return errors.New("secp256k1eth: private key scalar is not in the valid range (0, N)") + } + *privKey = rawBytes + return nil +} + var _ crypto.PubKey = PubKey{} // PubKey is the compressed SEC1 public key (33 bytes). @@ -221,6 +247,22 @@ func NewPubKeyFromBytes(bz []byte) (PubKey, error) { return pk, nil } +// UnmarshalJSON unmarshals the public key from JSON, rejecting bytes that are +// not a valid compressed secp256k1 point so a malformed key errors at decode +// time instead of panicking later (e.g. during genesis validation). +func (pubKey *PubKey) UnmarshalJSON(bz []byte) error { + var rawBytes []byte + if err := json.Unmarshal(bz, &rawBytes); err != nil { + return err + } + pk, err := NewPubKeyFromBytes(rawBytes) + if err != nil { + return err + } + *pubKey = pk + return nil +} + func addressFromPubKey(pub *secp256k1.PublicKey) []byte { // SerializeUncompressed returns 65 bytes: 0x04 || X || Y. Drop the 0x04 // prefix before hashing, matching go-ethereum's address derivation. diff --git a/crypto/secp256k1eth/key_test.go b/crypto/secp256k1eth/key_test.go index d0266125730..88672f897bd 100644 --- a/crypto/secp256k1eth/key_test.go +++ b/crypto/secp256k1eth/key_test.go @@ -1,6 +1,7 @@ package secp256k1eth_test import ( + "bytes" "encoding/hex" "math/big" "testing" @@ -189,6 +190,40 @@ func TestJSONRoundTrip(t *testing.T) { require.True(t, pub.Equals(pub2)) } +func TestJSONUnmarshalRejectsMalformedKeys(t *testing.T) { + // Wrong-length pubkey. + shortPub, err := cmtjson.Marshal(secp256k1eth.PubKey(make([]byte, 5))) + require.NoError(t, err) + var pub secp256k1eth.PubKey + require.ErrorContains(t, cmtjson.Unmarshal(shortPub, &pub), "invalid public key size") + + // Correct length but off-curve: X = 2^256-1 exceeds the field prime. + offCurve := make([]byte, secp256k1eth.PubKeySize) + offCurve[0] = 0x02 + for i := 1; i < len(offCurve); i++ { + offCurve[i] = 0xFF + } + offCurveBz, err := cmtjson.Marshal(secp256k1eth.PubKey(offCurve)) + require.NoError(t, err) + require.ErrorContains(t, cmtjson.Unmarshal(offCurveBz, &pub), "invalid public key") + + // Wrong-length privkey. + shortPriv, err := cmtjson.Marshal(secp256k1eth.PrivKey(make([]byte, 5))) + require.NoError(t, err) + var priv secp256k1eth.PrivKey + require.ErrorContains(t, cmtjson.Unmarshal(shortPriv, &priv), "invalid private key size") + + // Correct length but out-of-range scalars: zero and 2^256-1 (> N). + zeroPriv, err := cmtjson.Marshal(secp256k1eth.PrivKey(make([]byte, secp256k1eth.PrivKeySize))) + require.NoError(t, err) + require.ErrorContains(t, cmtjson.Unmarshal(zeroPriv, &priv), "not in the valid range") + + overflow := bytes.Repeat([]byte{0xFF}, secp256k1eth.PrivKeySize) + overflowPriv, err := cmtjson.Marshal(secp256k1eth.PrivKey(overflow)) + require.NoError(t, err) + require.ErrorContains(t, cmtjson.Unmarshal(overflowPriv, &priv), "not in the valid range") +} + func TestGoEthereumCompatibilityVector(t *testing.T) { // Private key from go-ethereum's signature_test.go (testPrivHex). // The expected address matches go-ethereum's testAddrHex, confirming From 9730a227032a15fc2d6eb01a67719049b036efa4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:34:26 -0700 Subject: [PATCH 32/32] build(deps): Bump google.golang.org/grpc from 1.81.1 to 1.82.1 (#5982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.81.1 to 1.82.1.
Release notes

Sourced from google.golang.org/grpc's releases.

Release 1.82.1

Security

  • server: Stop reading from the connection when flooded by HTTP/2 frames. The default value for this limit is 100 frames, excluding DATA and HEADERS, and may be changed by setting environment variable GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT.
  • xds/rbac: Support Metadata and RequestedServerName permissions matcher fields. If present in a DENY rule, previously these would be ignored and fail-open.
  • xds/rbac: Fix panic when parsing unsupported fields in NotRule/NotId permissions.
  • xds/rbac: Support the deprecated source_ip principal identifier by treating it as equivalent to direct_remote_ip.

Release 1.82.0

Behavior Changes

  • server: Remove support for GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING environment varibale. Strict incoming RPC path validation (which has been the default since v1.79.3) can no longer be disabled. (#9112)
  • transport: Add environment variable to change the default max header list size from 16MB to 8KB. This may be enabled by setting GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE=true. This will be enabled by default in a subsequent release. (#9019)
  • balancer: Load Balancing policy registry is now case-sensitive. Set GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES=false (and file an issue) to revert to case-insensitive behavior. (#9017)

New Features

  • experimental/stats: Expose a new API, NewContextWithLabelCallback, to register a callback that is invoked when telemetry labels are added. (#8877)
  • client: Return a portion of the response body in the error message, when the client receives an unexpected non-gRPC HTTP response, to make debugging easier. (#8929)
  • server: Add environment variable GRPC_GO_SERVER_GOROUTINE_LABELS that controls setting runtime/pprof.Labels on goroutines spawned by the server. Set GRPC_GO_SERVER_GOROUTINE_LABELS=grpc.method=true to add the grpc.method label on goroutines spawned to handle incoming requests. (#9082)

Bug Fixes

  • xds/server: Fix a memory leak of HTTP filter instances occurring when route configurations are updated in-place during a Route Discovery Service (RDS) update. (#9138)
  • grpc: In the deprecated gzip Compressor (used via the deprecated WithCompressor dial option), enforce the MaxRecvMsgSize limit on the decompressed message buffer, preventing excessive memory allocation from highly compressed payloads. (#9114)
  • stats/opentelemetry: Record retry attempts, grpc.previous-rpc-attempts, at the call level and not the attempt level. (#8923)
  • encoding: Ensure Close() is always called on readers returned from Compressor.Decompress if possible. (#9135)
  • channelz: Fix the LastMessageSentTimestamp and LastMessageReceivedTimestamp fields in SocketMetrics to ensure they contain correct timestamp values. (#9109)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.81.1&new-version=1.82.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>