Skip to content

Commit 555a48d

Browse files
authored
feat: expose execution client params to ev-node (#2982)
* feat: expose execution client params to ev-node * updates * wip * wip * updates * updates * cleanup based * simplify * wip * use proper mock * simplify * docs * updates * update spec * updates * updates * fix dummy execution env * complete * remove duplicate tests * fix unit tests, implement syncer verification * add changelog * cache evm execution info * feedback
1 parent 9348732 commit 555a48d

Some content is hidden

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

48 files changed

+2220
-967
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
Additionally, modified the core package to support marking transactions as forced included transactions.
1919
The execution client ought to perform basic validation on those transactions as they have skipped the execution client's mempool.
2020
- Add batching stategies (default stay time-based, unchanged with previous betas). Currently available strategies are `time`, `size`, `immediate` and `adaptive`.
21+
- Added `FilterTxs` method to the execution interface. This method is meant to filter txs by size and if the execution clients allows it, by gas. This is useful for force included transactions, as those aren't filtered by the sequencer's mempool.
22+
- Added `GetExecutionInfo` method to the execution interface. This method returns some execution information, such as the maximum gas per block.
2123

2224
### Changed
2325

apps/evm/cmd/run.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ var RunCmd = &cobra.Command{
8989
}
9090

9191
// Create sequencer based on configuration
92-
sequencer, err := createSequencer(logger, datastore, nodeConfig, genesis, daClient)
92+
sequencer, err := createSequencer(logger, datastore, nodeConfig, genesis, daClient, executor)
9393
if err != nil {
9494
return err
9595
}
@@ -160,14 +160,15 @@ func createSequencer(
160160
nodeConfig config.Config,
161161
genesis genesis.Genesis,
162162
daClient block.FullDAClient,
163+
executor execution.Executor,
163164
) (coresequencer.Sequencer, error) {
164165
if nodeConfig.Node.BasedSequencer {
165166
// Based sequencer mode - fetch transactions only from DA
166167
if !nodeConfig.Node.Aggregator {
167168
return nil, fmt.Errorf("based sequencer mode requires aggregator mode to be enabled")
168169
}
169170

170-
basedSeq, err := based.NewBasedSequencer(daClient, nodeConfig, datastore, genesis, logger)
171+
basedSeq, err := based.NewBasedSequencer(daClient, nodeConfig, datastore, genesis, logger, executor)
171172
if err != nil {
172173
return nil, fmt.Errorf("failed to create based sequencer: %w", err)
173174
}
@@ -188,6 +189,7 @@ func createSequencer(
188189
[]byte(genesis.ChainID),
189190
1000,
190191
genesis,
192+
executor,
191193
)
192194
if err != nil {
193195
return nil, fmt.Errorf("failed to create single sequencer: %w", err)

apps/grpc/cmd/run.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ The execution client must implement the Evolve execution gRPC interface.`,
7676
}
7777

7878
// Create sequencer based on configuration
79-
sequencer, err := createSequencer(cmd.Context(), logger, datastore, nodeConfig, genesis)
79+
sequencer, err := createSequencer(cmd.Context(), logger, datastore, nodeConfig, genesis, executor)
8080
if err != nil {
8181
return err
8282
}
@@ -113,6 +113,7 @@ func createSequencer(
113113
datastore datastore.Batching,
114114
nodeConfig config.Config,
115115
genesis genesis.Genesis,
116+
executor execution.Executor,
116117
) (coresequencer.Sequencer, error) {
117118
blobClient, err := blobrpc.NewClient(ctx, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "")
118119
if err != nil {
@@ -127,7 +128,7 @@ func createSequencer(
127128
return nil, fmt.Errorf("based sequencer mode requires aggregator mode to be enabled")
128129
}
129130

130-
basedSeq, err := based.NewBasedSequencer(daClient, nodeConfig, datastore, genesis, logger)
131+
basedSeq, err := based.NewBasedSequencer(daClient, nodeConfig, datastore, genesis, logger, executor)
131132
if err != nil {
132133
return nil, fmt.Errorf("failed to create based sequencer: %w", err)
133134
}
@@ -148,6 +149,7 @@ func createSequencer(
148149
[]byte(genesis.ChainID),
149150
1000,
150151
genesis,
152+
executor,
151153
)
152154
if err != nil {
153155
return nil, fmt.Errorf("failed to create single sequencer: %w", err)

apps/testapp/cmd/run.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
kvexecutor "github.com/evstack/ev-node/apps/testapp/kv"
1313
"github.com/evstack/ev-node/block"
14+
"github.com/evstack/ev-node/core/execution"
1415
coresequencer "github.com/evstack/ev-node/core/sequencer"
1516
"github.com/evstack/ev-node/node"
1617
"github.com/evstack/ev-node/pkg/cmd"
@@ -91,7 +92,7 @@ var RunCmd = &cobra.Command{
9192
}
9293

9394
// Create sequencer based on configuration
94-
sequencer, err := createSequencer(ctx, logger, datastore, nodeConfig, genesis)
95+
sequencer, err := createSequencer(ctx, logger, datastore, nodeConfig, genesis, executor)
9596
if err != nil {
9697
return err
9798
}
@@ -114,6 +115,7 @@ func createSequencer(
114115
datastore datastore.Batching,
115116
nodeConfig config.Config,
116117
genesis genesis.Genesis,
118+
executor execution.Executor,
117119
) (coresequencer.Sequencer, error) {
118120
blobClient, err := blobrpc.NewClient(ctx, nodeConfig.DA.Address, nodeConfig.DA.AuthToken, "")
119121
if err != nil {
@@ -128,7 +130,7 @@ func createSequencer(
128130
return nil, fmt.Errorf("based sequencer mode requires aggregator mode to be enabled")
129131
}
130132

131-
basedSeq, err := based.NewBasedSequencer(daClient, nodeConfig, datastore, genesis, logger)
133+
basedSeq, err := based.NewBasedSequencer(daClient, nodeConfig, datastore, genesis, logger, executor)
132134
if err != nil {
133135
return nil, fmt.Errorf("failed to create based sequencer: %w", err)
134136
}
@@ -149,6 +151,7 @@ func createSequencer(
149151
[]byte(genesis.ChainID),
150152
1000,
151153
genesis,
154+
executor,
152155
)
153156
if err != nil {
154157
return nil, fmt.Errorf("failed to create single sequencer: %w", err)

apps/testapp/kv/http_server_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func TestHandleKV_Get(t *testing.T) {
141141
// Create and execute the transaction directly
142142
tx := []byte(fmt.Sprintf("%s=%s", tt.key, tt.value))
143143
ctx := context.Background()
144-
_, _, err := exec.ExecuteTxs(ctx, [][]byte{tx}, 1, time.Now(), []byte(""))
144+
_, err := exec.ExecuteTxs(ctx, [][]byte{tx}, 1, time.Now(), []byte(""))
145145
if err != nil {
146146
t.Fatalf("Failed to execute setup transaction: %v", err)
147147
}
@@ -287,13 +287,13 @@ func TestHTTPIntegration_GetKVWithMultipleHeights(t *testing.T) {
287287

288288
// Execute transactions at different heights for the same key
289289
txsHeight1 := [][]byte{[]byte("testkey=original_value")}
290-
_, _, err = exec.ExecuteTxs(ctx, txsHeight1, 1, time.Now(), []byte(""))
290+
_, err = exec.ExecuteTxs(ctx, txsHeight1, 1, time.Now(), []byte(""))
291291
if err != nil {
292292
t.Fatalf("ExecuteTxs failed for height 1: %v", err)
293293
}
294294

295295
txsHeight2 := [][]byte{[]byte("testkey=updated_value")}
296-
_, _, err = exec.ExecuteTxs(ctx, txsHeight2, 2, time.Now(), []byte(""))
296+
_, err = exec.ExecuteTxs(ctx, txsHeight2, 2, time.Now(), []byte(""))
297297
if err != nil {
298298
t.Fatalf("ExecuteTxs failed for height 2: %v", err)
299299
}

apps/testapp/kv/kvexecutor.go

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/evstack/ev-node/core/execution"
1112
"github.com/evstack/ev-node/pkg/store"
1213
ds "github.com/ipfs/go-datastore"
1314
"github.com/ipfs/go-datastore/query"
@@ -141,52 +142,52 @@ func (k *KVExecutor) computeStateRoot(ctx context.Context) ([]byte, error) {
141142
// InitChain initializes the chain state with genesis parameters.
142143
// It checks the database to see if genesis was already performed.
143144
// If not, it computes the state root from the current DB state and persists genesis info.
144-
func (k *KVExecutor) InitChain(ctx context.Context, genesisTime time.Time, initialHeight uint64, chainID string) ([]byte, uint64, error) {
145+
func (k *KVExecutor) InitChain(ctx context.Context, genesisTime time.Time, initialHeight uint64, chainID string) ([]byte, error) {
145146
select {
146147
case <-ctx.Done():
147-
return nil, 0, ctx.Err()
148+
return nil, ctx.Err()
148149
default:
149150
}
150151

151152
initialized, err := k.db.Has(ctx, genesisInitializedKey)
152153
if err != nil {
153-
return nil, 0, fmt.Errorf("failed to check genesis initialization status: %w", err)
154+
return nil, fmt.Errorf("failed to check genesis initialization status: %w", err)
154155
}
155156

156157
if initialized {
157158
genesisRoot, err := k.db.Get(ctx, genesisStateRootKey)
158159
if err != nil {
159-
return nil, 0, fmt.Errorf("genesis initialized but failed to retrieve state root: %w", err)
160+
return nil, fmt.Errorf("genesis initialized but failed to retrieve state root: %w", err)
160161
}
161-
return genesisRoot, 1024, nil // Assuming 1024 is a constant gas value
162+
return genesisRoot, nil
162163
}
163164

164165
// Genesis not initialized. Compute state root from the current DB state.
165166
// Note: The DB might not be empty if restarting, this reflects the state *at genesis time*.
166167
stateRoot, err := k.computeStateRoot(ctx)
167168
if err != nil {
168-
return nil, 0, fmt.Errorf("failed to compute initial state root for genesis: %w", err)
169+
return nil, fmt.Errorf("failed to compute initial state root for genesis: %w", err)
169170
}
170171

171172
// Persist genesis state root and initialized flag
172173
batch, err := k.db.Batch(ctx)
173174
if err != nil {
174-
return nil, 0, fmt.Errorf("failed to create batch for genesis persistence: %w", err)
175+
return nil, fmt.Errorf("failed to create batch for genesis persistence: %w", err)
175176
}
176177
err = batch.Put(ctx, genesisStateRootKey, stateRoot)
177178
if err != nil {
178-
return nil, 0, fmt.Errorf("failed to put genesis state root in batch: %w", err)
179+
return nil, fmt.Errorf("failed to put genesis state root in batch: %w", err)
179180
}
180181
err = batch.Put(ctx, genesisInitializedKey, []byte("true")) // Store a marker value
181182
if err != nil {
182-
return nil, 0, fmt.Errorf("failed to put genesis initialized flag in batch: %w", err)
183+
return nil, fmt.Errorf("failed to put genesis initialized flag in batch: %w", err)
183184
}
184185
err = batch.Commit(ctx)
185186
if err != nil {
186-
return nil, 0, fmt.Errorf("failed to commit genesis persistence batch: %w", err)
187+
return nil, fmt.Errorf("failed to commit genesis persistence batch: %w", err)
187188
}
188189

189-
return stateRoot, 1024, nil // Assuming 1024 is a constant gas value
190+
return stateRoot, nil
190191
}
191192

192193
// GetTxs retrieves available transactions from the mempool channel.
@@ -222,16 +223,16 @@ func (k *KVExecutor) GetTxs(ctx context.Context) ([][]byte, error) {
222223
// ExecuteTxs processes each transaction assumed to be in the format "key=value".
223224
// It updates the database accordingly using a batch and removes the executed transactions from the mempool.
224225
// Invalid transactions are filtered out and logged, but execution continues.
225-
func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) ([]byte, uint64, error) {
226+
func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) ([]byte, error) {
226227
select {
227228
case <-ctx.Done():
228-
return nil, 0, ctx.Err()
229+
return nil, ctx.Err()
229230
default:
230231
}
231232

232233
batch, err := k.db.Batch(ctx)
233234
if err != nil {
234-
return nil, 0, fmt.Errorf("failed to create database batch: %w", err)
235+
return nil, fmt.Errorf("failed to create database batch: %w", err)
235236
}
236237

237238
validTxCount := 0
@@ -274,7 +275,7 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
274275
err = batch.Put(ctx, dsKey, []byte(value))
275276
if err != nil {
276277
// This error is unlikely for Put unless the context is cancelled.
277-
return nil, 0, fmt.Errorf("failed to stage put operation in batch for key '%s': %w", key, err)
278+
return nil, fmt.Errorf("failed to stage put operation in batch for key '%s': %w", key, err)
278279
}
279280
validTxCount++
280281
}
@@ -287,18 +288,18 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
287288
// Commit the batch to apply all changes atomically
288289
err = batch.Commit(ctx)
289290
if err != nil {
290-
return nil, 0, fmt.Errorf("failed to commit transaction batch: %w", err)
291+
return nil, fmt.Errorf("failed to commit transaction batch: %w", err)
291292
}
292293

293294
// Compute the new state root *after* successful commit
294295
stateRoot, err := k.computeStateRoot(ctx)
295296
if err != nil {
296297
// This is problematic, state was changed but root calculation failed.
297298
// May need more robust error handling or recovery logic.
298-
return nil, 0, fmt.Errorf("failed to compute state root after executing transactions: %w", err)
299+
return nil, fmt.Errorf("failed to compute state root after executing transactions: %w", err)
299300
}
300301

301-
return stateRoot, 1024, nil
302+
return stateRoot, nil
302303
}
303304

304305
// SetFinal marks a block as finalized at the specified height.
@@ -422,3 +423,64 @@ func (k *KVExecutor) Rollback(ctx context.Context, height uint64) error {
422423
func getTxKey(height uint64, txKey string) ds.Key {
423424
return heightKeyPrefix.Child(ds.NewKey(fmt.Sprintf("%d/%s", height, txKey)))
424425
}
426+
427+
// GetExecutionInfo returns execution layer parameters.
428+
// For KVExecutor, returns MaxGas=0 indicating no gas-based filtering.
429+
func (k *KVExecutor) GetExecutionInfo(ctx context.Context) (execution.ExecutionInfo, error) {
430+
return execution.ExecutionInfo{MaxGas: 0}, nil
431+
}
432+
433+
// FilterTxs validates force-included transactions and applies size filtering.
434+
// For KVExecutor, validates key=value format when force-included txs are present.
435+
// KVExecutor doesn't track gas, so maxGas is ignored.
436+
func (k *KVExecutor) FilterTxs(ctx context.Context, txs [][]byte, maxBytes, maxGas uint64, hasForceIncludedTransaction bool) ([]execution.FilterStatus, error) {
437+
result := make([]execution.FilterStatus, len(txs))
438+
439+
var cumulativeBytes uint64
440+
limitReached := false
441+
442+
for i, tx := range txs {
443+
// Skip empty transactions
444+
if len(tx) == 0 {
445+
result[i] = execution.FilterRemove
446+
continue
447+
}
448+
449+
txBytes := uint64(len(tx))
450+
451+
// Only validate tx format if force-included txs are present
452+
// Mempool txs are already validated
453+
if hasForceIncludedTransaction {
454+
// Basic format validation: must be key=value
455+
parts := strings.SplitN(string(tx), "=", 2)
456+
if len(parts) != 2 || strings.TrimSpace(parts[0]) == "" {
457+
result[i] = execution.FilterRemove
458+
continue
459+
}
460+
}
461+
462+
// Skip tx that can never make it in a block (too big)
463+
if maxBytes > 0 && txBytes > maxBytes {
464+
result[i] = execution.FilterRemove
465+
continue
466+
}
467+
468+
// Once limit is reached, postpone remaining txs
469+
if limitReached {
470+
result[i] = execution.FilterPostpone
471+
continue
472+
}
473+
474+
// Check size limit
475+
if maxBytes > 0 && cumulativeBytes+txBytes > maxBytes {
476+
limitReached = true
477+
result[i] = execution.FilterPostpone
478+
continue
479+
}
480+
481+
cumulativeBytes += txBytes
482+
result[i] = execution.FilterOK
483+
}
484+
485+
return result, nil
486+
}

0 commit comments

Comments
 (0)