Skip to content

Commit 5c9380d

Browse files
authored
chore!: audit fixes (#2764)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. Ex: Closes #<issue number> -->
1 parent 31c701f commit 5c9380d

File tree

34 files changed

+552
-153
lines changed

34 files changed

+552
-153
lines changed

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
}

apps/grpc/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

@@ -38,9 +40,24 @@ This will create the necessary configuration structure in the specified root dir
3840
return fmt.Errorf("error validating config: %w", err)
3941
}
4042

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

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

apps/testapp/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/testapp/cmd/init_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,12 @@ func TestInitCommand(t *testing.T) {
4242
// Register all persistent flags from root command
4343
rollconf.AddGlobalFlags(cmd, "testapp")
4444

45+
// Create passphrase file
46+
passphraseFile := filepath.Join(dir, "passphrase")
47+
require.NoError(t, os.WriteFile(passphraseFile, []byte("test"), 0600))
48+
4549
// Set home flag to the test directory
46-
cmd.SetArgs([]string{"init", "--home", dir, "--rollkit.node.aggregator", "--rollkit.signer.passphrase", "test"})
50+
cmd.SetArgs([]string{"init", "--home", dir, "--evnode.node.aggregator", "--evnode.signer.passphrase_file", passphraseFile})
4751

4852
// Execute the command
4953
err = cmd.Execute()

block/internal/common/consts.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package common
2+
3+
const DefaultMaxBlobSize = 1.5 * 1024 * 1024 // 1.5MB fallback blob size limit

block/internal/common/errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ var (
1717

1818
// ErrHeightFromFutureStr is the error message for height from future returned by da
1919
ErrHeightFromFutureStr = errors.New("given height is from the future")
20+
21+
// ErrOversizedItem is an unrecoverable error indicating a single item exceeds DA blob size limit
22+
ErrOversizedItem = errors.New("single item exceeds DA blob size limit")
2023
)

block/internal/executing/executor.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ func (e *Executor) produceBlock() error {
389389

390390
if err := e.validateBlock(currentState, header, data); err != nil {
391391
e.sendCriticalError(fmt.Errorf("failed to validate block: %w", err))
392+
e.logger.Error().Err(err).Msg("CRITICAL: Permanent block validation error - halting block production")
392393
return fmt.Errorf("failed to validate block: %w", err)
393394
}
394395

@@ -439,7 +440,8 @@ func (e *Executor) produceBlock() error {
439440
// retrieveBatch gets the next batch of transactions from the sequencer
440441
func (e *Executor) retrieveBatch(ctx context.Context) (*BatchData, error) {
441442
req := coresequencer.GetNextBatchRequest{
442-
Id: []byte(e.genesis.ChainID),
443+
Id: []byte(e.genesis.ChainID),
444+
MaxBytes: common.DefaultMaxBlobSize,
443445
}
444446

445447
res, err := e.sequencer.GetNextBatch(ctx, req)

block/internal/reaping/reaper.go

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ import (
1919
"github.com/evstack/ev-node/pkg/store"
2020
)
2121

22-
// DefaultInterval is the default reaper interval
23-
const DefaultInterval = 1 * time.Second
22+
const (
23+
// DefaultInterval is the default reaper interval
24+
DefaultInterval = 1 * time.Second
25+
// MaxBackoffInterval is the maximum backoff interval for retries
26+
MaxBackoffInterval = 30 * time.Second
27+
// BackoffMultiplier is the multiplier for exponential backoff
28+
BackoffMultiplier = 2
29+
)
2430

2531
// Reaper is responsible for periodically retrieving transactions from the executor,
2632
// filtering out already seen transactions, and submitting new transactions to the sequencer.
@@ -76,7 +82,7 @@ func NewReaper(
7682
func (r *Reaper) Start(ctx context.Context) error {
7783
r.ctx, r.cancel = context.WithCancel(ctx)
7884

79-
// Start repear loop
85+
// Start reaper loop
8086
r.wg.Add(1)
8187
go func() {
8288
defer r.wg.Done()
@@ -91,12 +97,35 @@ func (r *Reaper) reaperLoop() {
9197
ticker := time.NewTicker(r.interval)
9298
defer ticker.Stop()
9399

100+
consecutiveFailures := 0
101+
94102
for {
95103
select {
96104
case <-r.ctx.Done():
97105
return
98106
case <-ticker.C:
99-
r.SubmitTxs()
107+
err := r.SubmitTxs()
108+
if err != nil {
109+
// Increment failure counter and apply exponential backoff
110+
consecutiveFailures++
111+
backoff := r.interval * time.Duration(1<<min(consecutiveFailures, 5)) // Cap at 2^5 = 32x
112+
backoff = min(backoff, MaxBackoffInterval)
113+
r.logger.Warn().
114+
Err(err).
115+
Int("consecutive_failures", consecutiveFailures).
116+
Dur("next_retry_in", backoff).
117+
Msg("reaper encountered error, applying backoff")
118+
119+
// Reset ticker with backoff interval
120+
ticker.Reset(backoff)
121+
} else {
122+
// Reset failure counter and backoff on success
123+
if consecutiveFailures > 0 {
124+
r.logger.Info().Msg("reaper recovered from errors, resetting backoff")
125+
consecutiveFailures = 0
126+
ticker.Reset(r.interval)
127+
}
128+
}
100129
}
101130
}
102131
}
@@ -113,34 +142,42 @@ func (r *Reaper) Stop() error {
113142
}
114143

115144
// SubmitTxs retrieves transactions from the executor and submits them to the sequencer.
116-
func (r *Reaper) SubmitTxs() {
145+
// Returns an error if any critical operation fails.
146+
func (r *Reaper) SubmitTxs() error {
117147
txs, err := r.exec.GetTxs(r.ctx)
118148
if err != nil {
119149
r.logger.Error().Err(err).Msg("failed to get txs from executor")
120-
return
150+
return fmt.Errorf("failed to get txs from executor: %w", err)
121151
}
122152
if len(txs) == 0 {
123153
r.logger.Debug().Msg("no new txs")
124-
return
154+
return nil
125155
}
126156

127157
var newTxs [][]byte
158+
var seenStoreErrors int
128159
for _, tx := range txs {
129160
txHash := hashTx(tx)
130161
key := ds.NewKey(txHash)
131162
has, err := r.seenStore.Has(r.ctx, key)
132163
if err != nil {
133164
r.logger.Error().Err(err).Msg("failed to check seenStore")
165+
seenStoreErrors++
134166
continue
135167
}
136168
if !has {
137169
newTxs = append(newTxs, tx)
138170
}
139171
}
140172

173+
// If all transactions failed seenStore check, return error
174+
if seenStoreErrors > 0 && len(newTxs) == 0 {
175+
return fmt.Errorf("failed to check seenStore for all %d transactions", seenStoreErrors)
176+
}
177+
141178
if len(newTxs) == 0 {
142179
r.logger.Debug().Msg("no new txs to submit")
143-
return
180+
return nil
144181
}
145182

146183
r.logger.Debug().Int("txCount", len(newTxs)).Msg("submitting txs to sequencer")
@@ -150,14 +187,14 @@ func (r *Reaper) SubmitTxs() {
150187
Batch: &coresequencer.Batch{Transactions: newTxs},
151188
})
152189
if err != nil {
153-
r.logger.Error().Err(err).Msg("failed to submit txs to sequencer")
154-
return
190+
return fmt.Errorf("failed to submit txs to sequencer: %w", err)
155191
}
156192

157193
for _, tx := range newTxs {
158194
txHash := hashTx(tx)
159195
key := ds.NewKey(txHash)
160196
if err := r.seenStore.Put(r.ctx, key, []byte{1}); err != nil {
197+
// Log but don't fail on persistence errors
161198
r.logger.Error().Err(err).Str("txHash", txHash).Msg("failed to persist seen tx")
162199
}
163200
}
@@ -169,6 +206,7 @@ func (r *Reaper) SubmitTxs() {
169206
}
170207

171208
r.logger.Debug().Msg("successfully submitted txs")
209+
return nil
172210
}
173211

174212
// SeenStore returns the datastore used to track seen transactions.

block/internal/reaping/reaper_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func TestReaper_SubmitTxs_NewTxs_SubmitsAndPersistsAndNotifies(t *testing.T) {
9595
r := newTestReaper(t, "chain-A", mockExec, mockSeq, e)
9696
store := r.SeenStore()
9797

98-
r.SubmitTxs()
98+
assert.NoError(t, r.SubmitTxs())
9999

100100
// Seen keys persisted
101101
has1, err := store.Has(context.Background(), ds.NewKey(hashTx(tx1)))
@@ -128,7 +128,7 @@ func TestReaper_SubmitTxs_AllSeen_NoSubmit(t *testing.T) {
128128
mockExec.EXPECT().GetTxs(mock.Anything).Return([][]byte{tx1, tx2}, nil).Once()
129129
// No SubmitBatchTxs expected
130130

131-
r.SubmitTxs()
131+
assert.NoError(t, r.SubmitTxs())
132132

133133
// Ensure no notification occurred
134134
if e.HasPendingTxNotification() {
@@ -158,7 +158,7 @@ func TestReaper_SubmitTxs_PartialSeen_FiltersAndPersists(t *testing.T) {
158158
return &coresequencer.SubmitBatchTxsResponse{}, nil
159159
}).Once()
160160

161-
r.SubmitTxs()
161+
assert.NoError(t, r.SubmitTxs())
162162

163163
// Both should be seen after successful submit
164164
hasOld, err := store.Has(context.Background(), ds.NewKey(hashTx(txOld)))
@@ -186,7 +186,7 @@ func TestReaper_SubmitTxs_SequencerError_NoPersistence_NoNotify(t *testing.T) {
186186
e := newTestExecutor(t)
187187
r := newTestReaper(t, "chain-D", mockExec, mockSeq, e)
188188

189-
r.SubmitTxs()
189+
assert.Error(t, r.SubmitTxs())
190190

191191
// Should not be marked seen
192192
store := r.SeenStore()

0 commit comments

Comments
 (0)