Skip to content

Commit e36957e

Browse files
authored
Merge branch 'main' into feat/ev-node-hot-backup
2 parents 7a31192 + 765b127 commit e36957e

146 files changed

Lines changed: 6125 additions & 1100 deletions

File tree

Some content is hidden

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

.github/workflows/docs_build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Checkout
1919
uses: actions/checkout@v5
2020
- name: Setup Node
21-
uses: actions/setup-node@v5
21+
uses: actions/setup-node@v6
2222
with:
2323
node-version: 20
2424
cache: yarn # or pnpm / npm

.github/workflows/docs_deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
fetch-depth: 0 # Not needed if lastUpdated is not enabled
3434
# - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm
3535
- name: Setup Node
36-
uses: actions/setup-node@v5
36+
uses: actions/setup-node@v6
3737
with:
3838
node-version: 20
3939
cache: yarn # or pnpm / npm

.github/workflows/docs_preview.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
uses: actions/checkout@v5
2525

2626
- name: Setup Node
27-
uses: actions/setup-node@v5
27+
uses: actions/setup-node@v6
2828
with:
2929
node-version: 20
3030
cache: yarn

.github/workflows/ghcr-prune.yml

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Cleanup workflow for pruning old commit-hash Docker tags from GHCR.
2+
name: GHCR Tag Prune
3+
on:
4+
schedule:
5+
- cron: "0 6 * * *" # daily at 06:00 UTC
6+
workflow_dispatch:
7+
inputs:
8+
retention-days:
9+
description: "Override retention window (days)"
10+
required: false
11+
type: number
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
env:
18+
DEFAULT_RETENTION_DAYS: 14
19+
20+
jobs:
21+
prune:
22+
name: Remove aged commit-hash tags
23+
runs-on: ubuntu-latest
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
package:
28+
- ev-node
29+
- ev-node-evm-single
30+
- local-da
31+
steps:
32+
- name: Delete stale tags
33+
uses: actions/github-script@v7
34+
env:
35+
PACKAGE_NAME: ${{ matrix.package }}
36+
OVERRIDE_RETENTION: ${{ github.event.inputs.retention-days }}
37+
with:
38+
script: |
39+
const packageName = process.env.PACKAGE_NAME;
40+
if (!packageName) {
41+
core.setFailed('PACKAGE_NAME env not provided');
42+
return;
43+
}
44+
45+
const retentionDaysInput = process.env.OVERRIDE_RETENTION;
46+
const retentionDays = retentionDaysInput ? Number(retentionDaysInput) : Number(process.env.DEFAULT_RETENTION_DAYS);
47+
if (Number.isNaN(retentionDays) || retentionDays <= 0) {
48+
core.setFailed(`Invalid retention window: ${retentionDaysInput}`);
49+
return;
50+
}
51+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
52+
const owner = context.repo.owner;
53+
const ownerType = context.payload.repository?.owner?.type === 'User' ? 'User' : 'Organization';
54+
55+
core.info(`Processing ${packageName} for ${ownerType.toLowerCase()} ${owner}; removing commit-hash tags older than ${retentionDays} days`);
56+
57+
const listParams = {
58+
package_type: 'container',
59+
package_name: packageName,
60+
per_page: 100,
61+
};
62+
if (ownerType === 'Organization') {
63+
listParams.org = owner;
64+
} else {
65+
listParams.username = owner;
66+
}
67+
68+
const listFn = ownerType === 'Organization'
69+
? github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg
70+
: github.rest.packages.getAllPackageVersionsForPackageOwnedByUser;
71+
72+
const deleteFn = ownerType === 'Organization'
73+
? github.rest.packages.deletePackageVersionForOrg
74+
: github.rest.packages.deletePackageVersionForUser;
75+
76+
const versions = await github.paginate(listFn, listParams);
77+
core.info(`Found ${versions.length} versions`);
78+
79+
const hashRegex = /^(?:sha256:)?[0-9a-f]{7,64}$/i;
80+
const prRegex = /^pr-\d+$/i;
81+
const maxDeletes = 100;
82+
let deleted = 0;
83+
for (const version of versions) {
84+
if (deleted >= maxDeletes) {
85+
core.info(`Hit per-run deletion cap (${maxDeletes}); stopping early for ${packageName}`);
86+
break;
87+
}
88+
const created = new Date(version.created_at).getTime();
89+
if (Number.isNaN(created) || created >= cutoff) {
90+
continue;
91+
}
92+
93+
const tags = version.metadata?.container?.tags ?? [];
94+
const digest = version.metadata?.container?.digest;
95+
const identifiers = [...tags];
96+
if (digest) {
97+
identifiers.push(digest);
98+
}
99+
if (version.name) {
100+
identifiers.push(version.name);
101+
}
102+
103+
if (tags.length === 0) {
104+
core.info(`Skipping version ${version.id} - no tags found`);
105+
continue;
106+
}
107+
108+
const hasReleaseTag = tags.some(tag => tag.startsWith('v'));
109+
if (hasReleaseTag) {
110+
continue;
111+
}
112+
113+
const hasCommitTag = identifiers.some(id => hashRegex.test(id));
114+
const hasPrTag = tags.some(tag => prRegex.test(tag));
115+
if (!hasCommitTag && !hasPrTag) {
116+
continue;
117+
}
118+
119+
core.info(`Deleting version ${version.id} (${tags.join(', ')}) created ${version.created_at}`);
120+
await deleteFn({
121+
package_type: 'container',
122+
package_name: packageName,
123+
package_version_id: version.id,
124+
...(ownerType === 'Organization' ? { org: owner } : { username: owner }),
125+
});
126+
deleted += 1;
127+
}
128+
129+
core.info(`Deleted ${deleted} old commit-hash tags for ${packageName}`);

