Skip to content

Commit 0d36358

Browse files
yperbasisclaude
andcommitted
Merge remote-tracking branch 'origin/main' into yperbasis/moksha
Five conflicts: 1) cmd/rpctest/rpctest/account_range_verify.go — main added defer Close() on the two MDBX handles. Trivial: kept both. 2) db/migrations/migrations.go — main landed #21280 (the legacy E2 tables drop spinoff originally cut from this branch), which adds dropLegacyE2Tables. Moksha has both dropLegacyE2Tables and dropAccountIncarnation registered. Resolution: keep both in the ChainDB sequence. 3) db/kv/kvcache/cache.go — main removed AssertCheckValues entirely (the txpool stub call site went away with #21280's caller cleanup). Moksha had it as a no-op stub. Take main's deletion; the only reference left was a commented-out test line. 4) execution/stagedsync/exec3_finalize_test.go — PR #21211 deleted ~240 lines of test scenarios (TestFinalizeTx_SimpleTransfer, _London, _AllScenarios, coinbaseIsRecipientScenario, selfTransferScenario, hasCoinbaseDelta, adjustForTransferDelta) because the finalizeWithIBS / finalizeTx (delta-args) code paths they exercised were dead. Take main's deletion. 5) execution/state/intra_block_state.go — three blocks: a) Same-block-revival check in CreateAccount. Main (#21319) simplified it to `!account.Empty()` on the version-map-refreshed record. Moksha had a path-by-path LatestTxIndex scan (BalancePath/NoncePath/CodeHashPath > destructTxIndex). Take main's cleaner check — semantically equivalent on the same input (the refreshed `account` reflects exactly those higher- txIndex writes). b) prevInc / IncarnationPath bookkeeping for CreateAccount on main. Entirely incarnation-dependent; moksha has no IncarnationPath and no Account.Incarnation. Take HEAD's empty resolution. c) Synthetic versionRead(IncarnationPath, ...) in CreateAccount on main. Same reason. Take HEAD's empty resolution. Plus two non-conflicting touch-ups dropping IncarnationPath references that the merge silently brought in via main-side changes: - execution/state/versionmap.go MVReadResultNone/MapRead recursive- cross-check whitelist dropped IncarnationPath from the path list. - execution/state/versionedio.go SD short-circuit zero-value-fallback whitelist dropped IncarnationPath from the path list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 44a6540 + d2e577d commit 0d36358

47 files changed

Lines changed: 1336 additions & 1072 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/claude.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
2020
runs-on: ubuntu-latest
2121
permissions:
22-
contents: read
22+
contents: write # Required so Claude can push commits to branches it creates
2323
pull-requests: read
2424
issues: read
2525
id-token: write

.github/workflows/test-hive-eest.yml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,24 @@ jobs:
5656
shard: rlp
5757
pool-size: 0
5858
fixtures-tarball: eest_stable
59-
# Glamsterdam devnet: BAL EIPs against devnet fixtures
6059
- sim: consume-engine
61-
sim-limit: ".*(8024|7708|7778|7843|7928|7954|8037).*"
60+
sim-limit: ".*(7708|7778|7843|7928|7954|7976|7981|8024|8037).*"
6261
shard: glamsterdam-devnet
6362
pool-size: 24
6463
fixtures-tarball: eest_devnet
6564
extra-hive-flags: "--sim.loglevel=3 --client.checktimelimit=300s"
6665
erigon-extra-flags: "--experimental.bal"
67-
# test_block_regular_gas_limit: error classification mismatch (GAS_USED_OVERFLOW vs GAS_ALLOWANCE_EXCEEDED)
68-
# transitioning to bal-devnet-4
69-
max-failures: 579
66+
# EIP-7928 test_invalid_{pre,post}_fork_block_*_bal_hash_field
67+
# expect -32602 InvalidParams (wrong test expectations), but the
68+
# requests are well-formed V4/V5 newPayload — the only divergence
69+
# is a blockHash computed over a wrong-fork-schema header. Spec
70+
# convention is InvalidStatus + INVALID_BLOCK_HASH, which is what
71+
# erigon returns. Testing team has been notified to rectify the
72+
# expected exceptions.
73+
# Two additional tests have been observed flaky in the merge queue:
74+
# - EIP-7954 test_max_initcode_size[fork_Amsterdam-...-over_max]
75+
# - EIP-7928 test_bal_invalid_extraneous_entries (various params)
76+
max-failures: 4
7077
steps:
7178
- name: Clean docker system
7279
run: |

