Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions blocksync/reactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/p2p"
bcproto "github.com/tendermint/tendermint/proto/tendermint/blocksync"
"github.com/tendermint/tendermint/sequencer"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/store"
"github.com/tendermint/tendermint/types"
Expand Down Expand Up @@ -76,6 +77,10 @@ type Reactor struct {

requestsCh <-chan BlockRequest
errorsCh <-chan peerError

// Sequencer signature verification (set after upgrade via SetVerifier/SetSigStore)
verifier sequencer.SequencerVerifier
sigStore *sequencer.SignatureStore
}

// NewReactor returns new reactor instance.
Expand Down Expand Up @@ -141,6 +146,16 @@ func (bcR *Reactor) SetStateV2(stateV2 SequencerState) {
bcR.stateV2 = stateV2
}

// SetVerifier sets the sequencer verifier for block signature validation.
func (bcR *Reactor) SetVerifier(v sequencer.SequencerVerifier) {
bcR.verifier = v
}

// SetSigStore sets the signature store for persisting/attaching block signatures.
func (bcR *Reactor) SetSigStore(s *sequencer.SignatureStore) {
bcR.sigStore = s
}

// Pool returns the block pool for broadcast reactor to check peer heights.
func (bcR *Reactor) Pool() *BlockPool {
return bcR.pool
Expand Down Expand Up @@ -271,6 +286,19 @@ func (bcR *Reactor) respondToPeerV2(msg *bcproto.BlockRequest, src p2p.Peer) boo
return src.TrySend(BlocksyncChannel, msgBytes)
}

// Attach signature from store; if unavailable, refuse to serve
sig, err := bcR.sigStore.GetSignature(blockData.Hash)
if err != nil {
bcR.Logger.Error("No signature available for block, refusing to serve",
"height", msg.Height, "err", err)
msgBytes, encErr := EncodeMsg(&bcproto.NoBlockResponse{Height: msg.Height})
if encErr != nil {
return false
}
return src.TrySend(BlocksyncChannel, msgBytes)
}
blockData.Signature = sig

bcR.Logger.Debug("respondToPeerV2: got block from geth",
"height", msg.Height,
"hash", blockData.Hash.Hex())
Expand All @@ -293,8 +321,7 @@ func (bcR *Reactor) L2Node() l2node.L2Node {
}

// syncBlockV2 handles syncing a single BlockV2 in sequencer mode.
// No signature verification during sync - only broadcast channel verifies signatures.
// Returns true if sync was successful, false if there was an error (already handled).
// Verifies block signature using the history-aware verifier, then applies.
func (bcR *Reactor) syncBlockV2(block types.SyncableBlock, blocksSynced *uint64, lastRate *float64, lastHundred *time.Time) bool {
blockV2, ok := block.(*types.BlockV2)
if !ok {
Expand All @@ -303,13 +330,25 @@ func (bcR *Reactor) syncBlockV2(block types.SyncableBlock, blocksSynced *uint64,
return false
}

// Apply BlockV2 via stateV2 (no signature verification during sync)
// Verify block signature (uses IsSequencerAt with history-aware lookup)
if err := sequencer.VerifyBlockSignature(bcR.verifier, blockV2); err != nil {
bcR.Logger.Error("Block signature verification failed", "height", blockV2.Number, "err", err)
bcR.pool.RedoRequest(blockV2.GetHeight())
return false
}

// Apply BlockV2 via stateV2
if err := bcR.stateV2.ApplyBlock(blockV2); err != nil {
bcR.Logger.Error("Failed to apply BlockV2", "height", blockV2.Number, "err", err)
bcR.pool.RedoRequest(blockV2.GetHeight())
return false
}

// Persist signature for historical serving
if err := bcR.sigStore.SaveSignature(blockV2.Hash, blockV2.Signature); err != nil {
panic(fmt.Sprintf("failed to save signature at height %d: %v", blockV2.Number, err))
}

bcR.pool.PopRequest()
*blocksSynced++

Expand Down
23 changes: 20 additions & 3 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ type Node struct {
blockBroadcastReactor *sequencer.BlockBroadcastReactor
}

func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, err error) {
func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, sigStore *sequencer.SignatureStore, err error) {
var blockStoreDB dbm.DB
blockStoreDB, err = dbProvider(&DBContext{"blockstore", config})
if err != nil {
Expand All @@ -265,6 +265,14 @@ func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.Block
return
}

// TODO: add a new store, notify the-3rd parties to integrate
var sigDB dbm.DB
sigDB, err = dbProvider(&DBContext{"signatures", config})
if err != nil {
return
}
sigStore = sequencer.NewSignatureStore(sigDB)

return
}

Expand Down Expand Up @@ -509,6 +517,8 @@ func createSequencerComponents(
logger log.Logger,
verifier sequencer.SequencerVerifier,
signer sequencer.Signer,
sigStore *sequencer.SignatureStore,
ha sequencer.SequencerHA,
) (*sequencer.StateV2, *sequencer.BlockBroadcastReactor, error) {
// Create StateV2
stateV2, err := sequencer.NewStateV2(
Expand All @@ -517,6 +527,8 @@ func createSequencerComponents(
logger,
verifier,
signer,
sigStore,
ha,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to create StateV2: %w", err)
Expand All @@ -529,6 +541,7 @@ func createSequencerComponents(
waitSync,
logger,
verifier,
sigStore,
)
broadcastReactor.SetLogger(logger.With("module", "sequencer"))

Expand Down Expand Up @@ -789,7 +802,7 @@ func NewNode(
) (
*Node, error,
) {
blockStore, stateDB, err := initDBs(config, dbProvider)
blockStore, stateDB, sigStore, err := initDBs(config, dbProvider)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1016,12 +1029,16 @@ func NewNode(
logger,
sequencerVerifier,
sequencerSigner,
sigStore,
nil, // ha: nil for now, Raft HA will be injected in a future milestone
); err != nil {
return nil, err
}

// Set stateV2 on blocksync reactor for post-upgrade sync
// Set stateV2&verifier&sigStore on blocksync reactor for post-upgrade
bcR.SetStateV2(node.stateV2)
bcR.SetVerifier(sequencerVerifier)
bcR.SetSigStore(sigStore)

// Register BlockBroadcastReactor with Switch
sw.AddReactor("SEQUENCER", node.blockBroadcastReactor)
Expand Down
35 changes: 35 additions & 0 deletions sequencer/block_verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sequencer

import (
"fmt"

"github.com/morph-l2/go-ethereum/crypto"
)

// VerifyBlockSignature verifies a block's ECDSA signature against the
// expected sequencer at that block's height. All V2 blocks must carry a valid signature.
func VerifyBlockSignature(verifier SequencerVerifier, block *BlockV2) error {
if verifier == nil {
return fmt.Errorf("%w: verifier not configured", ErrInvalidSignature)
}

if len(block.Signature) == 0 {
return fmt.Errorf("%w: missing signature at height %d", ErrInvalidSignature, block.Number)
}

pubKey, err := crypto.SigToPub(block.Hash.Bytes(), block.Signature)
if err != nil {
return fmt.Errorf("%w: recover pubkey at height %d: %v", ErrInvalidSignature, block.Number, err)
}
signer := crypto.PubkeyToAddress(*pubKey)

ok, err := verifier.IsSequencerAt(signer, block.Number)
if err != nil {
return fmt.Errorf("IsSequencerAt height %d: %w", block.Number, err)
}
if !ok {
return fmt.Errorf("%w: signer %s is not sequencer at height %d",
ErrInvalidSignature, signer.Hex(), block.Number)
}
return nil
}
Loading
Loading