Skip to content

Commit 6737929

Browse files
committed
Store last header hash
1 parent b930d3c commit 6737929

14 files changed

Lines changed: 269 additions & 82 deletions

File tree

block/components.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func NewSyncComponents(
137137
metrics *Metrics,
138138
blockOpts BlockOptions,
139139
) (*Components, error) {
140+
logger.Info().Msg("Starting in sync-mode")
140141
cacheManager, err := cache.NewManager(config, store, logger)
141142
if err != nil {
142143
return nil, fmt.Errorf("failed to create cache manager: %w", err)
@@ -200,6 +201,7 @@ func NewAggregatorComponents(
200201
metrics *Metrics,
201202
blockOpts BlockOptions,
202203
) (*Components, error) {
204+
logger.Info().Msg("Starting in aggregator-mode")
203205
cacheManager, err := cache.NewManager(config, store, logger)
204206
if err != nil {
205207
return nil, fmt.Errorf("failed to create cache manager: %w", err)

block/internal/executing/executor.go

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -597,35 +597,7 @@ func (e *Executor) validateBlock(lastState types.State, header *types.SignedHead
597597
return fmt.Errorf("invalid header: %w", err)
598598
}
599599

600-
// Validate header against data
601-
if err := types.Validate(header, data); err != nil {
602-
return fmt.Errorf("header-data validation failed: %w", err)
603-
}
604-
605-
// Check chain ID
606-
if header.ChainID() != lastState.ChainID {
607-
return fmt.Errorf("chain ID mismatch: expected %s, got %s",
608-
lastState.ChainID, header.ChainID())
609-
}
610-
611-
// Check height
612-
expectedHeight := lastState.LastBlockHeight + 1
613-
if header.Height() != expectedHeight {
614-
return fmt.Errorf("invalid height: expected %d, got %d",
615-
expectedHeight, header.Height())
616-
}
617-
618-
// Check timestamp
619-
if header.Height() > 1 && lastState.LastBlockTime.After(header.Time()) {
620-
return fmt.Errorf("block time must be strictly increasing")
621-
}
622-
623-
// Check app hash
624-
if !bytes.Equal(header.AppHash, lastState.AppHash) {
625-
return fmt.Errorf("app hash mismatch")
626-
}
627-
628-
return nil
600+
return lastState.AssertValidForNextState(header, data)
629601
}
630602

631603
// sendCriticalError sends a critical error to the error channel without blocking

block/internal/syncing/da_retriever_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func TestDARetriever_ProcessBlobs_HeaderAndData_Success(t *testing.T) {
168168
r := NewDARetriever(nil, cm, config.DefaultConfig(), gen, zerolog.Nop())
169169

170170
dataBin, data := makeSignedDataBytes(t, gen.ChainID, 2, addr, pub, signer, 2)
171-
hdrBin, _ := makeSignedHeaderBytes(t, gen.ChainID, 2, addr, pub, signer, nil, &data.Data)
171+
hdrBin, _ := makeSignedHeaderBytes(t, gen.ChainID, 2, addr, pub, signer, nil, &data.Data, nil)
172172

173173
events := r.processBlobs(context.Background(), [][]byte{hdrBin, dataBin}, 77)
174174
require.Len(t, events, 1)
@@ -196,7 +196,7 @@ func TestDARetriever_ProcessBlobs_HeaderOnly_EmptyDataExpected(t *testing.T) {
196196
r := NewDARetriever(nil, cm, config.DefaultConfig(), gen, zerolog.Nop())
197197

198198
// Header with no data hash present should trigger empty data creation (per current logic)
199-
hb, _ := makeSignedHeaderBytes(t, gen.ChainID, 3, addr, pub, signer, nil, nil)
199+
hb, _ := makeSignedHeaderBytes(t, gen.ChainID, 3, addr, pub, signer, nil, nil, nil)
200200

201201
events := r.processBlobs(context.Background(), [][]byte{hb}, 88)
202202
require.Len(t, events, 1)
@@ -223,7 +223,7 @@ func TestDARetriever_TryDecodeHeaderAndData_Basic(t *testing.T) {
223223
gen := genesis.Genesis{ChainID: "tchain", InitialHeight: 1, StartTime: time.Now().Add(-time.Second), ProposerAddress: addr}
224224
r := NewDARetriever(nil, cm, config.DefaultConfig(), gen, zerolog.Nop())
225225

226-
hb, sh := makeSignedHeaderBytes(t, gen.ChainID, 5, addr, pub, signer, nil, nil)
226+
hb, sh := makeSignedHeaderBytes(t, gen.ChainID, 5, addr, pub, signer, nil, nil, nil)
227227
gotH := r.tryDecodeHeader(hb, 123)
228228
require.NotNil(t, gotH)
229229
assert.Equal(t, sh.Hash().String(), gotH.Hash().String())
@@ -279,7 +279,7 @@ func TestDARetriever_RetrieveFromDA_TwoNamespaces_Success(t *testing.T) {
279279

280280
// Prepare header/data blobs
281281
dataBin, data := makeSignedDataBytes(t, gen.ChainID, 9, addr, pub, signer, 1)
282-
hdrBin, _ := makeSignedHeaderBytes(t, gen.ChainID, 9, addr, pub, signer, nil, &data.Data)
282+
hdrBin, _ := makeSignedHeaderBytes(t, gen.ChainID, 9, addr, pub, signer, nil, &data.Data, nil)
283283

284284
cfg := config.DefaultConfig()
285285
cfg.DA.Namespace = "nsHdr"
@@ -322,7 +322,7 @@ func TestDARetriever_ProcessBlobs_CrossDAHeightMatching(t *testing.T) {
322322

323323
// Create header and data for the same block height but from different DA heights
324324
dataBin, data := makeSignedDataBytes(t, gen.ChainID, 5, addr, pub, signer, 2)
325-
hdrBin, _ := makeSignedHeaderBytes(t, gen.ChainID, 5, addr, pub, signer, nil, &data.Data)
325+
hdrBin, _ := makeSignedHeaderBytes(t, gen.ChainID, 5, addr, pub, signer, nil, &data.Data, nil)
326326

327327
// Process header from DA height 100 first
328328
events1 := r.processBlobs(context.Background(), [][]byte{hdrBin}, 100)
@@ -361,9 +361,9 @@ func TestDARetriever_ProcessBlobs_MultipleHeadersCrossDAHeightMatching(t *testin
361361
data4Bin, data4 := makeSignedDataBytes(t, gen.ChainID, 4, addr, pub, signer, 2)
362362
data5Bin, data5 := makeSignedDataBytes(t, gen.ChainID, 5, addr, pub, signer, 1)
363363

364-
hdr3Bin, _ := makeSignedHeaderBytes(t, gen.ChainID, 3, addr, pub, signer, nil, &data3.Data)
365-
hdr4Bin, _ := makeSignedHeaderBytes(t, gen.ChainID, 4, addr, pub, signer, nil, &data4.Data)
366-
hdr5Bin, _ := makeSignedHeaderBytes(t, gen.ChainID, 5, addr, pub, signer, nil, &data5.Data)
364+
hdr3Bin, _ := makeSignedHeaderBytes(t, gen.ChainID, 3, addr, pub, signer, nil, &data3.Data, nil)
365+
hdr4Bin, _ := makeSignedHeaderBytes(t, gen.ChainID, 4, addr, pub, signer, nil, &data4.Data, nil)
366+
hdr5Bin, _ := makeSignedHeaderBytes(t, gen.ChainID, 5, addr, pub, signer, nil, &data5.Data, nil)
367367

368368
// Process multiple headers from DA height 200 - should be stored as pending
369369
events1 := r.processBlobs(context.Background(), [][]byte{hdr3Bin, hdr4Bin, hdr5Bin}, 200)

block/internal/syncing/syncer.go

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ func (s *Syncer) GetLastState() types.State {
159159
stateCopy := *state
160160
stateCopy.AppHash = bytes.Clone(state.AppHash)
161161
stateCopy.LastResultsHash = bytes.Clone(state.LastResultsHash)
162+
stateCopy.LastHeaderHash = bytes.Clone(state.LastHeaderHash)
162163

163164
return stateCopy
164165
}
@@ -430,7 +431,7 @@ func (s *Syncer) trySyncNextBlock(event *common.DAHeightEvent) error {
430431
// Compared to the executor logic where the current block needs to be applied first,
431432
// here only the previous block needs to be applied to proceed to the verification.
432433
// The header validation must be done before applying the block to avoid executing gibberish
433-
if err := s.validateBlock(header, data, currentState); err != nil {
434+
if err := s.validateBlock(currentState, data, header); err != nil {
434435
// remove header as da included (not per se needed, but keep cache clean)
435436
s.cache.RemoveHeaderDAIncluded(header.Hash().String())
436437
return err
@@ -539,37 +540,15 @@ func (s *Syncer) executeTxsWithRetry(ctx context.Context, rawTxs [][]byte, heade
539540
// NOTE: if the header was gibberish and somehow passed all validation prior but the data was correct
540541
// or if the data was gibberish and somehow passed all validation prior but the header was correct
541542
// we are still losing both in the pending event. This should never happen.
542-
func (s *Syncer) validateBlock(header *types.SignedHeader, data *types.Data, state types.State, ) error {
543+
func (s *Syncer) validateBlock(currState types.State, data *types.Data, header *types.SignedHeader) error {
543544
// Set custom verifier for aggregator node signature
544545
header.SetCustomVerifierForSyncNode(s.options.SyncNodeSignatureBytesProvider)
545546

546-
// Validate header with data
547547
if err := header.ValidateBasicWithData(data); err != nil {
548548
return fmt.Errorf("invalid header: %w", err)
549549
}
550550

551-
// Validate header against data
552-
if err := types.Validate(header, data); err != nil {
553-
return fmt.Errorf("header-data validation failed: %w", err)
554-
}
555-
if state.LastBlockHeight < s.genesis.InitialHeight {
556-
return nil
557-
}
558-
// Validate header against state
559-
if header.Height() != state.LastBlockHeight+1 {
560-
return fmt.Errorf("%w: invalid block height - got: %d, want: %d", errInvalidState, header.Height(), state.LastBlockHeight+1)
561-
}
562-
563-
if !header.Time().After(state.LastBlockTime) {
564-
return fmt.Errorf("%w: invalid block time - got: %v, last: %v", errInvalidState, header.Time(), state.LastBlockTime)
565-
}
566-
if !bytes.Equal(header.LastHeaderHash, state.LastHeaderHash) {
567-
return fmt.Errorf("%w: invalid last header hash - got: %x, want: %x", errInvalidState, header.LastHeaderHash, state.LastHeaderHash)
568-
}
569-
if !bytes.Equal(header.AppHash, state.AppHash) {
570-
return fmt.Errorf("%w: invalid last app hash - got: %x, want: %x", errInvalidState, header.AppHash, state.AppHash)
571-
}
572-
return nil
551+
return currState.AssertValidForNextState(header, data)
573552
}
574553

575554
// sendCriticalError sends a critical error to the error channel without blocking

block/internal/syncing/syncer_backoff_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func TestSyncer_BackoffResetOnSuccess(t *testing.T) {
197197
Return(nil, errors.New("temporary failure")).Once()
198198

199199
// Second call - success (should reset backoff and increment DA height)
200-
_, header := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, nil, nil)
200+
_, header := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, nil, nil, nil)
201201
data := &types.Data{
202202
Metadata: &types.Metadata{
203203
ChainID: gen.ChainID,

block/internal/syncing/syncer_benchmark_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func newBenchFixture(b *testing.B, totalHeights uint64, shuffledTx bool, daDelay
120120
heightEvents := make([]common.DAHeightEvent, totalHeights)
121121
for i := uint64(0); i < totalHeights; i++ {
122122
blockHeight, daHeight := i+gen.InitialHeight, i+daHeightOffset
123-
_, sh := makeSignedHeaderBytes(b, gen.ChainID, blockHeight, addr, pub, signer, nil, nil)
123+
_, sh := makeSignedHeaderBytes(b, gen.ChainID, blockHeight, addr, pub, signer, nil, nil, nil)
124124
d := &types.Data{Metadata: &types.Metadata{ChainID: gen.ChainID, Height: blockHeight, Time: uint64(time.Now().UnixNano())}}
125125
heightEvents[i] = common.DAHeightEvent{Header: sh, Data: d, DaHeight: daHeight}
126126
}

block/internal/syncing/syncer_test.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package syncing
33
import (
44
"context"
55
crand "crypto/rand"
6+
"crypto/sha512"
67
"errors"
78
"testing"
89
"time"
@@ -44,7 +45,17 @@ func buildSyncTestSigner(tb testing.TB) (addr []byte, pub crypto.PubKey, signer
4445
}
4546

4647
// makeSignedHeaderBytes builds a valid SignedHeader and returns its binary encoding and the object
47-
func makeSignedHeaderBytes(tb testing.TB, chainID string, height uint64, proposer []byte, pub crypto.PubKey, signer signerpkg.Signer, appHash []byte, data *types.Data) ([]byte, *types.SignedHeader) {
48+
func makeSignedHeaderBytes(
49+
tb testing.TB,
50+
chainID string,
51+
height uint64,
52+
proposer []byte,
53+
pub crypto.PubKey,
54+
signer signerpkg.Signer,
55+
appHash []byte,
56+
data *types.Data,
57+
lastHeaderHash []byte,
58+
) ([]byte, *types.SignedHeader) {
4859
time := uint64(time.Now().UnixNano())
4960
dataHash := common.DataHashForEmptyTxs
5061
if data != nil {
@@ -58,6 +69,7 @@ func makeSignedHeaderBytes(tb testing.TB, chainID string, height uint64, propose
5869
AppHash: appHash,
5970
DataHash: dataHash,
6071
ProposerAddress: proposer,
72+
LastHeaderHash: lastHeaderHash,
6173
},
6274
Signer: types.Signer{PubKey: pub, Address: proposer},
6375
}
@@ -97,7 +109,6 @@ func TestSyncer_validateBlock_DataHashMismatch(t *testing.T) {
97109

98110
cfg := config.DefaultConfig()
99111
gen := genesis.Genesis{ChainID: "tchain", InitialHeight: 1, StartTime: time.Now().Add(-time.Second), ProposerAddress: addr}
100-
101112
mockExec := testmocks.NewMockExecutor(t)
102113

103114
s := NewSyncer(
@@ -114,24 +125,24 @@ func TestSyncer_validateBlock_DataHashMismatch(t *testing.T) {
114125
common.DefaultBlockOptions(),
115126
make(chan error, 1),
116127
)
117-
128+
require.NoError(t, s.initializeState())
118129
// Create header and data with correct hash
119130
data := makeData(gen.ChainID, 1, 2) // non-empty
120-
_, header := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, nil, data)
131+
_, header := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, nil, data, nil)
121132

122-
err = s.validateBlock(header, data)
133+
err = s.validateBlock(s.GetLastState(), data, header)
123134
require.NoError(t, err)
124135

125136
// Create header and data with mismatched hash
126137
data = makeData(gen.ChainID, 1, 2) // non-empty
127-
_, header = makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, nil, nil)
128-
err = s.validateBlock(header, data)
138+
_, header = makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, nil, nil, nil)
139+
err = s.validateBlock(s.GetLastState(), data, header)
129140
require.Error(t, err)
130141

131142
// Create header and empty data
132143
data = makeData(gen.ChainID, 1, 0) // empty
133-
_, header = makeSignedHeaderBytes(t, gen.ChainID, 2, addr, pub, signer, nil, nil)
134-
err = s.validateBlock(header, data)
144+
_, header = makeSignedHeaderBytes(t, gen.ChainID, 2, addr, pub, signer, nil, nil, nil)
145+
err = s.validateBlock(s.GetLastState(), data, header)
135146
require.Error(t, err)
136147
}
137148

@@ -169,7 +180,7 @@ func TestProcessHeightEvent_SyncsAndUpdatesState(t *testing.T) {
169180
// Create signed header & data for height 1
170181
lastState := s.GetLastState()
171182
data := makeData(gen.ChainID, 1, 0)
172-
_, hdr := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, lastState.AppHash, data)
183+
_, hdr := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, lastState.AppHash, data, nil)
173184

174185
// Expect ExecuteTxs call for height 1
175186
mockExec.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, uint64(1), mock.Anything, lastState.AppHash).
@@ -218,7 +229,7 @@ func TestSequentialBlockSync(t *testing.T) {
218229
// Sync two consecutive blocks via processHeightEvent so ExecuteTxs is called and state stored
219230
st0 := s.GetLastState()
220231
data1 := makeData(gen.ChainID, 1, 1) // non-empty
221-
_, hdr1 := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, st0.AppHash, data1)
232+
_, hdr1 := makeSignedHeaderBytes(t, gen.ChainID, 1, addr, pub, signer, st0.AppHash, data1, st0.LastHeaderHash)
222233
// Expect ExecuteTxs call for height 1
223234
mockExec.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, uint64(1), mock.Anything, st0.AppHash).
224235
Return([]byte("app1"), uint64(1024), nil).Once()
@@ -227,7 +238,7 @@ func TestSequentialBlockSync(t *testing.T) {
227238

228239
st1, _ := st.GetState(context.Background())
229240
data2 := makeData(gen.ChainID, 2, 0) // empty data
230-
_, hdr2 := makeSignedHeaderBytes(t, gen.ChainID, 2, addr, pub, signer, st1.AppHash, data2)
241+
_, hdr2 := makeSignedHeaderBytes(t, gen.ChainID, 2, addr, pub, signer, st1.AppHash, data2, st1.LastHeaderHash)
231242
// Expect ExecuteTxs call for height 2
232243
mockExec.EXPECT().ExecuteTxs(mock.Anything, mock.Anything, uint64(2), mock.Anything, st1.AppHash).
233244
Return([]byte("app2"), uint64(1024), nil).Once()
@@ -365,22 +376,27 @@ func TestSyncLoopPersistState(t *testing.T) {
365376
syncerInst1.daRetriever, syncerInst1.p2pHandler = daRtrMock, p2pHndlMock
366377

367378
// with n da blobs fetched
379+
var prevHeaderHash, prevAppHash []byte
368380
for i := range myFutureDAHeight - myDAHeightOffset {
369-
chainHeight, daHeight := i, i+myDAHeightOffset
381+
chainHeight, daHeight := i+1, i+myDAHeightOffset
370382
emptyData := &types.Data{
371383
Metadata: &types.Metadata{
372384
ChainID: gen.ChainID,
373385
Height: chainHeight,
374386
Time: uint64(time.Now().Add(time.Duration(chainHeight) * time.Second).UnixNano()),
375387
},
376388
}
377-
_, sigHeader := makeSignedHeaderBytes(t, gen.ChainID, chainHeight, addr, pub, signer, nil, emptyData)
389+
_, sigHeader := makeSignedHeaderBytes(t, gen.ChainID, chainHeight, addr, pub, signer, prevAppHash, emptyData, prevHeaderHash)
378390
evts := []common.DAHeightEvent{{
379391
Header: sigHeader,
380392
Data: emptyData,
381393
DaHeight: daHeight,
382394
}}
383395
daRtrMock.On("RetrieveFromDA", mock.Anything, daHeight).Return(evts, nil)
396+
prevHeaderHash = sigHeader.Hash()
397+
hasher := sha512.New()
398+
hasher.Write(prevAppHash)
399+
prevAppHash = hasher.Sum(nil)
384400
}
385401

386402
// stop at next height
@@ -395,7 +411,6 @@ func TestSyncLoopPersistState(t *testing.T) {
395411
Return(nil, coreda.ErrHeightFromFuture)
396412

397413
go syncerInst1.processLoop()
398-
399414
// dssync from DA until stop height reached
400415
syncerInst1.syncLoop()
401416
t.Log("syncLoop on instance1 completed")

proto/evnode/v1/state.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ message State {
1616
uint64 da_height = 6;
1717
bytes last_results_hash = 7;
1818
bytes app_hash = 8;
19+
bytes LastHeaderHash = 9;
1920
}

test/e2e/evm_full_node_e2e_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ func restartSequencerAndFullNode(t *testing.T, sut *SystemUnderTest, sequencerHo
948948
// Now restart the full node (without init - node already exists)
949949
sut.ExecCmd(evmSingleBinaryPath,
950950
"start",
951+
"--evnode.log.format", "json",
951952
"--home", fullNodeHome,
952953
"--evm.jwt-secret", fullNodeJwtSecret,
953954
"--evm.genesis-hash", genesisHash,

test/e2e/evm_test_common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ func restartDAAndSequencer(t *testing.T, sut *SystemUnderTest, sequencerHome, jw
563563
// Then restart the sequencer node (without init - node already exists)
564564
sut.ExecCmd(evmSingleBinaryPath,
565565
"start",
566+
"--evnode.log.format", "json",
566567
"--evm.jwt-secret", jwtSecret,
567568
"--evm.genesis-hash", genesisHash,
568569
"--rollkit.node.block_time", DefaultBlockTime,

0 commit comments

Comments
 (0)