agents.md

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,43 @@ Before committing, always verify changes with: `make lint && make erigon integra
4242
./build/bin/erigon --datadir=dev --chain=dev --beacon.api=beacon,validator,node,config # PoS dev mode
4343
```
4444

45+
## Test-Driven Development
46+
47+
When fixing bugs or adding new features, follow the test-driven development (TDD) cycle: **Red → Green → Refactor**.
48+
49+
1. **Red** — write a failing test that specifies the desired behavior. Confirm it fails *for the right reason* (the behavior is missing or wrong), not because of a typo, missing import, or wrong setup.
50+
2. **Green** — write the minimum production code needed to make the test pass. Do not write code the current failing test does not demand.
51+
3. **Refactor** — clean up code and tests with the suite staying green. Skipping this step is how technical debt accumulates.
52+
53+
### For bug fixes
54+
55+
Reproduce the bug as a failing test **before** touching the fix. This proves three things at once: (a) the bug exists, (b) the agent/contributor understands it, and (c) the fix actually addresses it — the test flips red → green when the fix lands.
56+
57+
Never write the fix first and then "add a test for it" — that test only proves the code matches itself, not that it fixes the original defect. If the bug cannot be reproduced as a test, stop and either get more information or escalate; do not guess at a fix.
58+
59+
### For new features
60+
61+
- Drive the API shape from how the test wants to call it (outside-in).
62+
- Start with the simplest meaningful behavior, not the full design.
63+
- Add edge cases as separate Red → Green cycles, not bundled into one giant test.
64+
65+
### Anti-patterns
66+
67+
- **Test-after development**: writing code first, then "adding a test" — this is not TDD; the test merely echoes the code's existing behavior.
68+
- **Tests that pass on the first run**: means the test did not actually drive anything; either the behavior already existed or the assertion is wrong.
69+
- **Skipping the refactor step**: the suite is green but the design didn't improve.
70+
- **Bundling many behaviors into one test**: makes failures hard to localize and refactors brittle.
71+
- **Adding `t.Skip` instead of fixing a failing test**: forbidden for automated agents — see [Test skips](#test-skips) below.
72+
73+
### When pragmatism applies
74+
75+
TDD is the default for behavior changes (bug fixes, new logic, new endpoints). It applies less cleanly to:
76+
- Pure refactors with no behavior change — existing tests are the safety net; do not write new tests just to satisfy the cycle.
77+
- Exploratory spikes — throw the spike away and TDD the real implementation.
78+
- Mechanical changes — renames, generated code regeneration, dependency bumps.
79+
80+
When skipping TDD for one of these reasons, say so explicitly in the PR description.
81+
4582
## Test skips
4683

4784
These rules apply project-wide — to every contributor and to every automated agent (LLM coding assistants, CI bots, etc.) working in this repository.
@@ -87,21 +124,49 @@ Run `make lint` before every push. The linter is non-deterministic — run it re
87124

88125
### Comments
89126

90-
Prefer self-explanatory code over comments. Use clear names and small, focused functions so the code reads on its own. Default to writing no comment.
127+
**Default: no comment.** Clear names and small focused functions read on their own. The vast majority of code — including code written by automated agents — should carry zero new comments. Before adding one, ask whether renaming, extracting a helper, or restructuring would remove the need. Almost always, it does.
128+
129+
A comment may be warranted for: a non-obvious invariant the types don't enforce; a workaround for a bug in a dependency or the runtime (link the issue/commit); a surprising edge case a reader would otherwise miss; a performance-sensitive choice where the obvious implementation would be wrong.
130+
131+
When a comment is genuinely required, it MUST be:
132+
133+
- **One sentence; rarely two; never a paragraph.** No bulleted lists inside `//`. No multi-section block comments with `// Concurrency:` / `// Why:` / `// How to apply:` sub-headings. If the explanation doesn't fit in two sentences, the rest belongs in the commit message, the PR description, or a design doc — not in source, where it goes stale.
134+
- **High-level, not scenario-specific.** Explain the invariant or gotcha in general terms. Don't walk through specific call sites, sequences of operations, or particular situations a reader could find with `grep`. The right level of abstraction is "what must remain true," not "what happened to me last Tuesday."
135+
- **Free of forensic detail.** Strip dates (`// found on 2026-05-21`), devnet/branch names (`// seen on bal-devnet-7`), PR/issue/review references (`// flagged in #21314 round-4 review`), incident anecdotes, and "used by X, Y, Z" callsite lists. That history belongs in the commit message and PR description, where it survives intact; in source it rots, misleads later readers, and bloats the file.
136+
- **Not a restatement of the code.** If a reader could delete the comment without losing information, delete it. Standard Go idioms and well-known library behavior don't need annotation.
137+
138+
A good comment:
139+
140+
```go
141+
// Safe to close while read views are still iterating: the memStore backing
142+
// makes Rollback a no-op on the data.
143+
func (m *MemoryMutation) Rollback() { ... }
144+
```
145+
146+
The same constraint written badly — long, name-dropping internal callers, threading review history through the source:
147+
148+
```go
149+
// Rollback releases this mutation's local cursor cache and forwards Rollback
150+
// to the backing in-memory tx / db. Concurrency invariant (load-bearing —
151+
// see also Filters.WithOverlay): for a MemoryMutation created via
152+
// NewMemoryBatch (the pure-Go memStore backing used by
153+
// SharedDomains.blockOverlay), memTx.Rollback and memDb.Close are no-ops on
154+
// the in-memory data — see memory_store.go. That is what makes it safe for
155+
// the FCU bg-commit goroutine to call Close on the published BlockOverlay
156+
// while concurrent RPC readers are still iterating views obtained via
157+
// NewReadView / NewTemporalReadView. If this is ever switched to
158+
// NewMemoryBatchMDBX (real MDBX backing, where Rollback DOES invalidate
159+
// cursors), the bg-commit close + concurrent-RPC-reader pattern becomes
160+
// unsafe and refcounting/drain logic is required...
161+
```
162+
163+
If a constraint really needs to be enforced for the codebase's safety, prefer **code that enforces it** (a runtime assert, a type the caller can't misuse, a single private constructor) over a comment that describes it. A `panic` survives refactors; a long comment doesn't.
91164

