Skip to content

Commit 8db31f9

Browse files
committed
redo the design
1 parent 45a7d75 commit 8db31f9

67 files changed

Lines changed: 1180 additions & 1888 deletions

Some content is hidden

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

apps/evm/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.7
44

55
replace (
66
github.com/evstack/ev-node => ../../
7+
github.com/evstack/ev-node/core => ../../core
78
github.com/evstack/ev-node/execution/evm => ../../execution/evm
89
)
910

apps/grpc/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.7
44

55
replace (
66
github.com/evstack/ev-node => ../../
7+
github.com/evstack/ev-node/core => ../../core
78
github.com/evstack/ev-node/execution/grpc => ../../execution/grpc
89
)
910

apps/testapp/go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ module github.com/evstack/ev-node/apps/testapp
22

33
go 1.25.7
44

5-
replace github.com/evstack/ev-node => ../../.
5+
replace (
6+
github.com/evstack/ev-node => ../../.
7+
github.com/evstack/ev-node/core => ../../core
8+
)
69

710
require (
811
github.com/evstack/ev-node v1.1.1

apps/testapp/kv/kvexecutor.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,16 +239,16 @@ func (k *KVExecutor) GetTxs(ctx context.Context) ([][]byte, error) {
239239
// ExecuteTxs processes each transaction assumed to be in the format "key=value".
240240
// It updates the database accordingly using a batch and removes the executed transactions from the mempool.
241241
// Invalid transactions are filtered out and logged, but execution continues.
242-
func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) ([]byte, error) {
242+
func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight uint64, timestamp time.Time, prevStateRoot []byte) (execution.ExecuteResult, error) {
243243
select {
244244
case <-ctx.Done():
245-
return nil, ctx.Err()
245+
return execution.ExecuteResult{}, ctx.Err()
246246
default:
247247
}
248248

249249
batch, err := k.db.Batch(ctx)
250250
if err != nil {
251-
return nil, fmt.Errorf("failed to create database batch: %w", err)
251+
return execution.ExecuteResult{}, fmt.Errorf("failed to create database batch: %w", err)
252252
}
253253

254254
validTxCount := 0
@@ -291,7 +291,7 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
291291
err = batch.Put(ctx, dsKey, []byte(value))
292292
if err != nil {
293293
// This error is unlikely for Put unless the context is cancelled.
294-
return nil, fmt.Errorf("failed to stage put operation in batch for key '%s': %w", key, err)
294+
return execution.ExecuteResult{}, fmt.Errorf("failed to stage put operation in batch for key '%s': %w", key, err)
295295
}
296296
validTxCount++
297297
}
@@ -304,7 +304,7 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
304304
// Commit the batch to apply all changes atomically
305305
err = batch.Commit(ctx)
306306
if err != nil {
307-
return nil, fmt.Errorf("failed to commit transaction batch: %w", err)
307+
return execution.ExecuteResult{}, fmt.Errorf("failed to commit transaction batch: %w", err)
308308
}
309309

310310
k.blocksProduced.Add(1)
@@ -315,10 +315,10 @@ func (k *KVExecutor) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight u
315315
if err != nil {
316316
// This is problematic, state was changed but root calculation failed.
317317
// May need more robust error handling or recovery logic.
318-
return nil, fmt.Errorf("failed to compute state root after executing transactions: %w", err)
318+
return execution.ExecuteResult{}, fmt.Errorf("failed to compute state root after executing transactions: %w", err)
319319
}
320320

321-
return stateRoot, nil
321+
return execution.ExecuteResult{UpdatedStateRoot: stateRoot}, nil
322322
}
323323

324324
// SetFinal marks a block as finalized at the specified height.

apps/testapp/kv/kvexecutor_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ func TestExecuteTxs_Valid(t *testing.T) {
105105
[]byte("key2=value2"),
106106
}
107107

108-
stateRoot, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
108+
result, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
109109
if err != nil {
110110
t.Fatalf("ExecuteTxs failed: %v", err)
111111
}
112112

113113
// Check that stateRoot contains the updated key-value pairs
114-
rootStr := string(stateRoot)
114+
rootStr := string(result.UpdatedStateRoot)
115115
if !strings.Contains(rootStr, "key1:value1;") || !strings.Contains(rootStr, "key2:value2;") {
116116
t.Errorf("State root does not contain expected key-values: %s", rootStr)
117117
}
@@ -134,13 +134,13 @@ func TestExecuteTxs_Invalid(t *testing.T) {
134134
[]byte(""),
135135
}
136136

137-
stateRoot, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
137+
result, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
138138
if err != nil {
139139
t.Fatalf("ExecuteTxs should handle gibberish gracefully, got error: %v", err)
140140
}
141141

