-
Notifications
You must be signed in to change notification settings - Fork 179
/
finalizer.go
129 lines (112 loc) · 4.12 KB
/
finalizer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// (c) 2019 Dapper Labs - ALL RIGHTS RESERVED
package consensus
import (
"context"
"fmt"
"github.com/dgraph-io/badger/v2"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
"github.com/onflow/flow-go/module/trace"
"github.com/onflow/flow-go/state/protocol"
"github.com/onflow/flow-go/storage"
"github.com/onflow/flow-go/storage/badger/operation"
)
// Finalizer is a simple wrapper around our temporary state to clean up after a
// block has been fully finalized to the persistent protocol state.
type Finalizer struct {
db *badger.DB
headers storage.Headers
state protocol.FollowerState
cleanup CleanupFunc
tracer module.Tracer
}
// NewFinalizer creates a new finalizer for the temporary state.
func NewFinalizer(db *badger.DB,
headers storage.Headers,
state protocol.FollowerState,
tracer module.Tracer,
options ...func(*Finalizer)) *Finalizer {
f := &Finalizer{
db: db,
state: state,
headers: headers,
cleanup: CleanupNothing(),
tracer: tracer,
}
for _, option := range options {
option(f)
}
return f
}
// MakeFinal will finalize the block with the given ID and clean up the memory
// pools after it.
//
// This assumes that guarantees and seals are already in persistent state when
// included in a block proposal. Between entering the non-finalized chain state
// and being finalized, entities should be present in both the volatile memory
// pools and persistent storage.
// No errors are expected during normal operation.
func (f *Finalizer) MakeFinal(blockID flow.Identifier) error {
span, ctx := f.tracer.StartBlockSpan(context.Background(), blockID, trace.CONFinalizerFinalizeBlock)
defer span.End()
// STEP ONE: This is an idempotent operation. In case we are trying to
// finalize a block that is already below finalized height, we want to do
// one of two things: if it conflicts with the block already finalized at
// that height, it's an invalid operation. Otherwise, it is a no-op.
var finalized uint64
err := f.db.View(operation.RetrieveFinalizedHeight(&finalized))
if err != nil {
return fmt.Errorf("could not retrieve finalized height: %w", err)
}
pending, err := f.headers.ByBlockID(blockID)
if err != nil {
return fmt.Errorf("could not retrieve pending header: %w", err)
}
if pending.Height <= finalized {
dup, err := f.headers.ByHeight(pending.Height)
if err != nil {
return fmt.Errorf("could not retrieve finalized equivalent: %w", err)
}
if dup.ID() != blockID {
return fmt.Errorf("cannot finalize pending block conflicting with finalized state (height: %d, pending: %x, finalized: %x)", pending.Height, blockID, dup.ID())
}
return nil
}
// STEP TWO: At least one block in the chain back to the finalized state is
// a valid candidate for finalization. Figure out all blocks between the
// to-be-finalized block and the last finalized block. If we can't trace
// back to the last finalized block, this is also an invalid call.
var finalID flow.Identifier
err = f.db.View(operation.LookupBlockHeight(finalized, &finalID))
if err != nil {
return fmt.Errorf("could not retrieve finalized header: %w", err)
}
pendingIDs := []flow.Identifier{blockID}
ancestorID := pending.ParentID
for ancestorID != finalID {
ancestor, err := f.headers.ByBlockID(ancestorID)
if err != nil {
return fmt.Errorf("could not retrieve parent (%x): %w", ancestorID, err)
}
if ancestor.Height < finalized {
return fmt.Errorf("cannot finalize pending block unconnected to last finalized block (height: %d, finalized: %d)", ancestor.Height, finalized)
}
pendingIDs = append(pendingIDs, ancestorID)
ancestorID = ancestor.ParentID
}
// STEP THREE: We walk backwards through the collected ancestors, starting
// with the first block after finalizing state, and finalize them one by
// one in the protocol state.
for i := len(pendingIDs) - 1; i >= 0; i-- {
pendingID := pendingIDs[i]
err = f.state.Finalize(ctx, pendingID)
if err != nil {
return fmt.Errorf("could not finalize block (%x): %w", pendingID, err)
}
err := f.cleanup(pendingID)
if err != nil {
return fmt.Errorf("could not execute cleanup (%x): %w", pendingID, err)
}
}
return nil
}