Skip to content

Commit 8db2bd2

Browse files
authored
Merge pull request #954 from onflow/leo/validate-block-with-parent-hash
Add parent hash verification during block ingestion
2 parents 5517c7c + 67d2e61 commit 8db2bd2

File tree

5 files changed

+98
-4
lines changed

5 files changed

+98
-4
lines changed

models/errors/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var (
2525
ErrDisconnected = NewRecoverableError(errors.New("disconnected"))
2626
ErrMissingBlock = errors.New("missing block")
2727
ErrMissingTransactions = errors.New("missing transactions")
28+
ErrInvalidParentHash = errors.New("invalid parent block hash")
2829

2930
// Transaction errors
3031

services/ingestion/engine_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ func TestSerialBlockIngestion(t *testing.T) {
8585

8686
storedCounter := 0
8787
runs := uint64(20)
88+
var prevBlock *models.Block
8889
for i := latestHeight + 1; i < latestHeight+runs; i++ {
8990
cadenceHeight := i + 10
90-
blockCdc, block, blockEvent, err := newBlock(i, nil)
91+
blockCdc, block, blockEvent, err := newBlockWithParent(i, nil, prevBlock)
9192
require.NoError(t, err)
9293

9394
blocks.
@@ -107,6 +108,8 @@ func TestSerialBlockIngestion(t *testing.T) {
107108
}},
108109
Height: cadenceHeight,
109110
})
111+
112+
prevBlock = block
110113
}
111114

112115
close(eventsChan)
@@ -535,8 +538,21 @@ func TestBlockAndTransactionIngestion(t *testing.T) {
535538
}
536539

537540
func newBlock(height uint64, txHashes []gethCommon.Hash) (cadence.Event, *models.Block, *events.Event, error) {
541+
return newBlockWithParent(height, txHashes, nil)
542+
}
543+
544+
func newBlockWithParent(height uint64, txHashes []gethCommon.Hash, parent *models.Block) (cadence.Event, *models.Block, *events.Event, error) {
545+
parentHash := gethCommon.HexToHash("0x1")
546+
if parent != nil {
547+
var err error
548+
parentHash, err = parent.Hash()
549+
if err != nil {
550+
return cadence.Event{}, nil, nil, err
551+
}
552+
}
553+
538554
gethBlock := types.NewBlock(
539-
gethCommon.HexToHash("0x1"),
555+
parentHash,
540556
height,
541557
uint64(1337),
542558
big.NewInt(100),

services/replayer/blocks_provider.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
gethCommon "github.com/ethereum/go-ethereum/common"
77
"github.com/onflow/flow-evm-gateway/models"
8+
errs "github.com/onflow/flow-evm-gateway/models/errors"
89
"github.com/onflow/flow-evm-gateway/storage"
910
"github.com/onflow/flow-go/fvm/evm/offchain/blocks"
1011
evmTypes "github.com/onflow/flow-go/fvm/evm/types"
@@ -75,6 +76,24 @@ func (bp *BlocksProvider) OnBlockReceived(block *models.Block) error {
7576
)
7677
}
7778

79+
// Verify that the new block's parent hash matches the latest block's hash
80+
if bp.latestBlock != nil {
81+
latestHash, err := bp.latestBlock.Hash()
82+
if err != nil {
83+
return fmt.Errorf("failed to compute hash of latest block %d: %w", bp.latestBlock.Height, err)
84+
}
85+
if block.ParentBlockHash != latestHash {
86+
return fmt.Errorf(
87+
"%w: block %d has parent hash %s, but parent block %d has hash %s",
88+
errs.ErrInvalidParentHash,
89+
block.Height,
90+
block.ParentBlockHash.Hex(),
91+
bp.latestBlock.Height,
92+
latestHash.Hex(),
93+
)
94+
}
95+
}
96+
7897
bp.latestBlock = block
7998

8099
return nil

services/replayer/blocks_provider_test.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,52 @@ func TestOnBlockReceived(t *testing.T) {
5353
)
5454
})
5555

