-
Notifications
You must be signed in to change notification settings - Fork 178
/
state.go
165 lines (146 loc) · 4.63 KB
/
state.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package badger
import (
"errors"
"fmt"
"github.com/dgraph-io/badger/v2"
"github.com/onflow/flow-go/consensus/hotstuff"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
"github.com/onflow/flow-go/state/cluster"
"github.com/onflow/flow-go/storage"
"github.com/onflow/flow-go/storage/badger/operation"
"github.com/onflow/flow-go/storage/badger/procedure"
)
type State struct {
db *badger.DB
clusterID flow.ChainID // the chain ID for the cluster
epoch uint64 // the operating epoch for the cluster
}
// Bootstrap initializes the persistent cluster state with a genesis block.
// The genesis block must have height 0, a parent hash of 32 zero bytes,
// and an empty collection as payload.
func Bootstrap(db *badger.DB, stateRoot *StateRoot) (*State, error) {
isBootstrapped, err := IsBootstrapped(db, stateRoot.ClusterID())
if err != nil {
return nil, fmt.Errorf("failed to determine whether database contains bootstrapped state: %w", err)
}
if isBootstrapped {
return nil, fmt.Errorf("expected empty cluster state for cluster ID %s", stateRoot.ClusterID())
}
state := newState(db, stateRoot.ClusterID(), stateRoot.EpochCounter())
genesis := stateRoot.Block()
rootQC := stateRoot.QC()
// bootstrap cluster state
err = operation.RetryOnConflict(state.db.Update, func(tx *badger.Txn) error {
chainID := genesis.Header.ChainID
// insert the block
err := procedure.InsertClusterBlock(genesis)(tx)
if err != nil {
return fmt.Errorf("could not insert genesis block: %w", err)
}
// insert block height -> ID mapping
err = operation.IndexClusterBlockHeight(chainID, genesis.Header.Height, genesis.ID())(tx)
if err != nil {
return fmt.Errorf("failed to map genesis block height to block: %w", err)
}
// insert boundary
err = operation.InsertClusterFinalizedHeight(chainID, genesis.Header.Height)(tx)
// insert started view for hotstuff
if err != nil {
return fmt.Errorf("could not insert genesis boundary: %w", err)
}
safetyData := &hotstuff.SafetyData{
LockedOneChainView: genesis.Header.View,
HighestAcknowledgedView: genesis.Header.View,
}
livenessData := &hotstuff.LivenessData{
CurrentView: genesis.Header.View + 1,
NewestQC: rootQC,
}
// insert safety data
err = operation.InsertSafetyData(chainID, safetyData)(tx)
if err != nil {
return fmt.Errorf("could not insert safety data: %w", err)
}
// insert liveness data
err = operation.InsertLivenessData(chainID, livenessData)(tx)
if err != nil {
return fmt.Errorf("could not insert liveness data: %w", err)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("bootstrapping failed: %w", err)
}
return state, nil
}
func OpenState(db *badger.DB, _ module.Tracer, _ storage.Headers, _ storage.ClusterPayloads, clusterID flow.ChainID, epoch uint64) (*State, error) {
isBootstrapped, err := IsBootstrapped(db, clusterID)
if err != nil {
return nil, fmt.Errorf("failed to determine whether database contains bootstrapped state: %w", err)
}
if !isBootstrapped {
return nil, fmt.Errorf("expected database to contain bootstrapped state")
}
state := newState(db, clusterID, epoch)
return state, nil
}
func newState(db *badger.DB, clusterID flow.ChainID, epoch uint64) *State {
state := &State{
db: db,
clusterID: clusterID,
epoch: epoch,
}
return state
}
func (s *State) Params() cluster.Params {
params := &Params{
state: s,
}
return params
}
func (s *State) Final() cluster.Snapshot {
// get the finalized block ID
var blockID flow.Identifier
err := s.db.View(func(tx *badger.Txn) error {
var boundary uint64
err := operation.RetrieveClusterFinalizedHeight(s.clusterID, &boundary)(tx)
if err != nil {
return fmt.Errorf("could not retrieve finalized boundary: %w", err)
}
err = operation.LookupClusterBlockHeight(s.clusterID, boundary, &blockID)(tx)
if err != nil {
return fmt.Errorf("could not retrieve finalized ID: %w", err)
}
return nil
})
if err != nil {
return &Snapshot{
err: err,
}
}
snapshot := &Snapshot{
state: s,
blockID: blockID,
}
return snapshot
}
func (s *State) AtBlockID(blockID flow.Identifier) cluster.Snapshot {
snapshot := &Snapshot{
state: s,
blockID: blockID,
}
return snapshot
}
// IsBootstrapped returns whether the database contains a bootstrapped state.
func IsBootstrapped(db *badger.DB, clusterID flow.ChainID) (bool, error) {
var finalized uint64
err := db.View(operation.RetrieveClusterFinalizedHeight(clusterID, &finalized))
if errors.Is(err, storage.ErrNotFound) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("retrieving finalized height failed: %w", err)
}
return true, nil
}