-
Notifications
You must be signed in to change notification settings - Fork 175
/
state.go
150 lines (133 loc) · 4.17 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
package badger
import (
"errors"
"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/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
}
// 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())
genesis := stateRoot.Block()
// 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)
}
err = operation.InsertStartedView(chainID, genesis.Header.View)(tx)
if err != nil {
return fmt.Errorf("could not insert started view: %w", err)
}
// insert voted view for hotstuff
err = operation.InsertVotedView(chainID, genesis.Header.View)(tx)
if err != nil {
return fmt.Errorf("could not insert voted view: %w", err)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("bootstrapping failed: %w", err)
}
return state, nil
}
func OpenState(db *badger.DB, tracer module.Tracer, headers storage.Headers, payloads storage.ClusterPayloads, clusterID flow.ChainID) (*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)
return state, nil
}
func newState(db *badger.DB, clusterID flow.ChainID) *State {
state := &State{
db: db,
clusterID: clusterID,
}
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 or not 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
}