56-
t.Run("with new block non-sequential to latest block", func(t *testing.T) {
56+
t.Run("with sequential blocks and valid parent hash", func(t *testing.T) {
5757
_, blocks := setupBlocksDB(t)
5858
blocksProvider := NewBlocksProvider(blocks, flowGo.Emulator)
5959

6060
block1 := mocks.NewBlock(10)
6161
err := blocksProvider.OnBlockReceived(block1)
6262
require.NoError(t, err)
6363

64-
block2 := mocks.NewBlock(11)
64+
block2 := mocks.NewBlockWithParent(11, block1)
65+
err = blocksProvider.OnBlockReceived(block2)
66+
require.NoError(t, err)
67+
})
68+
69+
t.Run("with valid parent hash linkage", func(t *testing.T) {
70+
_, blocks := setupBlocksDB(t)
71+
blocksProvider := NewBlocksProvider(blocks, flowGo.Emulator)
72+
73+
block1 := mocks.NewBlock(10)
74+
err := blocksProvider.OnBlockReceived(block1)
75+
require.NoError(t, err)
76+
77+
// Create block2 with correct parent hash pointing to block1
78+
block2 := mocks.NewBlockWithParent(11, block1)
6579
err = blocksProvider.OnBlockReceived(block2)
6680
require.NoError(t, err)
81+
82+
// Create block3 with correct parent hash pointing to block2
83+
block3 := mocks.NewBlockWithParent(12, block2)
84+
err = blocksProvider.OnBlockReceived(block3)
85+
require.NoError(t, err)
86+
})
87+
88+
t.Run("with invalid parent hash", func(t *testing.T) {
89+
_, blocks := setupBlocksDB(t)
90+
blocksProvider := NewBlocksProvider(blocks, flowGo.Emulator)
91+
92+
block1 := mocks.NewBlock(10)
93+
err := blocksProvider.OnBlockReceived(block1)
94+
require.NoError(t, err)
95+
96+
// Create block2 with wrong parent hash (using NewBlock which generates arbitrary parent hash)
97+
block2 := mocks.NewBlock(11)
98+
err = blocksProvider.OnBlockReceived(block2)
99+
require.Error(t, err)
100+
assert.ErrorContains(t, err, "invalid parent block hash")
101+
assert.ErrorContains(t, err, "block 11 has parent hash")
67102
})
68103
}
69104

storage/mocks/mocks.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,29 @@ func NewBlock(height uint64) *models.Block {
3434
}
3535
}
3636

37+
// NewBlockWithParent creates a new block at the given height with the parent hash
38+
// correctly set to the hash of the provided parent block.
39+
func NewBlockWithParent(height uint64, parent *models.Block) *models.Block {
40+
var parentHash common.Hash
41+
if parent != nil {
42+
parentHash, _ = parent.Hash()
43+
}
44+
45+
return &models.Block{
46+
Block: &types.Block{
47+
ParentBlockHash: parentHash,
48+
Height: height,
49+
Timestamp: uint64(time.Now().Second()),
50+
TotalSupply: big.NewInt(1000),
51+
ReceiptRoot: common.HexToHash(fmt.Sprintf("0x100%d", height)),
52+
TransactionHashRoot: common.HexToHash(fmt.Sprintf("0x200%d", height)),
53+
TotalGasUsed: uint64(30_000),
54+
PrevRandao: common.HexToHash(fmt.Sprintf("0x300%d", height)),
55+
},
56+
TransactionHashes: make([]common.Hash, 0),
57+
}
58+
}
59+
3760
func NewReceipt(block *models.Block) *models.Receipt {
3861
txHash := common.HexToHash(fmt.Sprintf("0xff%d", block.Height))
3962
blockHash, _ := block.Hash()

0 commit comments

Comments
 (0)