Skip to content

Commit b4148f6

Browse files
authored
fix: writer_with_verify_hash tries to get hash from block that hasn't… (#23)
* fix: writer_with_verify_hash tries to get hash from block that hasn't been written * return specific error on hash mismatch
1 parent 6947107 commit b4148f6

2 files changed

Lines changed: 62 additions & 12 deletions

File tree

writer_with_verify_hash.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package ethwal
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67

78
"github.com/0xsequence/ethkit/go-ethereum/common"
89
)
910

11+
var ErrParentHashMismatch = errors.New("parent hash mismatch")
12+
1013
type BlockHashGetter func(ctx context.Context, blockNum uint64) (common.Hash, error)
1114

1215
func BlockHashGetterFromReader[T any](options Options) BlockHashGetter {
@@ -45,6 +48,11 @@ func NewWriterWithVerifyHash[T any](writer Writer[T], blockHashGetter BlockHashG
4548
}
4649

4750
func (w *writerWithVerifyHash[T]) Write(ctx context.Context, b Block[T]) error {
51+
// Skip if block is already written
52+
if b.Number <= w.Writer.BlockNum() {
53+
return nil
54+
}
55+
4856
// Skip validation if block is first block
4957
if b.Number == 1 {
5058
if err := w.Writer.Write(ctx, b); err != nil {
@@ -67,15 +75,12 @@ func (w *writerWithVerifyHash[T]) Write(ctx context.Context, b Block[T]) error {
6775

6876
// Validate parent hash
6977
if b.Parent != w.prevHash {
70-
w.prevHash = common.Hash{}
71-
return fmt.Errorf("parent hash mismatch, expected %s, got %s",
72-
w.prevHash.String(), b.Parent.String())
78+
return fmt.Errorf("%w, expected %s, got %s", ErrParentHashMismatch, b.Parent.String(), w.prevHash.String())
7379
}
7480

7581
// Write block
7682
err := w.Writer.Write(ctx, b)
7783
if err != nil {
78-
w.prevHash = common.Hash{}
7984
return fmt.Errorf("failed to write block: %w", err)
8085
}
8186

writer_with_verify_hash_test.go

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func TestWriterWithVerifyHash_Write_FirstBlock(t *testing.T) {
9292
}
9393

9494
// Mock expectations
95+
mockWriter.On("BlockNum").Return(uint64(0)) // No blocks written yet
9596
mockWriter.On("Write", ctx, block).Return(nil)
9697

9798
err := writer.Write(ctx, block)
@@ -138,8 +139,11 @@ func TestWriterWithVerifyHash_Write_SuccessfulSequence(t *testing.T) {
138139
}
139140

140141
// Mock expectations
142+
mockWriter.On("BlockNum").Return(uint64(0)).Once() // Before block 1
141143
mockWriter.On("Write", ctx, block1).Return(nil)
144+
mockWriter.On("BlockNum").Return(uint64(1)).Once() // Before block 2
142145
mockWriter.On("Write", ctx, block2).Return(nil)
146+
mockWriter.On("BlockNum").Return(uint64(2)).Once() // Before block 3
143147
mockWriter.On("Write", ctx, block3).Return(nil)
144148

145149
// Write blocks in sequence
@@ -178,6 +182,7 @@ func TestWriterWithVerifyHash_Write_WithBlockHashGetter(t *testing.T) {
178182
}
179183

180184
// Mock expectations
185+
mockWriter.On("BlockNum").Return(uint64(2)) // Block 2 is the last written block
181186
mockGetter.On("GetHash", ctx, uint64(2)).Return(prevHash, nil)
182187
mockWriter.On("Write", ctx, block3).Return(nil)
183188

@@ -212,13 +217,16 @@ func TestWriterWithVerifyHash_Write_ParentHashMismatch(t *testing.T) {
212217
Data: 44,
213218
}
214219

220+
// Mock expectations
221+
mockWriter.On("BlockNum").Return(uint64(1)) // Block 1 is the last written block
222+
215223
err := writer.Write(ctx, block)
216224

217225
require.Error(t, err)
218226
assert.Contains(t, err.Error(), "parent hash mismatch")
219227

220-
// Check that prevHash is reset to empty on error
221-
assert.Equal(t, common.Hash{}, writerImpl.prevHash)
228+
// prevHash should remain unchanged after error (not reset)
229+
assert.Equal(t, common.BytesToHash([]byte{0x01}), writerImpl.prevHash)
222230

223231
// Mock should not be called since validation fails first
224232
mockWriter.AssertNotCalled(t, "Write")
@@ -243,6 +251,7 @@ func TestWriterWithVerifyHash_Write_BlockHashGetterError(t *testing.T) {
243251
getterError := errors.New("failed to get block hash")
244252

245253
// Mock expectations
254+
mockWriter.On("BlockNum").Return(uint64(2)) // Block 2 is the last written block
246255
mockGetter.On("GetHash", ctx, uint64(2)).Return(common.Hash{}, getterError)
247256

248257
err := writer.Write(ctx, block)
@@ -273,6 +282,7 @@ func TestWriterWithVerifyHash_Write_UnderlyingWriterError(t *testing.T) {
273282
writeError := errors.New("write failed")
274283

275284
// Mock expectations
285+
mockWriter.On("BlockNum").Return(uint64(0)) // No blocks written yet
276286
mockWriter.On("Write", ctx, block).Return(writeError)
277287

278288
err := writer.Write(ctx, block)
@@ -281,7 +291,7 @@ func TestWriterWithVerifyHash_Write_UnderlyingWriterError(t *testing.T) {
281291
assert.Contains(t, err.Error(), "failed to write block")
282292
assert.ErrorIs(t, err, writeError)
283293

284-
// Check that prevHash is reset to empty on write error
294+
// prevHash should remain unchanged after error (not reset)
285295
writerImpl := writer.(*writerWithVerifyHash[int])
286296
assert.Equal(t, common.Hash{}, writerImpl.prevHash)
287297

@@ -308,16 +318,17 @@ func TestWriterWithVerifyHash_Write_ParentHashMismatchAfterGetter(t *testing.T)
308318
}
309319

310320
// Mock expectations - getter returns correct hash, but block has wrong parent
321+
mockWriter.On("BlockNum").Return(uint64(2)) // Block 2 is the last written block
311322
mockGetter.On("GetHash", ctx, uint64(2)).Return(actualPrevHash, nil)
312323

313324
err := writer.Write(ctx, block)
314325

315326
require.Error(t, err)
316327
assert.Contains(t, err.Error(), "parent hash mismatch")
317328

318-
// Check that prevHash is reset to empty on error
329+
// prevHash should be set to the fetched value (actualPrevHash), not reset
319330
writerImpl := writer.(*writerWithVerifyHash[int])
320-
assert.Equal(t, common.Hash{}, writerImpl.prevHash)
331+
assert.Equal(t, actualPrevHash, writerImpl.prevHash)
321332

322333
mockGetter.AssertExpectations(t)
323334
mockWriter.AssertNotCalled(t, "Write") // Should not call write if validation fails
@@ -368,7 +379,7 @@ func TestWriterWithVerifyHash_InterfaceCompliance(t *testing.T) {
368379
}
369380

370381
func TestWriterWithVerifyHash_Write_ResetAfterError(t *testing.T) {
371-
// Test that the writer can recover after an error by resetting prevHash
382+
// Test that the writer can recover after an error by manually resetting prevHash
372383
mockWriter := &mockWriter[int]{}
373384
mockGetter := &mockBlockHashGetter{}
374385

@@ -385,22 +396,27 @@ func TestWriterWithVerifyHash_Write_ResetAfterError(t *testing.T) {
385396
Data: 42,
386397
}
387398

399+
mockWriter.On("BlockNum").Return(uint64(0)).Once()
388400
mockWriter.On("Write", ctx, block1).Return(nil)
389401
err := writer.Write(ctx, block1)
390402
require.NoError(t, err)
391403
assert.Equal(t, block1.Hash, writerImpl.prevHash)
392404

393-
// Then try to write a block with wrong parent hash (should error and reset)
405+
// Then try to write a block with wrong parent hash (should error but NOT reset prevHash)
394406
block2Wrong := Block[int]{
395407
Hash: common.BytesToHash([]byte{0x02}),
396408
Parent: common.BytesToHash([]byte{0x99}), // Wrong parent
397409
Number: 2,
398410
Data: 43,
399411
}
400412

413+
mockWriter.On("BlockNum").Return(uint64(1)).Once()
401414
err = writer.Write(ctx, block2Wrong)
402415
require.Error(t, err)
403-
assert.Equal(t, common.Hash{}, writerImpl.prevHash) // Should be reset
416+
assert.Equal(t, block1.Hash, writerImpl.prevHash) // Should NOT be reset, remains as block1.Hash
417+
418+
// Manually reset prevHash to simulate recovery (e.g., after recreating the writer)
419+
writerImpl.prevHash = common.Hash{}
404420

405421
// Now write a new sequence starting from block 3 (requiring hash lookup)
406422
block2Hash := common.BytesToHash([]byte{0x02})
@@ -411,6 +427,7 @@ func TestWriterWithVerifyHash_Write_ResetAfterError(t *testing.T) {
411427
Data: 44,
412428
}
413429

430+
mockWriter.On("BlockNum").Return(uint64(2)).Once()
414431
mockGetter.On("GetHash", ctx, uint64(2)).Return(block2Hash, nil)
415432
mockWriter.On("Write", ctx, block3).Return(nil)
416433

@@ -422,6 +439,34 @@ func TestWriterWithVerifyHash_Write_ResetAfterError(t *testing.T) {
422439
mockGetter.AssertExpectations(t)
423440
}
424441

442+
func TestWriterWithVerifyHash_Write_SkipAlreadyWritten(t *testing.T) {
443+
mockWriter := &mockWriter[int]{}
444+
mockGetter := &mockBlockHashGetter{}
445+
446+
writer := NewWriterWithVerifyHash[int](mockWriter, mockGetter.GetHash)
447+
448+
ctx := context.Background()
449+
450+
block := Block[int]{
451+
Hash: common.BytesToHash([]byte{0x05}),
452+
Parent: common.BytesToHash([]byte{0x04}),
453+
Number: 5,
454+
Data: 42,
455+
}
456+
457+
// Mock: BlockNum returns 10, meaning blocks up to 10 are already written
458+
mockWriter.On("BlockNum").Return(uint64(10))
459+
460+
// Try to write block 5 - should be skipped since BlockNum() returns 10
461+
err := writer.Write(ctx, block)
462+
463+
require.NoError(t, err)
464+
mockWriter.AssertExpectations(t)
465+
// Write should not be called since block is already written
466+
mockWriter.AssertNotCalled(t, "Write")
467+
mockGetter.AssertNotCalled(t, "GetHash")
468+
}
469+
425470
func TestBlockGetterFromReader(t *testing.T) {
426471
// This test validates that the BlockGetterFromReader helper function can be created
427472
// The detailed functionality testing is covered in the integration tests above

0 commit comments

Comments
 (0)