142142
// State root should still be computed (empty block is valid)
143-
if stateRoot == nil {
143+
if result.UpdatedStateRoot == nil {
144144
t.Error("Expected non-nil state root even with all invalid transactions")
145145
}
146146

@@ -152,13 +152,13 @@ func TestExecuteTxs_Invalid(t *testing.T) {
152152
[]byte(""),
153153
}
154154

155-
stateRoot2, err := exec.ExecuteTxs(ctx, mixedTxs, 2, time.Now(), stateRoot)
155+
result2, err := exec.ExecuteTxs(ctx, mixedTxs, 2, time.Now(), result.UpdatedStateRoot)
156156
if err != nil {
157157
t.Fatalf("ExecuteTxs should filter invalid transactions and process valid ones, got error: %v", err)
158158
}
159159

160160
// State root should contain only the valid transactions
161-
rootStr := string(stateRoot2)
161+
rootStr := string(result2.UpdatedStateRoot)
162162
if !strings.Contains(rootStr, "valid_key:valid_value") || !strings.Contains(rootStr, "another_valid:value2") {
163163
t.Errorf("State root should contain valid transactions: %s", rootStr)
164164
}

block/internal/common/replay.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,12 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
152152
if height == s.genesis.InitialHeight {
153153
// For the first block, use genesis state.
154154
prevState = types.State{
155-
ChainID: s.genesis.ChainID,
156-
InitialHeight: s.genesis.InitialHeight,
157-
LastBlockHeight: s.genesis.InitialHeight - 1,
158-
LastBlockTime: s.genesis.StartTime,
159-
AppHash: header.AppHash, // Genesis app hash (input to first block execution)
155+
ChainID: s.genesis.ChainID,
156+
InitialHeight: s.genesis.InitialHeight,
157+
LastBlockHeight: s.genesis.InitialHeight - 1,
158+
LastBlockTime: s.genesis.StartTime,
159+
AppHash: header.AppHash, // Genesis app hash (input to first block execution)
160+
NextProposerAddress: append([]byte(nil), s.genesis.ProposerAddress...),
160161
}
161162
} else {
162163
// GetStateAtHeight(height-1) returns the state AFTER block height-1 was executed,
@@ -179,10 +180,25 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
179180
Int("tx_count", len(rawTxs)).
180181
Msg("executing transactions on execution layer")
181182

182-
newAppHash, err := s.exec.ExecuteTxs(ctx, rawTxs, height, header.Time(), prevState.AppHash)
183+
result, err := s.exec.ExecuteTxs(ctx, rawTxs, height, header.Time(), prevState.AppHash)
183184
if err != nil {
184185
return fmt.Errorf("failed to execute transactions: %w", err)
185186
}
187+
newAppHash := result.UpdatedStateRoot
188+
if len(result.NextProposerAddress) > 0 {
189+
if len(header.NextProposerAddress) == 0 {
190+
return fmt.Errorf("next proposer mismatch at height %d: header empty, execution %x", height, result.NextProposerAddress)
191+
}
192+
if !bytes.Equal(header.NextProposerAddress, result.NextProposerAddress) {
193+
return fmt.Errorf("next proposer mismatch at height %d: header %x, execution %x",
194+
height,
195+
header.NextProposerAddress,
196+
result.NextProposerAddress,
197+
)
198+
}
199+
} else if len(header.NextProposerAddress) > 0 && !bytes.Equal(header.NextProposerAddress, header.ProposerAddress) {
200+
return fmt.Errorf("next proposer mismatch at height %d: header %x, execution unchanged", height, header.NextProposerAddress)
201+
}
186202

187203
// The result of ExecuteTxs (newAppHash) should match the stored state at this height.
188204
// Note: header.AppHash is the PREVIOUS state's app hash (input), not the expected output.
@@ -207,6 +223,22 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
207223
Msg("app hash mismatch during replay")
208224
return err
209225
}
226+
if len(expectedState.NextProposerAddress) > 0 {
227+
expectedNextProposer := header.NextProposerAddress
228+
if len(expectedNextProposer) == 0 {
229+
expectedNextProposer = result.NextProposerAddress
230+
}
231+
if len(expectedNextProposer) == 0 {
232+
expectedNextProposer = header.ProposerAddress
233+
}
234+
if !bytes.Equal(expectedNextProposer, expectedState.NextProposerAddress) {
235+
return fmt.Errorf("next proposer mismatch at height %d: expected %x got %x",
236+
height,
237+
expectedState.NextProposerAddress,
238+
expectedNextProposer,
239+
)
240+
}
241+
}
210242
s.logger.Debug().
211243
Uint64("height", height).
212244
Str("app_hash", hex.EncodeToString(newAppHash)).

0 commit comments

Comments
 (0)