From 5970701b8f9c2330ff0887ea5faffe89d421baa0 Mon Sep 17 00:00:00 2001 From: Yacov Manevich Date: Thu, 17 Sep 2026 19:26:34 +0200 Subject: [PATCH] Use the prev inner block hash of the last non simplex block Previously to this commit, when building the first ever simplex block, simplex was using the previous block hash of the protocol metadata, which is computed differently and incorrectly because the block previous to the first ever simplex block is not a simplex block. Signed-off-by: Yacov Manevich --- adapters.go | 36 +++++++++++--- adapters_test.go | 23 ++++++--- external.go | 12 +++++ instance.go | 2 +- instance_helpers_test.go | 15 +++++- instance_test.go | 63 ++++++++++++++++++++++-- msm/msm.go | 13 +++-- msm/msm_test.go | 104 ++++++++++++++++++++++++++++++--------- util.go | 14 +++--- 9 files changed, 226 insertions(+), 56 deletions(-) diff --git a/adapters.go b/adapters.go index c3cc1593..6f6f4421 100644 --- a/adapters.go +++ b/adapters.go @@ -67,6 +67,7 @@ func (s *CallbackStorage) Retrieve(seq uint64) (common.VerifiedBlock, common.Fin return nil, common.Finalization{}, err } parsedBlock := &ParsedBlock{ + legacyBlock: seq <= s.lastNonSimplexHeight, msm: s.msm, StateMachineBlock: block, } @@ -108,16 +109,18 @@ func (cb *cachedBlock) Verify(ctx context.Context, verifyOpts ...common.VerifyOp } type CachedStorage struct { - msm *metadata.StateMachine - lock sync.RWMutex + lastNonSimplexHeight uint64 + msm *metadata.StateMachine + lock sync.RWMutex Storage cache map[common.Digest]cachedBlock } -func NewCachedStorage(storage Storage) *CachedStorage { +func NewCachedStorage(storage Storage, lastNonSimplexHeight uint64) *CachedStorage { return &CachedStorage{ - Storage: storage, - cache: make(map[common.Digest]cachedBlock), + lastNonSimplexHeight: lastNonSimplexHeight, + Storage: storage, + cache: make(map[common.Digest]cachedBlock), } } @@ -151,11 +154,25 @@ func (cs *CachedStorage) Retrieve(seq uint64, digest common.Digest) (common.Veri // We don't populate the cache here because we populate it externally. block, finalization, err := cs.GetBlock(seq) - if digest != (common.Digest{}) && block.Digest() != digest { - return nil, nil, common.ErrBlockNotFound + if err != nil { + return nil, nil, err + } + + legacy := seq <= cs.lastNonSimplexHeight + + if digest != (common.Digest{}) { + // A block predating Simplex is identified by its inner block's digest, not by a Simplex block digest. + blockDigest := block.Digest() + if legacy && block.InnerBlock != nil { + blockDigest = common.Digest(block.InnerBlock.Digest()) + } + if blockDigest != digest { + return nil, nil, common.ErrBlockNotFound + } } return &ParsedBlock{ + legacyBlock: legacy, StateMachineBlock: block, msm: cs.msm, }, finalization, err @@ -259,6 +276,7 @@ func (bw *blockBuilderWaiter) BuildBlock(ctx context.Context, metadata common.Pr } pb := &ParsedBlock{ + legacyBlock: metadata.Seq <= bw.cs.lastNonSimplexHeight, StateMachineBlock: *block, msm: bw.msm, } @@ -289,8 +307,12 @@ func (bd *blockDeserializer) DeserializeBlock(ctx context.Context, bytes []byte) innerBlock = block } + seq := rawBlock.Metadata.SimplexProtocolMetadata.Seq + legacy := seq <= bd.cs.lastNonSimplexHeight + return &cachedBlock{ ParsedBlock: &ParsedBlock{ + legacyBlock: legacy, StateMachineBlock: metadata.StateMachineBlock{ InnerBlock: innerBlock, Metadata: rawBlock.Metadata, diff --git a/adapters_test.go b/adapters_test.go index ee11f79f..00ddeba3 100644 --- a/adapters_test.go +++ b/adapters_test.go @@ -38,7 +38,7 @@ func newTestParsedBlock(num uint64, payload string) *ParsedBlock { // and a verified but not yet indexed block at seq 5. A zero digest matches on // seq alone, a non-zero digest must match the block's digest exactly. func TestCachedStorageRetrieve(t *testing.T) { - cs := NewCachedStorage(newTestStorage()) + cs := NewCachedStorage(newTestStorage(), 0) indexedBlock := newTestParsedBlock(0, "indexed") require.NoError(t, cs.Index(t.Context(), indexedBlock, common.Finalization{})) @@ -56,7 +56,9 @@ func TestCachedStorageRetrieve(t *testing.T) { seq uint64 digest common.Digest wantBlock *ParsedBlock - wantErr error + // legacy marks a block predating Simplex, which is identified by its inner block's digest. + legacy bool + wantErr error }{ { name: "cached block by seq with zero digest", @@ -76,9 +78,11 @@ func TestCachedStorageRetrieve(t *testing.T) { wantErr: common.ErrBlockNotFound, }, { + // Seq 0 is at or below the last non-Simplex height, so it is served as a legacy block. name: "uncached seq falls through to storage", seq: 0, wantBlock: indexedBlock, + legacy: true, }, { name: "indexed block with mismatched digest", @@ -101,7 +105,11 @@ func TestCachedStorageRetrieve(t *testing.T) { return } require.NoError(t, err) - require.Equal(t, common.Digest(tt.wantBlock.Digest()), got.BlockHeader().Digest) + wantDigest := common.Digest(tt.wantBlock.Digest()) + if tt.legacy { + wantDigest = common.Digest(tt.wantBlock.InnerBlock.Digest()) + } + require.Equal(t, wantDigest, got.BlockHeader().Digest) if tt.wantBlock == verifiedBlock { require.Nil(t, fin) } @@ -113,7 +121,7 @@ func TestCachedStorageRetrieve(t *testing.T) { // a zero-digest Retrieve of that seq returns the finalized block with its // finalization, even when a verified fork at the same seq was cached. func TestCachedStorageIndexEvictsSameSeqFork(t *testing.T) { - cs := NewCachedStorage(newTestStorage()) + cs := NewCachedStorage(newTestStorage(), 0) require.NoError(t, cs.Index(t.Context(), newTestParsedBlock(0, "genesis"), common.Finalization{})) equivocatedBlock := &cachedBlock{ @@ -181,7 +189,7 @@ func TestCachedStoragePopulatedByWal(t *testing.T) { // it is finalized and indexed. func TestCachedStoragePopulatedBySelfBuiltBlock(t *testing.T) { storage := newTestStorageWithGenesis(t) - cs := NewCachedStorage(storage) + cs := NewCachedStorage(storage, 0) msm, err := metadata.NewStateMachine(&metadata.Config{ Logger: testutil.MakeLogger(t, 1), @@ -196,9 +204,8 @@ func TestCachedStoragePopulatedBySelfBuiltBlock(t *testing.T) { vm := newBlockBuilderVM(storage, newPendingBlockSignal()) bw := newBlockBuilderWaiter(msm, cs, vm) - // Build a block on top of genesis - genesis := &ParsedBlock{StateMachineBlock: metadata.StateMachineBlock{InnerBlock: genesisBlock}} - md := common.ProtocolMetadata{Seq: 1, Prev: genesis.BlockHeader().Digest} + // Build a block on top of genesis, which as a block predating Simplex is identified by its inner digest. + md := common.ProtocolMetadata{Seq: 1, Prev: common.Digest(genesisBlock.Digest())} vb, built := bw.BuildBlock(t.Context(), md, common.Blacklist{}) require.True(t, built) require.Equal(t, md.Seq, vb.BlockHeader().Seq) diff --git a/external.go b/external.go index 0d4fa192..b342a1a2 100644 --- a/external.go +++ b/external.go @@ -15,6 +15,8 @@ type ParsedBlock struct { metadata.StateMachineBlock msm *metadata.StateMachine + legacyBlock bool // true if this is not a simplex block, but a block pre-dating simplex. + // lock guards size, so Size() can be invoked concurrently lock sync.Mutex // size caches the length of the Bytes encoding, computed on first use @@ -27,6 +29,11 @@ func (p *ParsedBlock) Bytes() []byte { rawInnerBlock := p.InnerBlock.Bytes() innerBlockBytes = rawInnerBlock } + + if p.legacyBlock { + return innerBlockBytes + } + rawBlock := &metadata.RawBlock{ Metadata: p.Metadata.Clone(), InnerBlockBytes: innerBlockBytes, @@ -37,6 +44,11 @@ func (p *ParsedBlock) Bytes() []byte { func (p *ParsedBlock) BlockHeader() common.BlockHeader { md := p.Metadata.SimplexProtocolMetadata.Clone() digest := p.Digest() + + if p.legacyBlock { + digest = p.InnerBlock.Digest() + } + return common.BlockHeader{ ProtocolMetadata: md, Digest: digest, diff --git a/instance.go b/instance.go index 5888e099..c2ff88af 100644 --- a/instance.go +++ b/instance.go @@ -77,7 +77,7 @@ type Instance struct { } func NewInstance(config Config) *Instance { - cs := NewCachedStorage(config.Storage) + cs := NewCachedStorage(config.Storage, config.LastNonSimplexInnerBlock.Height()) // Non-validators have no block builder, so they pass a nil approval handler: // they broadcast approvals but do not need to record their own locally. transitionListener := newEpochTransitionListener( diff --git a/instance_helpers_test.go b/instance_helpers_test.go index e67d443a..85df9daf 100644 --- a/instance_helpers_test.go +++ b/instance_helpers_test.go @@ -240,9 +240,20 @@ func newTestStorage() *testStorage { } func newTestStorageWithGenesis(t *testing.T) *testStorage { + return newTestStorageWithPreSimplexBlocks(t, genesisBlock) +} + +func newTestStorageWithPreSimplexBlocks(t *testing.T, blocks ...*testInnerBlock) *testStorage { s := newTestStorage() - genesis := &ParsedBlock{StateMachineBlock: metadata.StateMachineBlock{InnerBlock: genesisBlock}} - require.NoError(t, s.Index(context.Background(), genesis, common.Finalization{})) + for _, inner := range blocks { + block := &ParsedBlock{StateMachineBlock: metadata.StateMachineBlock{ + InnerBlock: inner, + Metadata: metadata.StateMachineMetadata{ + SimplexProtocolMetadata: common.ProtocolMetadata{Seq: inner.Height()}, + }, + }} + require.NoError(t, s.Index(context.Background(), block, common.Finalization{})) + } return s } diff --git a/instance_test.go b/instance_test.go index 736172b0..3526e6cb 100644 --- a/instance_test.go +++ b/instance_test.go @@ -142,10 +142,11 @@ func TestInstanceDropsMessagesBeforeStart(t *testing.T) { validator := newNodeMapping(1) pChain := newTestPChain([]metadata.NodeBLSMapping{validator}) instance := NewInstance(Config{ - PlatformChain: pChain, - Storage: newTestStorageWithGenesis(t), - Logger: testutil.MakeLogger(t, 1), - ID: validator.NodeID[:], + LastNonSimplexInnerBlock: genesisBlock, + PlatformChain: pChain, + Storage: newTestStorageWithGenesis(t), + Logger: testutil.MakeLogger(t, 1), + ID: validator.NodeID[:], }) msg := &common.Message{Finalization: &common.Finalization{}} @@ -556,3 +557,57 @@ func TestValidatorSetsMetadataFromSnowman(t *testing.T) { require.Equal(t, uint64(1), block.BlockHeader().Round) require.Equal(t, numNonSimplexBlocks, block.BlockHeader().Seq) } + +// TestInstanceZeroBlockAfterPreSimplexBlocks brings up a network whose ledger already holds pre-Simplex +// blocks beyond the genesis block, and asserts that the zero block the network commits chains to the +// last non-Simplex block through that block's inner digest, at the sequence right above it. +func TestInstanceZeroBlockAfterPreSimplexBlocks(t *testing.T) { + // The ledger holds pre-Simplex blocks from height 0 (genesis) to lastNonSimplexHeight. + const lastNonSimplexHeight = uint64(3) + zeroBlockSeq := lastNonSimplexHeight + 1 + + // Timestamps lie in the past because the zero block carries over the last non-Simplex block's timestamp. + preSimplexBlocks := make([]*testInnerBlock, 0, lastNonSimplexHeight+1) + for h := uint64(0); h <= lastNonSimplexHeight; h++ { + preSimplexBlocks = append(preSimplexBlocks, &testInnerBlock{ + Height_: h, + TS: time.Now().Add(-time.Duration(lastNonSimplexHeight-h+1) * time.Second), + Payload: append([]byte("pre-simplex block "), byte('0'+h)), + }) + } + lastNonSimplexBlock := preSimplexBlocks[lastNonSimplexHeight] + + // Two validators, so neither can commit the zero block alone: the leader's proposal is only + // committed once the other node has verified it against its own copy of the pre-Simplex chain. + nodeA := newNodeMapping(1) + nodeB := newNodeMapping(2) + pChain := newTestPChain(metadata.NodeBLSMappings{nodeA, nodeB}) + + network := newNetwork(t, pChain) + // The zero block is built automatically at zeroBlockSeq; the first block the network asks for follows it. + network.seq = zeroBlockSeq + 1 + + for _, id := range []common.NodeID{nodeA.NodeID[:], nodeB.NodeID[:]} { + network.addNodeWithConfig(id, nodeConfig{ + storage: newTestStorageWithPreSimplexBlocks(t, preSimplexBlocks...), + lastNonSimplexBlock: lastNonSimplexBlock, + }) + } + + // Both nodes commit the zero block, then a couple of ordinary Simplex blocks on top of it. + network.sync() + network.acceptNewBlock() + network.acceptNewBlock() + + for _, n := range network.nodesSnapshot() { + zeroBlock, _, err := n.storage.GetBlock(zeroBlockSeq) + require.NoError(t, err) + require.Equal(t, metadata.BlockTypeZero, zeroBlock.Type()) + require.Nil(t, zeroBlock.InnerBlock) + + md := zeroBlock.Metadata.SimplexProtocolMetadata + require.Equal(t, common.Digest(lastNonSimplexBlock.Digest()), md.Prev, + "zero block must point to the last non-Simplex block by its inner digest") + require.Equal(t, zeroBlockSeq, md.Seq) + } +} diff --git a/msm/msm.go b/msm/msm.go index fd902b00..db6d3798 100644 --- a/msm/msm.go +++ b/msm/msm.go @@ -71,7 +71,7 @@ var ( errNilBlock = errors.New("block is nil") errInvalidPChainHeight = errors.New("invalid P-chain height") errZeroBlockHasInnerBlock = errors.New("zero block must not have an inner block") - errZeroBlockInnerDigestMismatch = errors.New("zero block inner block digest does not match last non-Simplex inner block digest") + errZeroBlockPrevDigestMismatch = errors.New("zero block previous digest does not match last non-Simplex inner block digest") errZeroBlockTimestampMismatch = errors.New("zero block timestamp does not match last non-Simplex inner block timestamp") errPrevSealingBlockNotFinalized = errors.New("previous sealing block is not finalized") errBlockDigestMismatch = errors.New("does not match proposed block digest") @@ -905,6 +905,11 @@ func (sm *StateMachine) buildBlockZero(parentBlock StateMachineBlock, simplexMet timestamp := sm.LastNonSimplexInnerBlock.Timestamp().UnixMilli() simplexEpochInfo := constructSimplexZeroBlockSimplexEpochInfo(pChainHeight, validatorSet, prevVMBlockSeq) + // The zero block builds on top of the last non-Simplex block, which is identified by its inner block's + // digest rather than by a Simplex block digest, and sits right above it in the sequence. + simplexMetadata.Prev = sm.LastNonSimplexInnerBlock.Digest() + simplexMetadata.Seq = sm.LastNonSimplexInnerBlock.Height() + 1 + // The zero block carries over the parent's ICM epoch unchanged, just as it carries over the // timestamp. If the parent is a genesis block that predates ICM, the carried-over epoch is empty, // and the first ICM epoch begins on the block built on top of the zero block. @@ -984,8 +989,10 @@ func (sm *StateMachine) verifyBlockZero(block *StateMachineBlock, prevBlock Stat if block.InnerBlock != nil { return errZeroBlockHasInnerBlock } - if prevBlock.InnerBlock.Digest() != sm.LastNonSimplexInnerBlock.Digest() { - return errZeroBlockInnerDigestMismatch + + // The zero block must build upon the last non-Simplex block + if block.Metadata.SimplexProtocolMetadata.Prev != sm.LastNonSimplexInnerBlock.Digest() { + return errZeroBlockPrevDigestMismatch } // The timestamp must equal the last non-Simplex inner block's timestamp. diff --git a/msm/msm_test.go b/msm/msm_test.go index fbd7f059..d7148d98 100644 --- a/msm/msm_test.go +++ b/msm/msm_test.go @@ -169,7 +169,7 @@ func TestMSMFirstSimplexBlockAfterPreSimplexBlocks(t *testing.T) { Round: 0, Seq: 43, Epoch: 43, - Prev: preSimplexParent.Digest(), + Prev: preSimplexParent.InnerBlock.Digest(), } sm1, testConfig1 := newStateMachine(t) @@ -219,6 +219,65 @@ func TestMSMFirstSimplexBlockAfterPreSimplexBlocks(t *testing.T) { require.NoError(t, sm2.VerifyBlock(context.Background(), block)) } +// TestMSMZeroBlockPrevIsLastNonSimplexInnerBlockDigest ensures the zero block anchors itself to the +// last non-Simplex block via that block's inner digest, and not via the digest consensus hands us +// in the protocol metadata. +func TestMSMZeroBlockPrevIsLastNonSimplexInnerBlockDigest(t *testing.T) { + preSimplexParent := StateMachineBlock{ + InnerBlock: &testutil.InnerBlock{ + TS: time.Now(), + BlockHeight: 42, + Content: []byte{4, 5, 6}, + }, + } + + innerDigest := common.Digest(preSimplexParent.InnerBlock.Digest()) + outerDigest := common.Digest(preSimplexParent.Digest()) + require.NotEqual(t, innerDigest, outerDigest) + + newZeroBlockStateMachine := func(t *testing.T) *StateMachine { + sm, tc := newStateMachine(t) + tc.blockStore[42] = &outerBlock{block: preSimplexParent} + sm.LastNonSimplexInnerBlock = preSimplexParent.InnerBlock + return sm + } + + t.Run("built zero block points to the inner digest", func(t *testing.T) { + sm := newZeroBlockStateMachine(t) + + block, err := sm.BuildBlock(context.Background(), common.ProtocolMetadata{ + Round: 0, + Seq: 43, + Epoch: 43, + Prev: outerDigest, + }, emptyBlacklist) + require.NoError(t, err) + + require.Equal(t, innerDigest, block.Metadata.SimplexProtocolMetadata.Prev) + require.Equal(t, preSimplexParent.InnerBlock.Height()+1, block.Metadata.SimplexProtocolMetadata.Seq) + + // A different node, which only knows the last non-Simplex block, accepts it. + require.NoError(t, newZeroBlockStateMachine(t).VerifyBlock(context.Background(), block)) + }) + + t.Run("zero block not pointing to the inner digest is rejected", func(t *testing.T) { + sm := newZeroBlockStateMachine(t) + + block, err := sm.BuildBlock(context.Background(), common.ProtocolMetadata{ + Round: 0, + Seq: 43, + Epoch: 43, + Prev: innerDigest, + }, emptyBlacklist) + require.NoError(t, err) + + block.Metadata.SimplexProtocolMetadata.Prev = outerDigest + + err = newZeroBlockStateMachine(t).VerifyBlock(context.Background(), block) + require.ErrorIs(t, err, errZeroBlockPrevDigestMismatch) + }) +} + func TestMSMBuildBlockRejectsZeroSeq(t *testing.T) { // Seq 0 is reserved for the genesis block, which should never be built. sm, _ := newStateMachine(t) @@ -481,24 +540,21 @@ func TestMSMFullEpochLifecycle(t *testing.T) { } // ----- Step 0: Building on top of genesis or upgrading to Simplex----- - genesis := StateMachineBlock{ - InnerBlock: &testutil.InnerBlock{ - BlockHeight: 0, // Genesis block has height 0 - TS: startTime, - Content: []byte{0}, - }, + genesis := &testutil.InnerBlock{ + BlockHeight: 0, // Genesis block has height 0 + TS: startTime, + Content: []byte{0}, } - notGenesis := StateMachineBlock{ - InnerBlock: &testutil.InnerBlock{ - BlockHeight: 42, - TS: startTime, - Content: []byte{0}, - }, + notGenesis := &testutil.InnerBlock{ + BlockHeight: 42, + TS: startTime, + Content: []byte{0}, } + for _, testCase := range []struct { name string - firstBlockBeforeSimplex StateMachineBlock + firstBlockBeforeSimplex *testutil.InnerBlock epochNum uint64 // firstBlockICMEpochInfo is the ICM epoch of the pre-Simplex parent, which the zero block // carries over. A genesis parent predates ICM, so its ICM epoch is empty and the first epoch @@ -514,7 +570,7 @@ func TestMSMFullEpochLifecycle(t *testing.T) { { name: "upgrading to Simplex from pre-Simplex blocks", firstBlockBeforeSimplex: notGenesis, - epochNum: notGenesis.InnerBlock.Height() + 1, + epochNum: notGenesis.Height() + 1, firstBlockICMEpochInfo: ICMEpochInfo{ PChainEpochHeight: pChainHeight1, EpochNumber: 1, @@ -576,9 +632,6 @@ func TestMSMFullEpochLifecycle(t *testing.T) { EpochStartTime: uint64(startTime.Unix()) + 1, } - // The zero block carries over the parent's ICM epoch. - testCase.firstBlockBeforeSimplex.Metadata.ICMEpochInfo = testCase.firstBlockICMEpochInfo - sm, tc := newStateMachine(t) sm.GetValidatorSet = getValidatorSet @@ -590,10 +643,10 @@ func TestMSMFullEpochLifecycle(t *testing.T) { // behavior is covered by TestVerifyCollectingApprovalsNotReady and // TestCollectAuxiliaryInfo. sm.AuxiliaryInfoApp = &noopTestAuxInfoApp{} - tc.blockStore[0] = &outerBlock{block: genesis} - tc.blockStore[42] = &outerBlock{block: notGenesis} + tc.blockStore[0] = &outerBlock{block: StateMachineBlock{InnerBlock: genesis}} + tc.blockStore[42] = &outerBlock{block: StateMachineBlock{InnerBlock: notGenesis}} - sm.LastNonSimplexInnerBlock = testCase.firstBlockBeforeSimplex.InnerBlock + sm.LastNonSimplexInnerBlock = testCase.firstBlockBeforeSimplex sm.GenesisValidatorSet = validatorSet1 sm.LastNonSimplexBlockPChainHeight = pChainHeight1 @@ -618,7 +671,7 @@ func TestMSMFullEpochLifecycle(t *testing.T) { smVerify.GetTime = fixedTime - smVerify.LastNonSimplexInnerBlock = testCase.firstBlockBeforeSimplex.InnerBlock + smVerify.LastNonSimplexInnerBlock = testCase.firstBlockBeforeSimplex smVerify.GenesisValidatorSet = validatorSet1 smVerify.LastNonSimplexBlockPChainHeight = pChainHeight1 @@ -628,8 +681,11 @@ func TestMSMFullEpochLifecycle(t *testing.T) { tcVerify.blockStore[seq] = &outerBlock{block: block, finalization: fin} } - baseSeq := testCase.firstBlockBeforeSimplex.InnerBlock.Height() - addBlock(baseSeq, testCase.firstBlockBeforeSimplex, nil) + baseSeq := testCase.firstBlockBeforeSimplex.Height() + addBlock(baseSeq, StateMachineBlock{InnerBlock: testCase.firstBlockBeforeSimplex, Metadata: StateMachineMetadata{ + // The zero block carries over the parent's ICM epoch. + ICMEpochInfo: testCase.firstBlockICMEpochInfo, + }}, nil) aggr := &signatureAggregator{} diff --git a/util.go b/util.go index 977dabb5..b633709b 100644 --- a/util.go +++ b/util.go @@ -44,8 +44,8 @@ func getLastAcceptedEpochAndValidatorSet(config *Config) (common.Nodes, uint64, } lastNonSimplexHeight := config.LastNonSimplexInnerBlock.Height() - parsedLastBlock := ParsedBlock{StateMachineBlock: lastBlock} - epochNum := parsedLastBlock.BlockHeader().Epoch + epochNum := lastBlock.Metadata.SimplexProtocolMetadata.Epoch + seq := lastBlock.Metadata.SimplexProtocolMetadata.Seq genesisValidatorSet := config.PlatformChain.GenesisValidatorSet() var validatorSet metadata.NodeBLSMappings @@ -60,14 +60,14 @@ func getLastAcceptedEpochAndValidatorSet(config *Config) (common.Nodes, uint64, zap.Uint64("epoch", epochNum)) // If the last block persisted is a sealing block, then we are in the next epoch. case lastBlock.SealingBlockInfo() != nil: - epochNum = parsedLastBlock.BlockHeader().Seq + epochNum = seq nodes = lastBlock.SealingBlockInfo().ValidatorSet config.Logger.Debug("Determined epoch and validator set from sealing block at tip", zap.Uint64("epoch", epochNum)) // Else, we have at least one Simplex block in the ledger, and it's not a sealing block. default: // Therefore, the sequence of the sealing block is the epoch number. - sealingBlockSeq := parsedLastBlock.BlockHeader().Epoch + sealingBlockSeq := epochNum sealingBlock, _, err := config.Storage.GetBlock(sealingBlockSeq) if err != nil { return nil, 0, fmt.Errorf("error retrieving sealing block from storage: %w", err) @@ -75,7 +75,7 @@ func getLastAcceptedEpochAndValidatorSet(config *Config) (common.Nodes, uint64, if sealingBlock.Metadata.SimplexEpochInfo.BlockValidationDescriptor == nil { return nil, 0, fmt.Errorf("%w at seq %d", errNonSealingBlock, sealingBlockSeq) } - validatorSet = constructValidatorSetFromSealingBlock(&ParsedBlock{StateMachineBlock: sealingBlock}) + validatorSet = constructValidatorSetFromSealingBlock(sealingBlock.Metadata.SimplexEpochInfo.BlockValidationDescriptor) nodes = validatorSetToNodes(validatorSet) config.Logger.Debug("Determined epoch and validator set from sealing block in storage", zap.Uint64("epoch", epochNum), zap.Uint64("sealingBlockSeq", sealingBlockSeq)) @@ -96,9 +96,9 @@ func validatorSetToNodes(validatorSet metadata.NodeBLSMappings) common.Nodes { return nodes } -func constructValidatorSetFromSealingBlock(lastBlock *ParsedBlock) metadata.NodeBLSMappings { +func constructValidatorSetFromSealingBlock(bvd *metadata.BlockValidationDescriptor) metadata.NodeBLSMappings { var validatorSet metadata.NodeBLSMappings - vdrs := lastBlock.Metadata.SimplexEpochInfo.BlockValidationDescriptor.AggregatedMembership.Members + vdrs := bvd.AggregatedMembership.Members for i := range vdrs { vdr := &vdrs[i] validatorSet = append(validatorSet, metadata.NodeBLSMapping{