From d83534ea07bee2fd0e3d23def081d821bab301fb Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Mon, 6 May 2024 16:28:51 -0400 Subject: [PATCH 01/13] sketch out required changes --- state/protocol/inmem/encodable.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 7890f335ce8..2ad5a750d39 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -17,6 +17,27 @@ type EncodableSnapshot struct { SealedVersionBeacon *flow.SealedVersionBeacon } +func (snap EncodableSnapshot) getHead() *flow.Header { + return snap.SealingSegment.Highest().Header +} + +func (snap EncodableSnapshot) getLatestSeal() *flow.Seal { + head := snap.getHead() + latestSealID := snap.SealingSegment.LatestSeals[head.ID()] + _ = latestSealID + // iterate backward through payloads of snap.SealingSegment.Blocks + // search for seal with matching ID + return nil +} + +func (snap EncodableSnapshot) getLatestResult() *flow.ExecutionResult { + latestSeal := snap.getLatestSeal() + _ = latestSeal + // iterate backward through payloads of snap.SealingSegment.Blocks + // search for result with ID matching latestSeal.ResultID + return nil +} + // EncodableDKG is the encoding format for protocol.DKG type EncodableDKG struct { GroupKey encodable.RandomBeaconPubKey From 6bafeac5da4eb8365791af8f8a02fa9602b42610 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 9 May 2024 12:17:35 -0400 Subject: [PATCH 02/13] remove Head field from EncodableSnapshot --- state/protocol/badger/snapshot_test.go | 2 +- state/protocol/badger/state_test.go | 2 +- state/protocol/badger/validity_test.go | 8 ++++---- state/protocol/inmem/convert.go | 6 ------ state/protocol/inmem/encodable.go | 4 ++-- state/protocol/inmem/snapshot.go | 2 +- state/protocol/util_test.go | 2 +- 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/state/protocol/badger/snapshot_test.go b/state/protocol/badger/snapshot_test.go index 9ed7d369249..16def36ef9b 100644 --- a/state/protocol/badger/snapshot_test.go +++ b/state/protocol/badger/snapshot_test.go @@ -40,7 +40,7 @@ func TestUnknownReferenceBlock(t *testing.T) { util.RunWithFullProtocolState(t, rootSnapshot, func(db *badger.DB, state *bprotocol.ParticipantState) { // build some finalized non-root blocks (heights 101-110) - head := unittest.BlockWithParentFixture(rootSnapshot.Encodable().Head) + head := unittest.BlockWithParentFixture(rootSnapshot.Encodable().GetHead()) head.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(rootProtocolStateID))) buildFinalizedBlock(t, state, head) diff --git a/state/protocol/badger/state_test.go b/state/protocol/badger/state_test.go index 7721ffad3fb..33ca2232c25 100644 --- a/state/protocol/badger/state_test.go +++ b/state/protocol/badger/state_test.go @@ -180,7 +180,7 @@ func TestBootstrap_EpochHeightBoundaries(t *testing.T) { t.Parallel() // start with a regular post-spork root snapshot rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) - epoch1FirstHeight := rootSnapshot.Encodable().Head.Height + epoch1FirstHeight := rootSnapshot.Encodable().GetHead().Height // For the spork root snapshot, only the first height of the root epoch should be indexed. // [x] diff --git a/state/protocol/badger/validity_test.go b/state/protocol/badger/validity_test.go index e1bfa95bb62..490b1ecb0bb 100644 --- a/state/protocol/badger/validity_test.go +++ b/state/protocol/badger/validity_test.go @@ -24,14 +24,14 @@ func TestEntityExpirySnapshotValidation(t *testing.T) { }) t.Run("not-enough-history", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) - rootSnapshot.Encodable().Head.Height += 10 // advance height to be not spork root snapshot + rootSnapshot.Encodable().GetHead().Height += 10 // advance height to be not spork root snapshot err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) require.Error(t, err) }) t.Run("enough-history-spork-just-started", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) // advance height to be not spork root snapshot, but still lower than transaction expiry - rootSnapshot.Encodable().Head.Height += flow.DefaultTransactionExpiry / 2 + rootSnapshot.Encodable().GetHead().Height += flow.DefaultTransactionExpiry / 2 // add blocks to sealing segment rootSnapshot.Encodable().SealingSegment.ExtraBlocks = unittest.BlockFixtures(int(flow.DefaultTransactionExpiry / 2)) err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) @@ -40,7 +40,7 @@ func TestEntityExpirySnapshotValidation(t *testing.T) { t.Run("enough-history-long-spork", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) // advance height to be not spork root snapshot - rootSnapshot.Encodable().Head.Height += flow.DefaultTransactionExpiry * 2 + rootSnapshot.Encodable().GetHead().Height += flow.DefaultTransactionExpiry * 2 // add blocks to sealing segment rootSnapshot.Encodable().SealingSegment.ExtraBlocks = unittest.BlockFixtures(int(flow.DefaultTransactionExpiry) - 1) err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) @@ -49,7 +49,7 @@ func TestEntityExpirySnapshotValidation(t *testing.T) { t.Run("more-history-than-needed", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) // advance height to be not spork root snapshot - rootSnapshot.Encodable().Head.Height += flow.DefaultTransactionExpiry * 2 + rootSnapshot.Encodable().GetHead().Height += flow.DefaultTransactionExpiry * 2 // add blocks to sealing segment rootSnapshot.Encodable().SealingSegment.ExtraBlocks = unittest.BlockFixtures(flow.DefaultTransactionExpiry * 2) err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) diff --git a/state/protocol/inmem/convert.go b/state/protocol/inmem/convert.go index fbbc3ea9078..a76b58110f0 100644 --- a/state/protocol/inmem/convert.go +++ b/state/protocol/inmem/convert.go @@ -23,15 +23,10 @@ func FromSnapshot(from protocol.Snapshot) (*Snapshot, error) { ) // convert top-level fields - snap.Head, err = from.Head() - if err != nil { - return nil, fmt.Errorf("could not get head: %w", err) - } snap.LatestResult, snap.LatestSeal, err = from.SealedResult() if err != nil { return nil, fmt.Errorf("could not get seal: %w", err) } - snap.SealingSegment, err = from.SealingSegment() if err != nil { return nil, fmt.Errorf("could not get sealing segment: %w", err) @@ -218,7 +213,6 @@ func SnapshotFromBootstrapStateWithParams( } snap := SnapshotFromEncodable(EncodableSnapshot{ - Head: root.Header, LatestSeal: seal, LatestResult: result, SealingSegment: &flow.SealingSegment{ diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 2ad5a750d39..7e46ccaa831 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -17,12 +17,12 @@ type EncodableSnapshot struct { SealedVersionBeacon *flow.SealedVersionBeacon } -func (snap EncodableSnapshot) getHead() *flow.Header { +func (snap EncodableSnapshot) GetHead() *flow.Header { return snap.SealingSegment.Highest().Header } func (snap EncodableSnapshot) getLatestSeal() *flow.Seal { - head := snap.getHead() + head := snap.GetHead() latestSealID := snap.SealingSegment.LatestSeals[head.ID()] _ = latestSealID // iterate backward through payloads of snap.SealingSegment.Blocks diff --git a/state/protocol/inmem/snapshot.go b/state/protocol/inmem/snapshot.go index 955b796368c..990a92873ac 100644 --- a/state/protocol/inmem/snapshot.go +++ b/state/protocol/inmem/snapshot.go @@ -20,7 +20,7 @@ type Snapshot struct { var _ protocol.Snapshot = (*Snapshot)(nil) func (s Snapshot) Head() (*flow.Header, error) { - return s.enc.Head, nil + return s.enc.GetHead(), nil } func (s Snapshot) QuorumCertificate() (*flow.QuorumCertificate, error) { diff --git a/state/protocol/util_test.go b/state/protocol/util_test.go index 81e9489815c..3863a483909 100644 --- a/state/protocol/util_test.go +++ b/state/protocol/util_test.go @@ -26,7 +26,7 @@ func TestIsSporkRootSnapshot(t *testing.T) { t.Run("other snapshot", func(t *testing.T) { snapshot := unittest.RootSnapshotFixture(unittest.IdentityListFixture(10, unittest.WithAllRoles())) - snapshot.Encodable().Head.Height += 1 // modify head height to break equivalence with spork root block height + snapshot.Encodable().GetHead().Height += 1 // modify head height to break equivalence with spork root block height isSporkRoot, err := protocol.IsSporkRootSnapshot(snapshot) require.NoError(t, err) assert.False(t, isSporkRoot) From d23a12aca5832df21c6f3f584f149119bb04ce9d Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 9 May 2024 12:19:48 -0400 Subject: [PATCH 03/13] rename --- state/protocol/badger/snapshot_test.go | 2 +- state/protocol/badger/state_test.go | 2 +- state/protocol/badger/validity_test.go | 8 ++++---- state/protocol/inmem/encodable.go | 6 +++--- state/protocol/inmem/snapshot.go | 2 +- state/protocol/util_test.go | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/state/protocol/badger/snapshot_test.go b/state/protocol/badger/snapshot_test.go index 16def36ef9b..6e74d74a06d 100644 --- a/state/protocol/badger/snapshot_test.go +++ b/state/protocol/badger/snapshot_test.go @@ -40,7 +40,7 @@ func TestUnknownReferenceBlock(t *testing.T) { util.RunWithFullProtocolState(t, rootSnapshot, func(db *badger.DB, state *bprotocol.ParticipantState) { // build some finalized non-root blocks (heights 101-110) - head := unittest.BlockWithParentFixture(rootSnapshot.Encodable().GetHead()) + head := unittest.BlockWithParentFixture(rootSnapshot.Encodable().Head()) head.SetPayload(unittest.PayloadFixture(unittest.WithProtocolStateID(rootProtocolStateID))) buildFinalizedBlock(t, state, head) diff --git a/state/protocol/badger/state_test.go b/state/protocol/badger/state_test.go index 33ca2232c25..d576935e7ff 100644 --- a/state/protocol/badger/state_test.go +++ b/state/protocol/badger/state_test.go @@ -180,7 +180,7 @@ func TestBootstrap_EpochHeightBoundaries(t *testing.T) { t.Parallel() // start with a regular post-spork root snapshot rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) - epoch1FirstHeight := rootSnapshot.Encodable().GetHead().Height + epoch1FirstHeight := rootSnapshot.Encodable().Head().Height // For the spork root snapshot, only the first height of the root epoch should be indexed. // [x] diff --git a/state/protocol/badger/validity_test.go b/state/protocol/badger/validity_test.go index 490b1ecb0bb..3a9bb04d9c1 100644 --- a/state/protocol/badger/validity_test.go +++ b/state/protocol/badger/validity_test.go @@ -24,14 +24,14 @@ func TestEntityExpirySnapshotValidation(t *testing.T) { }) t.Run("not-enough-history", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) - rootSnapshot.Encodable().GetHead().Height += 10 // advance height to be not spork root snapshot + rootSnapshot.Encodable().Head().Height += 10 // advance height to be not spork root snapshot err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) require.Error(t, err) }) t.Run("enough-history-spork-just-started", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) // advance height to be not spork root snapshot, but still lower than transaction expiry - rootSnapshot.Encodable().GetHead().Height += flow.DefaultTransactionExpiry / 2 + rootSnapshot.Encodable().Head().Height += flow.DefaultTransactionExpiry / 2 // add blocks to sealing segment rootSnapshot.Encodable().SealingSegment.ExtraBlocks = unittest.BlockFixtures(int(flow.DefaultTransactionExpiry / 2)) err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) @@ -40,7 +40,7 @@ func TestEntityExpirySnapshotValidation(t *testing.T) { t.Run("enough-history-long-spork", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) // advance height to be not spork root snapshot - rootSnapshot.Encodable().GetHead().Height += flow.DefaultTransactionExpiry * 2 + rootSnapshot.Encodable().Head().Height += flow.DefaultTransactionExpiry * 2 // add blocks to sealing segment rootSnapshot.Encodable().SealingSegment.ExtraBlocks = unittest.BlockFixtures(int(flow.DefaultTransactionExpiry) - 1) err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) @@ -49,7 +49,7 @@ func TestEntityExpirySnapshotValidation(t *testing.T) { t.Run("more-history-than-needed", func(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(participants) // advance height to be not spork root snapshot - rootSnapshot.Encodable().GetHead().Height += flow.DefaultTransactionExpiry * 2 + rootSnapshot.Encodable().Head().Height += flow.DefaultTransactionExpiry * 2 // add blocks to sealing segment rootSnapshot.Encodable().SealingSegment.ExtraBlocks = unittest.BlockFixtures(flow.DefaultTransactionExpiry * 2) err := ValidRootSnapshotContainsEntityExpiryRange(rootSnapshot) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 7e46ccaa831..2d4d8dba554 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -8,7 +8,6 @@ import ( // EncodableSnapshot is the encoding format for protocol.Snapshot type EncodableSnapshot struct { - Head *flow.Header LatestSeal *flow.Seal // TODO replace with same info from sealing segment LatestResult *flow.ExecutionResult // TODO replace with same info from sealing segment SealingSegment *flow.SealingSegment @@ -17,12 +16,13 @@ type EncodableSnapshot struct { SealedVersionBeacon *flow.SealedVersionBeacon } -func (snap EncodableSnapshot) GetHead() *flow.Header { +// Head returns the latest finalized header of the Snapshot. +func (snap EncodableSnapshot) Head() *flow.Header { return snap.SealingSegment.Highest().Header } func (snap EncodableSnapshot) getLatestSeal() *flow.Seal { - head := snap.GetHead() + head := snap.Head() latestSealID := snap.SealingSegment.LatestSeals[head.ID()] _ = latestSealID // iterate backward through payloads of snap.SealingSegment.Blocks diff --git a/state/protocol/inmem/snapshot.go b/state/protocol/inmem/snapshot.go index 990a92873ac..0024f7a18e6 100644 --- a/state/protocol/inmem/snapshot.go +++ b/state/protocol/inmem/snapshot.go @@ -20,7 +20,7 @@ type Snapshot struct { var _ protocol.Snapshot = (*Snapshot)(nil) func (s Snapshot) Head() (*flow.Header, error) { - return s.enc.GetHead(), nil + return s.enc.Head(), nil } func (s Snapshot) QuorumCertificate() (*flow.QuorumCertificate, error) { diff --git a/state/protocol/util_test.go b/state/protocol/util_test.go index 3863a483909..cf601183fe2 100644 --- a/state/protocol/util_test.go +++ b/state/protocol/util_test.go @@ -26,7 +26,7 @@ func TestIsSporkRootSnapshot(t *testing.T) { t.Run("other snapshot", func(t *testing.T) { snapshot := unittest.RootSnapshotFixture(unittest.IdentityListFixture(10, unittest.WithAllRoles())) - snapshot.Encodable().GetHead().Height += 1 // modify head height to break equivalence with spork root block height + snapshot.Encodable().Head().Height += 1 // modify head height to break equivalence with spork root block height isSporkRoot, err := protocol.IsSporkRootSnapshot(snapshot) require.NoError(t, err) assert.False(t, isSporkRoot) From 95b310834c0738418106a2597e90c95bc4113e52 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 9 May 2024 15:43:36 -0400 Subject: [PATCH 04/13] add GetLatestResult method to encodable snapshot --- consensus/integration/epoch_test.go | 14 ++++-- state/protocol/badger/snapshot_test.go | 2 +- state/protocol/badger/state_test.go | 6 +-- state/protocol/badger/validity.go | 8 ++-- state/protocol/inmem/convert.go | 6 --- state/protocol/inmem/encodable.go | 64 +++++++++++++++++++++----- state/protocol/inmem/encodable_test.go | 2 +- state/protocol/inmem/snapshot.go | 4 +- 8 files changed, 72 insertions(+), 34 deletions(-) diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index 2a96a1c9d72..7ae45c2f3dd 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -205,6 +205,8 @@ func withNextEpoch( // convert to encodable representation for simple modification encodableSnapshot := snapshot.Encodable() + rootResult, rootSeal, err := snapshot.SealedResult() + require.NoError(t, err) rootProtocolState := encodableSnapshot.SealingSegment.LatestProtocolStateEntry() epochProtocolState := rootProtocolState.EpochEntry @@ -236,7 +238,7 @@ func withNextEpoch( ActiveIdentities: flow.DynamicIdentityEntryListFromIdentities(nextEpochIdentities), } // Re-construct epoch protocol state with modified events (constructs ActiveIdentity fields) - epochProtocolState, err := flow.NewRichProtocolStateEntry( + epochProtocolState, err = flow.NewRichProtocolStateEntry( epochProtocolState.ProtocolStateEntry, epochProtocolState.PreviousEpochSetup, epochProtocolState.PreviousEpochCommit, currEpochSetup, currEpochCommit, @@ -258,16 +260,18 @@ func withNextEpoch( } // Since we modified the root protocol state, we need to update the root block's ProtocolStateID field. + // rootBlock is a pointer, so mutations apply to Snapshot rootBlock := encodableSnapshot.SealingSegment.Blocks[0] rootBlockPayload := rootBlock.Payload rootBlockPayload.ProtocolStateID = rootKVStore.ID() rootBlock.SetPayload(*rootBlockPayload) // Since we changed the root block, we need to update the QC, root result, and root seal. - encodableSnapshot.LatestResult.BlockID = rootBlock.ID() - encodableSnapshot.LatestSeal.ResultID = encodableSnapshot.LatestResult.ID() - encodableSnapshot.LatestSeal.BlockID = rootBlock.ID() + // rootResult and rootSeal are pointers, so mutations apply to Snapshot + rootResult.BlockID = rootBlock.ID() + rootSeal.ResultID = rootResult.ID() + rootSeal.BlockID = rootBlock.ID() encodableSnapshot.SealingSegment.LatestSeals = map[flow.Identifier]flow.Identifier{ - rootBlock.ID(): encodableSnapshot.LatestSeal.ID(), + rootBlock.ID(): encodableSnapshot.GetLatestSeal().ID(), } encodableSnapshot.QuorumCertificate = createQC(rootBlock) diff --git a/state/protocol/badger/snapshot_test.go b/state/protocol/badger/snapshot_test.go index 6e74d74a06d..f7dd16ac0a6 100644 --- a/state/protocol/badger/snapshot_test.go +++ b/state/protocol/badger/snapshot_test.go @@ -808,7 +808,7 @@ func TestSealingSegment_FailureCases(t *testing.T) { // Here, we want to specifically test correct handling of the edge case, where a block exists in storage // that has _lower height_ than the node's local root block. Such blocks are typically contained in the // bootstrapping data, such that all entities referenced in the local root block can be resolved. - // Is is possible to retrieve blocks that are lower than the local root block from storage, directly + // It is possible to retrieve blocks that are lower than the local root block from storage, directly // via their ID. Despite these blocks existing in storage, SealingSegment construction should be // because the known history is potentially insufficient when going below the root block. t.Run("sealing segment from block below local state root", func(t *testing.T) { diff --git a/state/protocol/badger/state_test.go b/state/protocol/badger/state_test.go index d576935e7ff..06370f2cb2f 100644 --- a/state/protocol/badger/state_test.go +++ b/state/protocol/badger/state_test.go @@ -609,7 +609,7 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.LatestSeal.BlockID = unittest.IdentifierFixture() + encodable.GetLatestSeal().BlockID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) @@ -620,7 +620,7 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.LatestResult.BlockID = unittest.IdentifierFixture() + encodable.GetLatestResult().BlockID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) @@ -631,7 +631,7 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.LatestSeal.ResultID = unittest.IdentifierFixture() + encodable.GetLatestSeal().ResultID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) diff --git a/state/protocol/badger/validity.go b/state/protocol/badger/validity.go index e6db77d8974..120947528ef 100644 --- a/state/protocol/badger/validity.go +++ b/state/protocol/badger/validity.go @@ -20,14 +20,14 @@ func IsValidRootSnapshot(snap protocol.Snapshot, verifyResultID bool) error { if err != nil { return fmt.Errorf("could not get sealing segment: %w", err) } - result, seal, err := snap.SealedResult() + err = segment.Validate() if err != nil { - return fmt.Errorf("could not latest sealed result: %w", err) + return fmt.Errorf("invalid root sealing segment: %w", err) } - err = segment.Validate() + result, seal, err := snap.SealedResult() if err != nil { - return fmt.Errorf("invalid root sealing segment: %w", err) + return fmt.Errorf("could not latest sealed result: %w", err) } highest := segment.Highest() // reference block of the snapshot diff --git a/state/protocol/inmem/convert.go b/state/protocol/inmem/convert.go index a76b58110f0..fa7bb88e24a 100644 --- a/state/protocol/inmem/convert.go +++ b/state/protocol/inmem/convert.go @@ -23,10 +23,6 @@ func FromSnapshot(from protocol.Snapshot) (*Snapshot, error) { ) // convert top-level fields - snap.LatestResult, snap.LatestSeal, err = from.SealedResult() - if err != nil { - return nil, fmt.Errorf("could not get seal: %w", err) - } snap.SealingSegment, err = from.SealingSegment() if err != nil { return nil, fmt.Errorf("could not get sealing segment: %w", err) @@ -213,8 +209,6 @@ func SnapshotFromBootstrapStateWithParams( } snap := SnapshotFromEncodable(EncodableSnapshot{ - LatestSeal: seal, - LatestResult: result, SealingSegment: &flow.SealingSegment{ Blocks: []*flow.Block{root}, ExecutionResults: flow.ExecutionResultList{result}, diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 2d4d8dba554..8cd2bdd8204 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -16,26 +16,66 @@ type EncodableSnapshot struct { SealedVersionBeacon *flow.SealedVersionBeacon } -// Head returns the latest finalized header of the Snapshot. +// Head returns the latest finalized header of the Snapshot, which is the block +// in the sealing segment with the greatest Height. +// The EncodableSnapshot receiver must be correctly formed. func (snap EncodableSnapshot) Head() *flow.Header { return snap.SealingSegment.Highest().Header } -func (snap EncodableSnapshot) getLatestSeal() *flow.Seal { +// GetLatestSeal returns the latest seal of the Snapshot. This is the seal +// for the block with the greatest height, of all seals in the Snapshot. +// The EncodableSnapshot receiver must be correctly formed. +func (snap EncodableSnapshot) GetLatestSeal() *flow.Seal { + // CASE 1: In the case of a spork root, the single block of the sealing + // segment is sealed by protocol definition by `FirstSeal`. + if snap.SealingSegment.IsSporkRoot() { + return snap.SealingSegment.FirstSeal + } + head := snap.Head() latestSealID := snap.SealingSegment.LatestSeals[head.ID()] - _ = latestSealID - // iterate backward through payloads of snap.SealingSegment.Blocks - // search for seal with matching ID - return nil + + // CASE 2: For a mid-spork root snapshot, there are multiple blocks in the sealing segment. + // Since seals are included in increasing height order, the latest seal must be in the + // first block (by height descending) which contains any seals. + for i := len(snap.SealingSegment.Blocks) - 1; i >= 0; i-- { + block := snap.SealingSegment.Blocks[i] + for _, seal := range block.Payload.Seals { + if seal.ID() == latestSealID { + return seal + } + } + if len(block.Payload.Seals) > 0 { + // We encountered a block with some seals, but not the latest seal. + // This can only occur in a structurally invalid SealingSegment. + panic("sanity check failed: no latest seal") + } + } + // Correctly formatted sealing segments must contain latest seal. + panic("unreachable for correctly formatted sealing segments") } -func (snap EncodableSnapshot) getLatestResult() *flow.ExecutionResult { - latestSeal := snap.getLatestSeal() - _ = latestSeal - // iterate backward through payloads of snap.SealingSegment.Blocks - // search for result with ID matching latestSeal.ResultID - return nil +// GetLatestResult returns the latest sealed result of the Snapshot. +// This is the result which is sealed by LatestSeal. +// The EncodableSnapshot receiver must be correctly formed. +func (snap EncodableSnapshot) GetLatestResult() *flow.ExecutionResult { + latestSeal := snap.GetLatestSeal() + + for _, result := range snap.SealingSegment.ExecutionResults { + if latestSeal.ResultID == result.ID() { + return result + } + } + for _, block := range snap.SealingSegment.Blocks { + for _, result := range block.Payload.Results { + if latestSeal.ResultID == result.ID() { + return result + } + } + } + // Correctly formatted sealing segments must contain latest result. + panic("unreachable for correctly formatted sealing segments") } // EncodableDKG is the encoding format for protocol.DKG diff --git a/state/protocol/inmem/encodable_test.go b/state/protocol/inmem/encodable_test.go index bc9aba73383..0186ddee44f 100644 --- a/state/protocol/inmem/encodable_test.go +++ b/state/protocol/inmem/encodable_test.go @@ -34,6 +34,6 @@ func TestEncodeDecode(t *testing.T) { require.NoError(t, err) // check that the computed and stored result IDs are consistent - decodedResult, decodedSeal := decodedSnapshot.LatestResult, decodedSnapshot.LatestSeal + decodedResult, decodedSeal := decodedSnapshot.GetLatestResult(), decodedSnapshot.GetLatestSeal() assert.Equal(t, decodedResult.ID(), decodedSeal.ResultID) } diff --git a/state/protocol/inmem/snapshot.go b/state/protocol/inmem/snapshot.go index 0024f7a18e6..bf05b92bec0 100644 --- a/state/protocol/inmem/snapshot.go +++ b/state/protocol/inmem/snapshot.go @@ -50,11 +50,11 @@ func (s Snapshot) Identity(nodeID flow.Identifier) (*flow.Identity, error) { } func (s Snapshot) Commit() (flow.StateCommitment, error) { - return s.enc.LatestSeal.FinalState, nil + return s.enc.GetLatestSeal().FinalState, nil } func (s Snapshot) SealedResult() (*flow.ExecutionResult, *flow.Seal, error) { - return s.enc.LatestResult, s.enc.LatestSeal, nil + return s.enc.GetLatestResult(), s.enc.GetLatestSeal(), nil } func (s Snapshot) SealingSegment() (*flow.SealingSegment, error) { From 843ddfcd56d1f22763833764700c902271e75184 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 9 May 2024 15:51:38 -0400 Subject: [PATCH 05/13] rename --- consensus/integration/epoch_test.go | 2 +- state/protocol/badger/state_test.go | 6 +++--- state/protocol/inmem/encodable.go | 25 ++++++++++++------------- state/protocol/inmem/encodable_test.go | 2 +- state/protocol/inmem/snapshot.go | 4 ++-- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index 7ae45c2f3dd..c184e5a4d76 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -271,7 +271,7 @@ func withNextEpoch( rootSeal.ResultID = rootResult.ID() rootSeal.BlockID = rootBlock.ID() encodableSnapshot.SealingSegment.LatestSeals = map[flow.Identifier]flow.Identifier{ - rootBlock.ID(): encodableSnapshot.GetLatestSeal().ID(), + rootBlock.ID(): encodableSnapshot.LatestSeal().ID(), } encodableSnapshot.QuorumCertificate = createQC(rootBlock) diff --git a/state/protocol/badger/state_test.go b/state/protocol/badger/state_test.go index 06370f2cb2f..17a202f8446 100644 --- a/state/protocol/badger/state_test.go +++ b/state/protocol/badger/state_test.go @@ -609,7 +609,7 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.GetLatestSeal().BlockID = unittest.IdentifierFixture() + encodable.LatestSeal().BlockID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) @@ -620,7 +620,7 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.GetLatestResult().BlockID = unittest.IdentifierFixture() + encodable.LatestSealedResult().BlockID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) @@ -631,7 +631,7 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.GetLatestSeal().ResultID = unittest.IdentifierFixture() + encodable.LatestSeal().ResultID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 8cd2bdd8204..caa3bb82cb2 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -8,8 +8,6 @@ import ( // EncodableSnapshot is the encoding format for protocol.Snapshot type EncodableSnapshot struct { - LatestSeal *flow.Seal // TODO replace with same info from sealing segment - LatestResult *flow.ExecutionResult // TODO replace with same info from sealing segment SealingSegment *flow.SealingSegment QuorumCertificate *flow.QuorumCertificate Params EncodableParams @@ -23,10 +21,10 @@ func (snap EncodableSnapshot) Head() *flow.Header { return snap.SealingSegment.Highest().Header } -// GetLatestSeal returns the latest seal of the Snapshot. This is the seal +// LatestSeal returns the latest seal of the Snapshot. This is the seal // for the block with the greatest height, of all seals in the Snapshot. // The EncodableSnapshot receiver must be correctly formed. -func (snap EncodableSnapshot) GetLatestSeal() *flow.Seal { +func (snap EncodableSnapshot) LatestSeal() *flow.Seal { // CASE 1: In the case of a spork root, the single block of the sealing // segment is sealed by protocol definition by `FirstSeal`. if snap.SealingSegment.IsSporkRoot() { @@ -56,17 +54,13 @@ func (snap EncodableSnapshot) GetLatestSeal() *flow.Seal { panic("unreachable for correctly formatted sealing segments") } -// GetLatestResult returns the latest sealed result of the Snapshot. -// This is the result which is sealed by LatestSeal. +// LatestSealedResult returns the latest sealed result of the Snapshot. This is the result which is sealed by LatestSeal. // The EncodableSnapshot receiver must be correctly formed. -func (snap EncodableSnapshot) GetLatestResult() *flow.ExecutionResult { - latestSeal := snap.GetLatestSeal() +func (snap EncodableSnapshot) LatestSealedResult() *flow.ExecutionResult { + latestSeal := snap.LatestSeal() - for _, result := range snap.SealingSegment.ExecutionResults { - if latestSeal.ResultID == result.ID() { - return result - } - } + // For both spork root and mid-spork snapshots, the latest sealing result must + // either appear in a block payload or in the ExecutionResults field. for _, block := range snap.SealingSegment.Blocks { for _, result := range block.Payload.Results { if latestSeal.ResultID == result.ID() { @@ -74,6 +68,11 @@ func (snap EncodableSnapshot) GetLatestResult() *flow.ExecutionResult { } } } + for _, result := range snap.SealingSegment.ExecutionResults { + if latestSeal.ResultID == result.ID() { + return result + } + } // Correctly formatted sealing segments must contain latest result. panic("unreachable for correctly formatted sealing segments") } diff --git a/state/protocol/inmem/encodable_test.go b/state/protocol/inmem/encodable_test.go index 0186ddee44f..e624653e6f2 100644 --- a/state/protocol/inmem/encodable_test.go +++ b/state/protocol/inmem/encodable_test.go @@ -34,6 +34,6 @@ func TestEncodeDecode(t *testing.T) { require.NoError(t, err) // check that the computed and stored result IDs are consistent - decodedResult, decodedSeal := decodedSnapshot.GetLatestResult(), decodedSnapshot.GetLatestSeal() + decodedResult, decodedSeal := decodedSnapshot.LatestSealedResult(), decodedSnapshot.LatestSeal() assert.Equal(t, decodedResult.ID(), decodedSeal.ResultID) } diff --git a/state/protocol/inmem/snapshot.go b/state/protocol/inmem/snapshot.go index bf05b92bec0..ff36ffdfa82 100644 --- a/state/protocol/inmem/snapshot.go +++ b/state/protocol/inmem/snapshot.go @@ -50,11 +50,11 @@ func (s Snapshot) Identity(nodeID flow.Identifier) (*flow.Identity, error) { } func (s Snapshot) Commit() (flow.StateCommitment, error) { - return s.enc.GetLatestSeal().FinalState, nil + return s.enc.LatestSeal().FinalState, nil } func (s Snapshot) SealedResult() (*flow.ExecutionResult, *flow.Seal, error) { - return s.enc.GetLatestResult(), s.enc.GetLatestSeal(), nil + return s.enc.LatestSealedResult(), s.enc.LatestSeal(), nil } func (s Snapshot) SealingSegment() (*flow.SealingSegment, error) { From 7c11ad1aa89c33e8dc6f6fe54545cfc0ebfc9e7c Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Fri, 10 May 2024 12:29:28 -0400 Subject: [PATCH 06/13] account for non-root sealing segment terminating at root --- state/protocol/inmem/encodable.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index caa3bb82cb2..7d176e24634 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -25,16 +25,16 @@ func (snap EncodableSnapshot) Head() *flow.Header { // for the block with the greatest height, of all seals in the Snapshot. // The EncodableSnapshot receiver must be correctly formed. func (snap EncodableSnapshot) LatestSeal() *flow.Seal { - // CASE 1: In the case of a spork root, the single block of the sealing - // segment is sealed by protocol definition by `FirstSeal`. - if snap.SealingSegment.IsSporkRoot() { - return snap.SealingSegment.FirstSeal - } - head := snap.Head() latestSealID := snap.SealingSegment.LatestSeals[head.ID()] - // CASE 2: For a mid-spork root snapshot, there are multiple blocks in the sealing segment. + // CASE 1: The spork root block is the latest sealed block. + // By protocol definition, FirstSeal seals the spork root block. + if snap.SealingSegment.FirstSeal.ID() == latestSealID { + return snap.SealingSegment.FirstSeal + } + + // CASE 2: For any other snapshot, the latest seal must be in a block payload. // Since seals are included in increasing height order, the latest seal must be in the // first block (by height descending) which contains any seals. for i := len(snap.SealingSegment.Blocks) - 1; i >= 0; i-- { @@ -47,11 +47,11 @@ func (snap EncodableSnapshot) LatestSeal() *flow.Seal { if len(block.Payload.Seals) > 0 { // We encountered a block with some seals, but not the latest seal. // This can only occur in a structurally invalid SealingSegment. - panic("sanity check failed: no latest seal") + panic("LatestSeal: sanity check failed: no latest seal") } } // Correctly formatted sealing segments must contain latest seal. - panic("unreachable for correctly formatted sealing segments") + panic("LatestSeal: unreachable for correctly formatted sealing segments") } // LatestSealedResult returns the latest sealed result of the Snapshot. This is the result which is sealed by LatestSeal. @@ -74,7 +74,7 @@ func (snap EncodableSnapshot) LatestSealedResult() *flow.ExecutionResult { } } // Correctly formatted sealing segments must contain latest result. - panic("unreachable for correctly formatted sealing segments") + panic("LatestSealedResult: unreachable for correctly formatted sealing segments") } // EncodableDKG is the encoding format for protocol.DKG From 31ccc2379f0905d216dd3ada238842a9492391a6 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Fri, 10 May 2024 12:54:35 -0400 Subject: [PATCH 07/13] nil check --- state/protocol/inmem/encodable.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 7d176e24634..62a2894cb18 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -30,7 +30,7 @@ func (snap EncodableSnapshot) LatestSeal() *flow.Seal { // CASE 1: The spork root block is the latest sealed block. // By protocol definition, FirstSeal seals the spork root block. - if snap.SealingSegment.FirstSeal.ID() == latestSealID { + if snap.SealingSegment.FirstSeal != nil && snap.SealingSegment.FirstSeal.ID() == latestSealID { return snap.SealingSegment.FirstSeal } From 6b884e6ddc6b97a2f543fca3ceb81964053470f7 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Fri, 10 May 2024 16:04:39 -0400 Subject: [PATCH 08/13] update consensus epoch test --- consensus/integration/epoch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index c184e5a4d76..3d92830fcb3 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -271,7 +271,7 @@ func withNextEpoch( rootSeal.ResultID = rootResult.ID() rootSeal.BlockID = rootBlock.ID() encodableSnapshot.SealingSegment.LatestSeals = map[flow.Identifier]flow.Identifier{ - rootBlock.ID(): encodableSnapshot.LatestSeal().ID(), + rootBlock.ID(): rootSeal.ID(), } encodableSnapshot.QuorumCertificate = createQC(rootBlock) From cb9dadb86453c2db2fc35f38b770bf3d80e2c63d Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Wed, 15 May 2024 11:44:27 -0400 Subject: [PATCH 09/13] remove unused file --- utils/unittest/staker.go | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 utils/unittest/staker.go diff --git a/utils/unittest/staker.go b/utils/unittest/staker.go deleted file mode 100644 index 6305bc224a2..00000000000 --- a/utils/unittest/staker.go +++ /dev/null @@ -1,19 +0,0 @@ -package unittest - -import ( - "github.com/onflow/flow-go/model/flow" -) - -type FixedStaker struct { - Staked bool -} - -func NewFixedStaker(initial bool) *FixedStaker { - return &FixedStaker{ - Staked: initial, - } -} - -func (f *FixedStaker) AmIStakedAt(_ flow.Identifier) bool { - return f.Staked -} From 8202d0ce2523bd190764f58734f77fdff4047b2c Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Thu, 16 May 2024 12:45:01 -0400 Subject: [PATCH 10/13] reverse traverse blocks in LatestSealedResult --- state/protocol/inmem/encodable.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 62a2894cb18..0bbef0adb30 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -61,7 +61,8 @@ func (snap EncodableSnapshot) LatestSealedResult() *flow.ExecutionResult { // For both spork root and mid-spork snapshots, the latest sealing result must // either appear in a block payload or in the ExecutionResults field. - for _, block := range snap.SealingSegment.Blocks { + for i := len(snap.SealingSegment.Blocks) - 1; i >= 0; i-- { + block := snap.SealingSegment.Blocks[i] for _, result := range block.Payload.Results { if latestSeal.ResultID == result.ID() { return result From 452a08a6728d01cd4f12e4064b2068c4de691f86 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Mon, 20 May 2024 08:25:45 -0400 Subject: [PATCH 11/13] document and enforce withNextSnapshot expects spork root only --- consensus/integration/epoch_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index 3d92830fcb3..ae56e01befd 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -188,6 +188,7 @@ func TestEpochTransition_IdentitiesDisjoint(t *testing.T) { // withNextEpoch adds a valid next epoch with the given identities to the input // snapshot. Also sets the length of the first (current) epoch to curEpochViews. +// NOTE: the input initial snapshot must be a spork root snapshot. // // We make the first (current) epoch start in committed phase so we can transition // to the next epoch upon reaching the appropriate view without any further changes @@ -207,6 +208,7 @@ func withNextEpoch( encodableSnapshot := snapshot.Encodable() rootResult, rootSeal, err := snapshot.SealedResult() require.NoError(t, err) + require.Len(t, encodableSnapshot.SealingSegment.Blocks, 1, "function `withNextEpoch` only works for spork-root/genesis snapshots") rootProtocolState := encodableSnapshot.SealingSegment.LatestProtocolStateEntry() epochProtocolState := rootProtocolState.EpochEntry From 5488a5ec94b540cae66b911069fbd8151ad7ddc9 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Mon, 20 May 2024 08:30:31 -0400 Subject: [PATCH 12/13] Apply suggestions from code review Co-authored-by: Alexander Hentschel --- state/protocol/inmem/encodable.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 0bbef0adb30..4848265f57c 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -28,13 +28,13 @@ func (snap EncodableSnapshot) LatestSeal() *flow.Seal { head := snap.Head() latestSealID := snap.SealingSegment.LatestSeals[head.ID()] - // CASE 1: The spork root block is the latest sealed block. + // Genesis/Spork-Root Case: The spork root block is the latest sealed block. // By protocol definition, FirstSeal seals the spork root block. if snap.SealingSegment.FirstSeal != nil && snap.SealingSegment.FirstSeal.ID() == latestSealID { return snap.SealingSegment.FirstSeal } - // CASE 2: For any other snapshot, the latest seal must be in a block payload. + // Common Case: The highest seal within the payload of any block in the sealing segment. // Since seals are included in increasing height order, the latest seal must be in the // first block (by height descending) which contains any seals. for i := len(snap.SealingSegment.Blocks) - 1; i >= 0; i-- { From 167d3f68a0caa69cac516002a9b0b91c07fb64a6 Mon Sep 17 00:00:00 2001 From: Jordan Schalm Date: Mon, 20 May 2024 08:46:31 -0400 Subject: [PATCH 13/13] remove panics --- state/protocol/badger/state_test.go | 12 ++++++++--- state/protocol/inmem/encodable.go | 30 ++++++++++++++++---------- state/protocol/inmem/encodable_test.go | 5 ++++- state/protocol/inmem/snapshot.go | 16 ++++++++++++-- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/state/protocol/badger/state_test.go b/state/protocol/badger/state_test.go index 17a202f8446..832dcb5b99a 100644 --- a/state/protocol/badger/state_test.go +++ b/state/protocol/badger/state_test.go @@ -609,7 +609,9 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.LatestSeal().BlockID = unittest.IdentifierFixture() + latestSeal, err := encodable.LatestSeal() + require.NoError(t, err) + latestSeal.BlockID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) @@ -620,7 +622,9 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.LatestSealedResult().BlockID = unittest.IdentifierFixture() + latestSealedResult, err := encodable.LatestSealedResult() + require.NoError(t, err) + latestSealedResult.BlockID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) @@ -631,7 +635,9 @@ func TestBootstrap_SealMismatch(t *testing.T) { rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()) // convert to encodable to easily modify snapshot encodable := rootSnapshot.Encodable() - encodable.LatestSeal().ResultID = unittest.IdentifierFixture() + latestSeal, err := encodable.LatestSeal() + require.NoError(t, err) + latestSeal.ResultID = unittest.IdentifierFixture() bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) { assert.Error(t, err) diff --git a/state/protocol/inmem/encodable.go b/state/protocol/inmem/encodable.go index 0bbef0adb30..0e7d0c159ff 100644 --- a/state/protocol/inmem/encodable.go +++ b/state/protocol/inmem/encodable.go @@ -1,6 +1,8 @@ package inmem import ( + "fmt" + "github.com/onflow/flow-go/model/cluster" "github.com/onflow/flow-go/model/encodable" "github.com/onflow/flow-go/model/flow" @@ -24,14 +26,15 @@ func (snap EncodableSnapshot) Head() *flow.Header { // LatestSeal returns the latest seal of the Snapshot. This is the seal // for the block with the greatest height, of all seals in the Snapshot. // The EncodableSnapshot receiver must be correctly formed. -func (snap EncodableSnapshot) LatestSeal() *flow.Seal { +// No errors are expected during normal operation. +func (snap EncodableSnapshot) LatestSeal() (*flow.Seal, error) { head := snap.Head() latestSealID := snap.SealingSegment.LatestSeals[head.ID()] // CASE 1: The spork root block is the latest sealed block. // By protocol definition, FirstSeal seals the spork root block. if snap.SealingSegment.FirstSeal != nil && snap.SealingSegment.FirstSeal.ID() == latestSealID { - return snap.SealingSegment.FirstSeal + return snap.SealingSegment.FirstSeal, nil } // CASE 2: For any other snapshot, the latest seal must be in a block payload. @@ -41,23 +44,28 @@ func (snap EncodableSnapshot) LatestSeal() *flow.Seal { block := snap.SealingSegment.Blocks[i] for _, seal := range block.Payload.Seals { if seal.ID() == latestSealID { - return seal + return seal, nil } } if len(block.Payload.Seals) > 0 { // We encountered a block with some seals, but not the latest seal. // This can only occur in a structurally invalid SealingSegment. - panic("LatestSeal: sanity check failed: no latest seal") + return nil, fmt.Errorf("LatestSeal: sanity check failed: no latest seal") } } // Correctly formatted sealing segments must contain latest seal. - panic("LatestSeal: unreachable for correctly formatted sealing segments") + return nil, fmt.Errorf("LatestSeal: unreachable for correctly formatted sealing segments") } -// LatestSealedResult returns the latest sealed result of the Snapshot. This is the result which is sealed by LatestSeal. +// LatestSealedResult returns the latest sealed result of the Snapshot. +// This is the result which is sealed by LatestSeal. // The EncodableSnapshot receiver must be correctly formed. -func (snap EncodableSnapshot) LatestSealedResult() *flow.ExecutionResult { - latestSeal := snap.LatestSeal() +// No errors are expected during normal operation. +func (snap EncodableSnapshot) LatestSealedResult() (*flow.ExecutionResult, error) { + latestSeal, err := snap.LatestSeal() + if err != nil { + return nil, fmt.Errorf("LatestSealedResult: could not get latest seal: %w", err) + } // For both spork root and mid-spork snapshots, the latest sealing result must // either appear in a block payload or in the ExecutionResults field. @@ -65,17 +73,17 @@ func (snap EncodableSnapshot) LatestSealedResult() *flow.ExecutionResult { block := snap.SealingSegment.Blocks[i] for _, result := range block.Payload.Results { if latestSeal.ResultID == result.ID() { - return result + return result, nil } } } for _, result := range snap.SealingSegment.ExecutionResults { if latestSeal.ResultID == result.ID() { - return result + return result, nil } } // Correctly formatted sealing segments must contain latest result. - panic("LatestSealedResult: unreachable for correctly formatted sealing segments") + return nil, fmt.Errorf("LatestSealedResult: unreachable for correctly formatted sealing segments") } // EncodableDKG is the encoding format for protocol.DKG diff --git a/state/protocol/inmem/encodable_test.go b/state/protocol/inmem/encodable_test.go index e624653e6f2..7fe2e2f898d 100644 --- a/state/protocol/inmem/encodable_test.go +++ b/state/protocol/inmem/encodable_test.go @@ -34,6 +34,9 @@ func TestEncodeDecode(t *testing.T) { require.NoError(t, err) // check that the computed and stored result IDs are consistent - decodedResult, decodedSeal := decodedSnapshot.LatestSealedResult(), decodedSnapshot.LatestSeal() + decodedSeal, err := decodedSnapshot.LatestSeal() + require.NoError(t, err) + decodedResult, err := decodedSnapshot.LatestSealedResult() + require.NoError(t, err) assert.Equal(t, decodedResult.ID(), decodedSeal.ResultID) } diff --git a/state/protocol/inmem/snapshot.go b/state/protocol/inmem/snapshot.go index ff36ffdfa82..3559912b13c 100644 --- a/state/protocol/inmem/snapshot.go +++ b/state/protocol/inmem/snapshot.go @@ -50,11 +50,23 @@ func (s Snapshot) Identity(nodeID flow.Identifier) (*flow.Identity, error) { } func (s Snapshot) Commit() (flow.StateCommitment, error) { - return s.enc.LatestSeal().FinalState, nil + latestSeal, err := s.enc.LatestSeal() + if err != nil { + return flow.StateCommitment{}, nil + } + return latestSeal.FinalState, nil } func (s Snapshot) SealedResult() (*flow.ExecutionResult, *flow.Seal, error) { - return s.enc.LatestSealedResult(), s.enc.LatestSeal(), nil + latestSeal, err := s.enc.LatestSeal() + if err != nil { + return nil, nil, err + } + latestSealedResult, err := s.enc.LatestSealedResult() + if err != nil { + return nil, nil, err + } + return latestSealedResult, latestSeal, nil } func (s Snapshot) SealingSegment() (*flow.SealingSegment, error) {