.github/workflows/test.yml

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
# Tests / Code Coverage workflow
2-
# This workflow is triggered by ci_release.yml workflow
2+
# This workflow is triggered by ci_release.yml workflow or manually
33
name: Tests / Code Coverage
44
on:
55
workflow_call:
66
inputs:
77
image-tag:
88
required: true
99
type: string
10+
workflow_dispatch:
11+
inputs:
12+
image-tag:
13+
description: 'Docker image tag to build (e.g., v1.2.3, sha-abc123)'
14+
required: true
15+
type: string
1016

1117
jobs:
1218
build-ev-node-image:
@@ -114,6 +120,22 @@ jobs:
114120
EV_NODE_IMAGE_REPO: ghcr.io/${{ github.repository }}
115121
EV_NODE_IMAGE_TAG: ${{ inputs.image-tag }}
116122

123+
docker-upgrade-tests:
124+
name: Docker Upgrade E2E Tests
125+
needs: build-ev-node-evm-single-image
126+
runs-on: ubuntu-latest
127+
steps:
128+
- uses: actions/checkout@v5
129+
- name: set up go
130+
uses: actions/setup-go@v6
131+
with:
132+
go-version-file: ./test/docker-e2e/go.mod
133+
- name: Run Docker Upgrade E2E Tests
134+
run: make test-docker-upgrade-e2e
135+
env:
136+
EVM_SINGLE_IMAGE_REPO: ghcr.io/${{ github.repository_owner }}/ev-node-evm-single
137+
EVM_SINGLE_NODE_IMAGE_TAG: ${{ inputs.image-tag }}
138+
117139
build_all-apps:
118140
name: Build All ev-node Binaries
119141
runs-on: ubuntu-latest
@@ -150,7 +172,7 @@ jobs:
150172
- name: Run unit test
151173
run: make test-cover
152174
- name: Upload unit test coverage report
153-
uses: actions/upload-artifact@v4
175+
uses: actions/upload-artifact@v5
154176
with:
155177
name: unit-test-coverage-report-${{ github.sha }}
156178
path: ./coverage.txt
@@ -169,7 +191,7 @@ jobs:
169191
- name: Run integration test
170192
run: make test-integration-cover
171193
- name: Upload integration test coverage report
172-
uses: actions/upload-artifact@v4
194+
uses: actions/upload-artifact@v5
173195
with:
174196
name: integration-test-coverage-report-${{ github.sha }}
175197
path: ./node/coverage.txt
@@ -210,12 +232,12 @@ jobs:
210232
steps:
211233
- uses: actions/checkout@v5
212234
- name: Download unit test coverage report
213-
uses: actions/download-artifact@v5
235+
uses: actions/download-artifact@v6
214236
with:
215237
name: unit-test-coverage-report-${{ github.sha }}
216238
path: ./unit-coverage
217239
- name: Download integration test coverage report
218-
uses: actions/download-artifact@v5
240+
uses: actions/download-artifact@v6
219241
with:
220242
name: integration-test-coverage-report-${{ github.sha }}
221243
path: ./integration-coverage

