Skip to content

Commit 28c32e3

Browse files
authored
fix(block): save created block for recovery and simplify genesis flow (#2672)
<!-- Please read and fill out this form before submitting your PR. Please make sure you have reviewed our contributors guide before submitting your first PR. NOTE: PR titles should follow semantic commits: https://www.conventionalcommits.org/en/v1.0.0/ --> ## Overview <!-- Please provide an explanation of the PR, including the appropriate context, background, goal, and rationale. If there is an issue with this information, please provide a tl;dr and link the issue. Ex: Closes #<issue number> -->
1 parent 7a5e924 commit 28c32e3

6 files changed

Lines changed: 436 additions & 97 deletions

File tree

block/components_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,23 +123,28 @@ func TestNewAggregatorComponents_Creation(t *testing.T) {
123123
memStore := store.New(ds)
124124

125125
cfg := config.DefaultConfig
126+
127+
// Create a test signer first
128+
priv, _, err := crypto.GenerateEd25519Key(crand.Reader)
129+
require.NoError(t, err)
130+
mockSigner, err := noop.NewNoopSigner(priv)
131+
require.NoError(t, err)
132+
133+
// Get the signer's address to use as proposer
134+
signerAddr, err := mockSigner.GetAddress()
135+
require.NoError(t, err)
136+
126137
gen := genesis.Genesis{
127138
ChainID: "test-chain",
128139
InitialHeight: 1,
129140
StartTime: time.Now(),
130-
ProposerAddress: []byte("test-proposer"),
141+
ProposerAddress: signerAddr,
131142
}
132143

133144
mockExec := testmocks.NewMockExecutor(t)
134145
mockSeq := testmocks.NewMockSequencer(t)
135146
dummyDA := coreda.NewDummyDA(10_000_000, 0, 0, 10*time.Millisecond)
136147

137-
// Create a test signer
138-
priv, _, err := crypto.GenerateEd25519Key(crand.Reader)
139-
require.NoError(t, err)
140-
mockSigner, err := noop.NewNoopSigner(priv)
141-
require.NoError(t, err)
142-
143148
components, err := NewAggregatorComponents(
144149
cfg,
145150
gen,

block/internal/common/errors.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import (
44
"errors"
55
)
66

7-
// These errors are used by Manager.
7+
// These errors are used by block components.
88
var (
9-
// ErrNotProposer is used when the manager is not a proposer
9+
// ErrNotProposer is used when the node is not a proposer
1010
ErrNotProposer = errors.New("not a proposer")
1111

1212
// ErrNoBatch indicate no batch is available for creating block

block/internal/executing/executor.go

Lines changed: 50 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"sync"
99
"time"
1010

11+
"github.com/ipfs/go-datastore"
1112
"github.com/rs/zerolog"
1213
"golang.org/x/sync/errgroup"
1314

@@ -187,11 +188,6 @@ func (e *Executor) initializeState() error {
187188
return fmt.Errorf("failed to initialize chain: %w", err)
188189
}
189190

190-
// Create genesis block
191-
if err := e.createGenesisBlock(e.ctx, stateRoot); err != nil {
192-
return fmt.Errorf("failed to create genesis block: %w", err)
193-
}
194-
195191
state = types.State{
196192
ChainID: e.genesis.ChainID,
197193
InitialHeight: e.genesis.InitialHeight,
@@ -215,55 +211,6 @@ func (e *Executor) initializeState() error {
215211
return nil
216212
}
217213

218-
// createGenesisBlock creates and stores the genesis block
219-
func (e *Executor) createGenesisBlock(ctx context.Context, stateRoot []byte) error {
220-
header := types.Header{
221-
AppHash: stateRoot,
222-
DataHash: common.DataHashForEmptyTxs,
223-
ProposerAddress: e.genesis.ProposerAddress,
224-
BaseHeader: types.BaseHeader{
225-
ChainID: e.genesis.ChainID,
226-
Height: e.genesis.InitialHeight,
227-
Time: uint64(e.genesis.StartTime.UnixNano()),
228-
},
229-
}
230-
231-
if e.signer == nil {
232-
return errors.New("signer cannot be nil")
233-
}
234-
235-
pubKey, err := e.signer.GetPublic()
236-
if err != nil {
237-
return fmt.Errorf("failed to get public key: %w", err)
238-
}
239-
240-
bz, err := e.options.AggregatorNodeSignatureBytesProvider(&header)
241-
if err != nil {
242-
return fmt.Errorf("failed to get signature payload: %w", err)
243-
}
244-
245-
sig, err := e.signer.Sign(bz)
246-
if err != nil {
247-
return fmt.Errorf("failed to sign header: %w", err)
248-
}
249-
250-
var signature types.Signature
251-
data := &types.Data{}
252-
253-
signature = sig
254-
255-
genesisHeader := &types.SignedHeader{
256-
Header: header,
257-
Signer: types.Signer{
258-
PubKey: pubKey,
259-
Address: e.genesis.ProposerAddress,
260-
},
261-
Signature: signature,
262-
}
263-
264-
return e.store.SaveBlockData(ctx, genesisHeader, data, &signature)
265-
}
266-
267214
// executionLoop handles block production and aggregation
268215
func (e *Executor) executionLoop() {
269216
e.logger.Info().Msg("starting execution loop")
@@ -364,20 +311,41 @@ func (e *Executor) produceBlock() error {
364311
}
365312
}
366313

367-
// get batch from sequencer
368-
batchData, err := e.retrieveBatch(e.ctx)
369-
if errors.Is(err, common.ErrNoBatch) {
370-
e.logger.Debug().Msg("no batch available")
371-
return nil
372-
} else if errors.Is(err, common.ErrNoTransactionsInBatch) {
373-
e.logger.Debug().Msg("no transactions in batch")
374-
} else if err != nil {
375-
return fmt.Errorf("failed to retrieve batch: %w", err)
376-
}
314+
var (
315+
header *types.SignedHeader
316+
data *types.Data
317+
)
318+
319+
// Check if there's an already stored block at the newHeight
320+
// If there is use that instead of creating a new block
321+
pendingHeader, pendingData, err := e.store.GetBlockData(e.ctx, newHeight)
322+
if err == nil {
323+
e.logger.Info().Uint64("height", newHeight).Msg("using pending block")
324+
header = pendingHeader
325+
data = pendingData
326+
} else if !errors.Is(err, datastore.ErrNotFound) {
327+
return fmt.Errorf("failed to get block data: %w", err)
328+
} else {
329+
// get batch from sequencer
330+
batchData, err := e.retrieveBatch(e.ctx)
331+
if errors.Is(err, common.ErrNoBatch) {
332+
e.logger.Debug().Msg("no batch available")
333+
return nil
334+
} else if errors.Is(err, common.ErrNoTransactionsInBatch) {
335+
e.logger.Debug().Msg("no transactions in batch")
336+
} else if err != nil {
337+
return fmt.Errorf("failed to retrieve batch: %w", err)
338+
}
377339

378-
header, data, err := e.createBlock(e.ctx, newHeight, batchData)
379-
if err != nil {
380-
return fmt.Errorf("failed to create block: %w", err)
340+
header, data, err = e.createBlock(e.ctx, newHeight, batchData)
341+
if err != nil {
342+
return fmt.Errorf("failed to create block: %w", err)
343+
}
344+
345+
// saved early for crash recovery, will be overwritten later with the final signature
346+
if err = e.store.SaveBlockData(e.ctx, header, data, &types.Signature{}); err != nil {
347+
return fmt.Errorf("failed to save block: %w", err)
348+
}
381349
}
382350

383351
newState, err := e.applyBlock(e.ctx, header.Header, data)
@@ -459,18 +427,28 @@ func (e *Executor) retrieveBatch(ctx context.Context) (*BatchData, error) {
459427
// createBlock creates a new block from the given batch
460428
func (e *Executor) createBlock(ctx context.Context, height uint64, batchData *BatchData) (*types.SignedHeader, *types.Data, error) {
461429
currentState := e.GetLastState()
430+
headerTime := uint64(e.genesis.StartTime.UnixNano())
462431

463432
// Get last block info
464433
var lastHeaderHash types.Hash
465434
var lastDataHash types.Hash
435+
var lastSignature types.Signature
466436

467437
if height > e.genesis.InitialHeight {
438+
headerTime = uint64(batchData.UnixNano())
439+
468440
lastHeader, lastData, err := e.store.GetBlockData(ctx, height-1)
469441
if err != nil {
470442
return nil, nil, fmt.Errorf("failed to get last block: %w", err)
471443
}
472444
lastHeaderHash = lastHeader.Hash()
473445
lastDataHash = lastData.Hash()
446+
447+
lastSignaturePtr, err := e.store.GetSignature(ctx, height-1)
448+
if err != nil {
449+
return nil, nil, fmt.Errorf("failed to get last signature: %w", err)
450+
}
451+
lastSignature = *lastSignaturePtr
474452
}
475453

476454
// Get signer info
@@ -479,7 +457,6 @@ func (e *Executor) createBlock(ctx context.Context, height uint64, batchData *Ba
479457
return nil, nil, fmt.Errorf("failed to get public key: %w", err)
480458
}
481459

482-
// Build validator hash
483460
// Get validator hash
484461
validatorHash, err := e.options.ValidatorHasherProvider(e.genesis.ProposerAddress, pubKey)
485462
if err != nil {
@@ -496,14 +473,15 @@ func (e *Executor) createBlock(ctx context.Context, height uint64, batchData *Ba
496473
BaseHeader: types.BaseHeader{
497474
ChainID: e.genesis.ChainID,
498475
Height: height,
499-
Time: uint64(batchData.UnixNano()),
476+
Time: headerTime,
500477
},
501478
LastHeaderHash: lastHeaderHash,
502479
ConsensusHash: make(types.Hash, 32),
503480
AppHash: currentState.AppHash,
504481
ProposerAddress: e.genesis.ProposerAddress,
505482
ValidatorHash: validatorHash,
506483
},
484+
Signature: lastSignature,
507485
Signer: types.Signer{
508486
PubKey: pubKey,
509487
Address: e.genesis.ProposerAddress,
@@ -639,6 +617,9 @@ func (e *Executor) sendCriticalError(err error) {
639617

640618
// recordBlockMetrics records metrics for the produced block
641619
func (e *Executor) recordBlockMetrics(data *types.Data) {
620+
if data == nil || data.Metadata == nil {
621+
return
622+
}
642623
e.metrics.NumTxs.Set(float64(len(data.Txs)))
643624
e.metrics.TotalTxs.Add(float64(len(data.Txs)))
644625
e.metrics.BlockSizeBytes.Set(float64(data.Size()))

0 commit comments

Comments
 (0)