92-
Add a comment only when the code itself can't tell the reader *why*:
93-
- Workarounds for bugs in dependencies, the runtime, or other parts of the codebase (link the issue or commit when possible)
94-
- Non-obvious invariants or constraints not enforced by types
95-
- Surprising edge cases that are easy to miss when reading
96-
- Performance-sensitive choices where the straightforward implementation would be wrong
165+
Function docstrings follow the same rule: a one-line summary, plus param/return notes only when the signature doesn't already say it. A docstring that needs sections is a sign the function does too much, or the explanation belongs elsewhere.
97166

98-
Avoid:
99-
- Restating what the code does (`// increment counter`)
100-
- Referencing the current task, PR, or caller (`// added for the X flow`) — that belongs in the commit message
101-
- Documenting standard Go idioms or well-known library behavior
102-
- `// TODO` notes without a linked issue or owner
167+
`// TODO` notes are only acceptable with a linked tracking issue and an owner. Better: file the issue and don't add the TODO; or fix it now.
103168

104-
When a comment is warranted, keep it short and focused on the *why*. If a reader could delete the comment without losing information, it shouldn't have been written.
169+
**For automated agents specifically:** previous iterations of this guidance were not enough — agents kept producing multi-paragraph block comments enumerating call sites and incident history. Treat the rules above as hard limits. If you catch yourself writing a third sentence in a comment, stop and either delete it, condense to one sentence, or move the content into the commit message.
105170