CHANGELOG.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Changelog
32

43
<!--
@@ -10,37 +9,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
109

1110
## [Unreleased]
1211

12+
### Changed
13+
14+
- Use cache instead of in memory store for reaper. Persist cache on reload. Autoclean after 24 hours. ([#2811](https://github.com/evstack/ev-node/pull/2811))
15+
16+
## v1.0.0-beta.9
17+
1318
### Added
1419

1520
<!-- New features or capabilities -->
21+
22+
- Added automated upgrade test for the `evm-single` app that verifies compatibility when moving from v1.0.0-beta.8 to HEAD in CI ([#2780](https://github.com/evstack/ev-node/pull/2780))
23+
- Added execution-layer replay mechanism so nodes can resynchronize by replaying missed batches against the executor ([#2771](https://github.com/evstack/ev-node/pull/2771))
24+
- Added cache-pruning logic that evicts entries once heights are finalized to keep node memory usage bounded ([#2761](https://github.com/evstack/ev-node/pull/2761))
25+
- Added Prometheus gauges and counters that surface DA submission failures, pending blobs, and resend attempts for easier operational monitoring ([#2756](https://github.com/evstack/ev-node/pull/2756))
1626
- Added gRPC execution client implementation for remote execution services using Connect-RPC protocol ([#2490](https://github.com/evstack/ev-node/pull/2490))
1727
- Added `ExecutorService` protobuf definition with InitChain, GetTxs, ExecuteTxs, and SetFinal RPCs ([#2490](https://github.com/evstack/ev-node/pull/2490))
1828
- Added new `grpc` app for running EVNode with a remote execution layer via gRPC ([#2490](https://github.com/evstack/ev-node/pull/2490))
1929

2030
### Changed
2131

2232
<!-- Changes to existing functionality -->
33+
34+
- Hardened signer CLI and block pipeline per security audit: passphrases must be provided via `--evnode.signer.passphrase_file`, JWT secrets must be provided via `--evm.jwt-secret-file`, data/header validation enforces metadata and timestamp checks, and the reaper backs off on failures (BREAKING) ([#2764](https://github.com/evstack/ev-node/pull/2764))
35+
- Added retries around executor `ExecuteTxs` calls to better tolerate transient execution errors ([#2784](https://github.com/evstack/ev-node/pull/2784))
36+
- Increased default `ReadinessMaxBlocksBehind` from 3 to 30 blocks so `/health/ready` stays true during normal batch sync ([#2779](https://github.com/evstack/ev-node/pull/2779))
2337
- Updated EVM execution client to use new `txpoolExt_getTxs` RPC API for retrieving pending transactions as RLP-encoded bytes
2438

2539
### Deprecated
2640

2741
<!-- Features that will be removed in future versions -->
28-
-
2942

3043
### Removed
3144

3245
<!-- Features that were removed -->
33-
-
46+
47+
- Removed `LastCommitHash`, `ConsensusHash`, and `LastResultsHash` from the canonical header representation in favor of slim headers (BREAKING; legacy hashes now live under `Header.Legacy`) ([#2766](https://github.com/evstack/ev-node/pull/2766))
3448

3549
### Fixed
3650

3751
<!-- Bug fixes -->
38-
- Pass correct namespaces for header and data to the da layer for posting ([#2560](https://github.com/evstack/ev-node/pull/2560))
3952

4053
### Security
4154

4255
<!-- Security vulnerability fixes -->
43-
-
4456

4557
<!--
4658
## Category Guidelines:
@@ -111,4 +123,4 @@ Pre-release versions: 0.x.y (anything may change)
111123
-->
112124

113125
<!-- Links -->
114-
[Unreleased]: https://github.com/evstack/ev-node/compare/v1.0.0-beta.1...HEAD
126+
- [Unreleased]: https://github.com/evstack/ev-node/compare/v1.0.0-beta.1...HEAD

apps/evm/single/cmd/init.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package cmd
33
import (
44
"errors"
55
"fmt"
6+
"os"
7+
"strings"
68

79
"github.com/spf13/cobra"
810

@@ -36,9 +38,24 @@ func InitCmd() *cobra.Command {
3638
return fmt.Errorf("error validating config: %w", err)
3739
}
3840

39-
passphrase, err := cmd.Flags().GetString(rollconf.FlagSignerPassphrase)
41+
// Get passphrase file path
42+
passphraseFile, err := cmd.Flags().GetString(rollconf.FlagSignerPassphraseFile)
4043
if err != nil {
41-
return fmt.Errorf("error reading passphrase flag: %w", err)
44+
return fmt.Errorf("failed to get '%s' flag: %w", rollconf.FlagSignerPassphraseFile, err)
45+
}
46+
47+
var passphrase string
48+
if passphraseFile != "" {
49+
// Read passphrase from file
50+
passphraseBytes, err := os.ReadFile(passphraseFile)
51+
if err != nil {
52+
return fmt.Errorf("failed to read passphrase from file '%s': %w", passphraseFile, err)
53+
}
54+
passphrase = strings.TrimSpace(string(passphraseBytes))
55+
56+
if passphrase == "" {
57+
return fmt.Errorf("passphrase file '%s' is empty", passphraseFile)
58+
}
4259
}
4360

4461
proposerAddress, err := rollcmd.CreateSigner(&cfg, homePath, passphrase)

apps/evm/single/cmd/run.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
7+
"os"
68
"path/filepath"
79

810
"github.com/evstack/ev-node/core/da"
@@ -119,10 +121,28 @@ func createExecutionClient(cmd *cobra.Command) (execution.Executor, error) {
119121
if err != nil {
120122
return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmEngineURL, err)
121123
}
122-
jwtSecret, err := cmd.Flags().GetString(evm.FlagEvmJWTSecret)
124+
125+
// Get JWT secret file path
126+
jwtSecretFile, err := cmd.Flags().GetString(evm.FlagEvmJWTSecretFile)
123127
if err != nil {
124-
return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmJWTSecret, err)
128+
return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmJWTSecretFile, err)
129+
}
130+
131+
if jwtSecretFile == "" {
132+
return nil, fmt.Errorf("JWT secret file must be provided via --evm.jwt-secret-file")
125133
}
134+
135+
// Read JWT secret from file
136+
secretBytes, err := os.ReadFile(jwtSecretFile)
137+
if err != nil {
138+
return nil, fmt.Errorf("failed to read JWT secret from file '%s': %w", jwtSecretFile, err)
139+
}
140+
jwtSecret := string(bytes.TrimSpace(secretBytes))
141+
142+
if jwtSecret == "" {
143+
return nil, fmt.Errorf("JWT secret file '%s' is empty", jwtSecretFile)
144+
}
145+
126146
genesisHashStr, err := cmd.Flags().GetString(evm.FlagEvmGenesisHash)
127147
if err != nil {
128148
return nil, fmt.Errorf("failed to get '%s' flag: %w", evm.FlagEvmGenesisHash, err)
@@ -143,7 +163,7 @@ func createExecutionClient(cmd *cobra.Command) (execution.Executor, error) {
143163
func addFlags(cmd *cobra.Command) {
144164
cmd.Flags().String(evm.FlagEvmEthURL, "http://localhost:8545", "URL of the Ethereum JSON-RPC endpoint")
145165
cmd.Flags().String(evm.FlagEvmEngineURL, "http://localhost:8551", "URL of the Engine API endpoint")
146-
cmd.Flags().String(evm.FlagEvmJWTSecret, "", "The JWT secret for authentication with the execution client")
166+
cmd.Flags().String(evm.FlagEvmJWTSecretFile, "", "Path to file containing the JWT secret for authentication")
147167
cmd.Flags().String(evm.FlagEvmGenesisHash, "", "Hash of the genesis block")
148168
cmd.Flags().String(evm.FlagEvmFeeRecipient, "", "Address that will receive transaction fees")
149169
}

0 commit comments

Comments
 (0)