106171
## Pull Requests & Workflows
107172

cl/clparams/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
"sort"
2727
"time"
2828

29-
"gopkg.in/yaml.v2"
29+
"gopkg.in/yaml.v3"
3030

3131
"github.com/c2h5oh/datasize"
3232

cl/cltypes/partial_data_column_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"testing"
77

88
"github.com/stretchr/testify/require"
9-
"gopkg.in/yaml.v2"
9+
"gopkg.in/yaml.v3"
1010

1111
"github.com/erigontech/erigon/cl/clparams"
1212
"github.com/erigontech/erigon/cl/cltypes"

cl/persistence/blob_storage/data_column_db.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,19 @@ func (s *dataColumnStorageImpl) WriteColumnSidecars(ctx context.Context, blockRo
9898
if err != nil {
9999
return err
100100
}
101-
// snappy of | length | ssz data |
102-
if err := ssz_snappy.EncodeAndWrite(fh, columnData); err != nil {
101+
defer func() {
103102
fh.Close()
104-
s.fs.Remove(filepath)
103+
if err != nil {
104+
s.fs.Remove(filepath)
105+
}
106+
}()
107+
// snappy of | length | ssz data |
108+
if err = ssz_snappy.EncodeAndWrite(fh, columnData); err != nil {
105109
return err
106110
}
107-
if err := fh.Sync(); err != nil {
108-
fh.Close()
109-
s.fs.Remove(filepath)
111+
if err = fh.Sync(); err != nil {
110112
return err
111113
}
112-
113-
fh.Close()
114114
s.emitters.Operation().SendDataColumnSidecar(beaconevents.NewDataColumnSidecarData(columnData))
115115
log.Trace("wrote data column sidecar", "slot", slot, "block_root", blockRoot.String(), "column_index", columnIndex)
116116
return nil

cl/phase1/forkchoice/get_head.go

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,18 @@ func (f *ForkChoiceStore) getHead(auxilliaryState *state.CachingBeaconState) (co
289289
// getFilteredBlockTree filters out dumb blocks.
290290
func (f *ForkChoiceStore) getFilteredBlockTree(base common.Hash) map[common.Hash]*cltypes.BeaconBlockHeader {
291291
blocks := make(map[common.Hash]*cltypes.BeaconBlockHeader)
292-
f.getFilterBlockTree(base, blocks)
292+
// Snapshot the store epoch once for the whole walk: OnTick updates f.time
293+
// without holding f.mu, so calling f.Slot() per-leaf can mix epochs across
294+
// leaves around a slot boundary.
295+
currentEpoch := f.computeEpochAtSlot(f.Slot())
296+
f.getFilterBlockTree(base, blocks, currentEpoch)
293297
return blocks
294298
}
295299

296300
// getFilterBlockTree recursively traverses the block tree to identify viable blocks.
297301
// It takes a block hash and a map of viable blocks as input parameters, and returns a boolean value indicating
298302
// whether the current block is viable.
299-
func (f *ForkChoiceStore) getFilterBlockTree(blockRoot common.Hash, blocks map[common.Hash]*cltypes.BeaconBlockHeader) bool {
303+
func (f *ForkChoiceStore) getFilterBlockTree(blockRoot common.Hash, blocks map[common.Hash]*cltypes.BeaconBlockHeader, currentEpoch uint64) bool {
300304
header, has := f.forkGraph.GetHeader(blockRoot)
301305
if !has {
302306
return false
@@ -308,7 +312,7 @@ func (f *ForkChoiceStore) getFilterBlockTree(blockRoot common.Hash, blocks map[c
308312
if len(children) > 0 {
309313
isAnyViable := false
310314
for _, child := range children {
311-
if f.getFilterBlockTree(child, blocks) {
315+
if f.getFilterBlockTree(child, blocks, currentEpoch) {
312316
isAnyViable = true
313317
}
314318
}
@@ -317,52 +321,39 @@ func (f *ForkChoiceStore) getFilterBlockTree(blockRoot common.Hash, blocks map[c
317321
}
318322
return isAnyViable
319323
}
320-
// Leaf node — check viability per spec filter_block_tree
321-
//
322-
// Justified check (spec): voting_source.epoch >= store.justified_checkpoint.epoch
323-
// Note: the spec uses >= (not ==) so blocks with higher unrealized justification are viable.
324-
// Also implements is_previous_epoch_justified fallback.
325-
//
326-
// Finalized check (spec): store.finalized_checkpoint.root == get_ancestor(store, block_root, finalized_slot)
327-
// Note: checks that the block descends from the finalized block, not checkpoint equality.
328-
329-
// Get voting source (unrealized justification for this block)
330-
votingSource, has := f.getUnrealizedJustification(blockRoot)
331-
if !has {
324+
// Leaf node — viability per spec filter_block_tree.
325+
326+
// Spec get_voting_source: pick unrealized only for prior-epoch blocks
327+
// (pull-up justification view); current/future-epoch blocks use the
328+
// block's realized state checkpoint.
329+
blockEpoch := f.computeEpochAtSlot(header.Slot)
330+
var votingSource solid.Checkpoint
331+
if currentEpoch > blockEpoch {
332+
votingSource, has = f.getUnrealizedJustification(blockRoot)
333+
} else {
332334
votingSource, has = f.forkGraph.GetCurrentJustifiedCheckpoint(blockRoot)
333-
if !has {
334-
return false
335-
}
335+
}
336+
if !has {
337+
return false
336338
}
337339

338340
genesisEpoch := f.beaconCfg.GenesisEpoch
339341

340-
// Spec: correct_justified = store.justified_checkpoint.epoch == GENESIS_EPOCH
341-
// or voting_source.epoch >= store.justified_checkpoint.epoch
342+
// Spec correct_justified:
343+
// store.justified_checkpoint.epoch == GENESIS_EPOCH
344+
// || voting_source.epoch == store.justified_checkpoint.epoch
345+
// || voting_source.epoch + 2 >= current_epoch
342346
justifiedOk := justifiedCheckpoint.Epoch == genesisEpoch ||
343-
votingSource.Epoch >= justifiedCheckpoint.Epoch
344-
345-
// is_previous_epoch_justified fallback:
346-
// If not correct_justified and previous epoch is justified, check that
347-
// the voting source is not more than two epochs ago.
348-
if !justifiedOk {
349-
currentEpoch := f.computeEpochAtSlot(f.Slot())
350-
previousEpoch := currentEpoch - 1
351-
if previousEpoch > currentEpoch { // underflow guard
352-
previousEpoch = 0
353-
}
354-
if justifiedCheckpoint.Epoch == previousEpoch {
355-
justifiedOk = votingSource.Epoch+2 >= currentEpoch
356-
}
357-
}
347+
votingSource.Epoch == justifiedCheckpoint.Epoch ||
348+
votingSource.Epoch+2 >= currentEpoch
358349

359-
// Spec: correct_finalized = store.finalized_checkpoint.epoch == GENESIS_EPOCH
360-
// or store.finalized_checkpoint.root == get_ancestor(store, block_root, finalized_slot)
350+
// Spec correct_finalized:
351+
// store.finalized_checkpoint.epoch == GENESIS_EPOCH
352+
// || store.finalized_checkpoint.root == get_checkpoint_block(block_root, finalized.epoch)
361353
finalizedOk := finalizedCheckpoint.Epoch == genesisEpoch
362354
if !finalizedOk {
363355
finalizedSlot := f.computeStartSlotAtEpoch(finalizedCheckpoint.Epoch)
364-
ancestor := f.Ancestor(blockRoot, finalizedSlot)
365-
finalizedOk = finalizedCheckpoint.Root == ancestor.Root
356+
finalizedOk = finalizedCheckpoint.Root == f.Ancestor(blockRoot, finalizedSlot).Root
366357
}
367358

368359
if justifiedOk && finalizedOk {

cl/phase1/forkchoice/on_block.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -412,24 +412,33 @@ func (f *ForkChoiceStore) OnBlock(ctx context.Context, block *cltypes.SignedBeac
412412
)
413413
f.operationsPool.NotifyBlock(block.Block)
414414

415-
// Eagerly compute unrealized justification and finality
415+
// Eagerly compute unrealized justification and finality (spec: compute_pulled_up_tip)
416416
if err := statechange.ProcessJustificationBitsAndFinality(lastProcessedState, nil); err != nil {
417417
return err
418418
}
419+
// Capture post-pull-up values BEFORE restoring lastProcessedState so the
420+
// prior-epoch updateCheckpoints below can use them per spec
421+
// (compute_pulled_up_tip uses state.current_justified_checkpoint AFTER
422+
// process_justification_and_finalization, not before).
423+
postPullupJustified := lastProcessedState.CurrentJustifiedCheckpoint()
424+
postPullupFinalized := lastProcessedState.FinalizedCheckpoint()
419425
// Store per-block unrealized checkpoints (spec: store.unrealized_justifications[block_root])
420-
f.unrealizedJustifications.Store(common.Hash(blockRoot), lastProcessedState.CurrentJustifiedCheckpoint())
421-
f.unrealizedFinalizations.Store(common.Hash(blockRoot), lastProcessedState.FinalizedCheckpoint())
422-
f.updateUnrealizedCheckpoints(lastProcessedState.CurrentJustifiedCheckpoint(), lastProcessedState.FinalizedCheckpoint())
426+
f.unrealizedJustifications.Store(common.Hash(blockRoot), postPullupJustified)
427+
f.unrealizedFinalizations.Store(common.Hash(blockRoot), postPullupFinalized)
428+
f.updateUnrealizedCheckpoints(postPullupJustified, postPullupFinalized)
423429
// Set the changed value pre-simulation
424430
lastProcessedState.SetPreviousJustifiedCheckpoint(previousJustifiedCheckpoint)
425431
lastProcessedState.SetCurrentJustifiedCheckpoint(currentJustifiedCheckpoint)
426432
lastProcessedState.SetFinalizedCheckpoint(stateFinalized)
427433
lastProcessedState.SetJustificationBits(justificationBits)
428434

429-
// If the block is from a prior epoch, apply the realized values
435+
// Spec compute_pulled_up_tip: if the block is from a prior epoch, apply the
436+
// post-pull-up checkpoints to the store. Previously this used the restored
437+
// pre-pull-up values, which meant store.justified/finalized lagged what
438+
// the spec would have for late-arriving prior-epoch blocks.
430439
currentEpoch := f.computeEpochAtSlot(f.Slot())
431440
if blockEpoch < currentEpoch {
432-
f.updateCheckpoints(lastProcessedState.CurrentJustifiedCheckpoint(), lastProcessedState.FinalizedCheckpoint())
441+
f.updateCheckpoints(postPullupJustified, postPullupFinalized)
433442
}
434443
f.emitters.State().SendBlock(&beaconevents.BlockData{
435444
Slot: block.Block.Slot,

cl/spectest/consensus_tests/ssz_static.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"testing"
2424

2525
"github.com/stretchr/testify/require"
26-
"gopkg.in/yaml.v2"
26+
"gopkg.in/yaml.v3"
2727

2828
"github.com/erigontech/erigon/cl/clparams"
2929
"github.com/erigontech/erigon/cl/cltypes"

cmd/evm/enginexrunner.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,10 @@ func engineXTestCmd(cliCtx *cli.Context) error {
134134
return nil
135135
}
136136

137-
var runnerOpts []engineapitester.EngineXTestRunnerOption
137+
runnerOpts := []engineapitester.EngineXTestRunnerOption{
138+
// needed for benchmarks, to reproduce real-life node startup
139+
engineapitester.WithWarmupKzgCtxOnInit(true),
140+
}
138141
if profilingEnabled {
139142
if cpuProfileDir != "" {
140143
err := os.MkdirAll(cpuProfileDir, 0o755)

0 commit comments

Comments
 (0)