-
Notifications
You must be signed in to change notification settings - Fork 180
/
Copy pathprotocol_state.go
154 lines (145 loc) · 9.41 KB
/
protocol_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
package protocol
import (
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/storage/badger/transaction"
)
// InitialProtocolState returns constant data for given epoch.
// This interface can be only obtained for epochs that have progressed to epoch commit event.
type InitialProtocolState interface {
// Epoch returns counter of epoch.
Epoch() uint64
// Clustering returns initial clustering from epoch setup.
// No errors are expected during normal operations.
Clustering() (flow.ClusterList, error)
// EpochSetup returns original epoch setup event that was used to initialize the protocol state.
EpochSetup() *flow.EpochSetup
// EpochCommit returns original epoch commit event that was used to update the protocol state.
EpochCommit() *flow.EpochCommit
// DKG returns information about DKG that was obtained from EpochCommit event.
// No errors are expected during normal operations.
DKG() (DKG, error)
// Entry Returns low-level protocol state entry that was used to initialize this object.
// It shouldn't be used by high-level logic, it is useful for some cases such as bootstrapping.
// Prefer using other methods to access protocol state.
Entry() *flow.RichProtocolStateEntry
}
// DynamicProtocolState extends the InitialProtocolState with data that can change from block to block.
// It can be used to access the identity table at given block.
type DynamicProtocolState interface {
InitialProtocolState
// EpochStatus returns the status of current epoch at given block based on the internal state of protocol.
EpochStatus() *flow.EpochStatus
// Identities returns identities that can participate in current and next epochs.
// Set of Authorized identities are different depending on epoch state:
// staking phase - identities for current epoch + identities from previous epoch (with 0 weight)
// setup & commit phase - identities for current epoch + identities from next epoch (with 0 weight)
Identities() flow.IdentityList
// GlobalParams returns params that are same for all nodes in the network.
GlobalParams() GlobalParams
}
// ProtocolState is the read-only interface for protocol state, it allows to query information
// on a per-block and per-epoch basis.
type ProtocolState interface {
// ByEpoch returns an object with static protocol state information by epoch number.
// To be able to use this interface we need to observe both epoch setup and commit events.
// Not available for next epoch unless we have observed an EpochCommit event.
// No errors are expected during normal operations.
// TODO(yuraolex): check return types
// TODO(yuraolex): decide if we really need this approach. It's unclear if it's useful to query
// by epoch counter. To implement it we need an additional index by epoch counter. Alternatively we need a way to map
// epoch counter -> block ID. It gets worse if we consider that we need a way to get the epoch counter itself at caller side.
//ByEpoch(epoch uint64) (InitialProtocolState, error)
// AtBlockID returns protocol state at block ID.
// The resulting protocol state is returned AFTER applying updates that are contained in block.
// Can be queried for any block that has been added to the block tree.
// Returns:
// - (DynamicProtocolState, nil) - if there is a protocol state associated with given block ID.
// - (nil, storage.ErrNotFound) - if there is no protocol state associated with given block ID.
// - (nil, exception) - any other error should be treated as exception.
AtBlockID(blockID flow.Identifier) (DynamicProtocolState, error)
// GlobalParams returns params that are the same for all nodes in the network.
GlobalParams() GlobalParams
}
type MutableProtocolState interface {
ProtocolState
// Mutator instantiates a `StateMutator` based on the previous protocol state.
// Has to be called for each block to evolve the protocol state.
// Expected errors during normal operations:
// * `storage.ErrNotFound` if no protocol state for parent block is known.
Mutator(candidateView uint64, parentID flow.Identifier) (StateMutator, error)
}
// StateMutator is a stateful object to evolve the protocol state. It is instantiated from the parent block's protocol state.
// State-changing operations can be iteratively applied and the StateMutator will internally evolve its in-memory state.
// While the StateMutator does not modify the database, it internally tracks all necessary updates. Upon calling `Build`,
// the StateMutator returns the updated protocol state, its ID and all database updates necessary for persisting the updated
// protocol state.
// The StateMutator is used by a replica's compliance layer to update protocol state when observing state-changing service in
// blocks. It is used by the primary in the block building process to obtain the correct protocol state for a proposal.
// Specifically, the leader may include state-changing service events in the block payload. The flow protocol prescribes that
// the proposal needs to include the ID of the protocol state, _after_ processing the payload incl. all state-changing events.
// Therefore, the leader instantiates a StateMutator, applies the service events to it and builds the updated protocol state ID.
type StateMutator interface {
// Build returns:
// - hasChanges: flag whether there were any changes; otherwise, `updatedState` and `stateID` equal the parent state
// - updatedState: the ProtocolState after applying all updates
// - stateID: the hash commitment to the `updatedState`
// - dbUpdates: database updates necessary for persisting the updated protocol state
// updated protocol state entry, state ID and a flag indicating if there were any changes.
Build() (hasChanges bool, updatedState *flow.ProtocolStateEntry, stateID flow.Identifier, dbUpdates []func(tx *transaction.Tx) error)
// ApplyServiceEventsFromValidatedSeals applies the state changes that are delivered via
// sealed service events:
// - iterating over the sealed service events in order of increasing height
// - identifying state-changing service event and calling into the embedded
// ProtocolStateMachine to apply the respective state update
// - tracking deferred database updates necessary to persist the updated state
// and its data dependencies
//
// All updates only mutate the `StateMutator`'s internal in-memory copy of the
// protocol state, without changing the parent state (i.e. the state we started from).
//
// SAFETY REQUIREMENT:
// The StateMutator assumes that the proposal has passed the following correctness checks!
// - The seals in the payload continuously follow the ancestry of this fork. Specifically,
// there are no gaps in the seals.
// - The seals guarantee correctness of the sealed execution result, including the contained
// service events. This is actively checked by the verification node, whose aggregated
// approvals in the form of a seal attest to the correctness of the sealed execution result,
// including the contained.
//
// Consensus nodes actively verify protocol compliance for any block proposal they receive,
// including integrity of each seal individually as well as the seals continuously following the
// fork. Light clients only process certified blocks, which guarantees that consensus nodes already
// ran those checks and found the proposal to be valid.
//
// Details on SERVICE EVENTS:
// Consider a chain where a service event is emitted during execution of block A.
// Block B contains an execution receipt for A. Block C contains a seal for block
// A's execution result.
//
// A <- .. <- B(RA) <- .. <- C(SA)
//
// Service Events are included within execution results, which are stored
// opaquely as part of the block payload in block B. We only validate, process and persist
// the typed service event to storage once we process C, the block containing the
// seal for block A. This is because we rely on the sealing subsystem to validate
// correctness of the service event before processing it.
// Consequently, any change to the protocol state introduced by a service event
// emitted during execution of block A would only become visible when querying
// C or its descendants.
//
// Error returns:
// - Per convention, the input seals from the block payload have already confirmed to be protocol compliant.
// Hence, the service events in the sealed execution results represent the honest execution path.
// Therefore, the sealed service events should encode a valid evolution of the protocol state -- provided
// the system smart contracts are correct.
// - As we can rule out byzantine attacks as the source of failures, the only remaining sources of problems
// can be (a) bugs in the system smart contracts or (b) bugs in the node implementation.
// A service event not representing a valid state transition despite all consistency checks passing
// is interpreted as case (a) and handled internally within the StateMutator. In short, we go into Epoch
// Fallback Mode by copying the parent state (a valid state snapshot) and setting the
// `InvalidStateTransitionAttempted` flag. All subsequent Epoch-lifecycle events are ignored.
// - A consistency or sanity check failing within the StateMutator is likely the symptom of an internal bug
// in the node software or state corruption, i.e. case (b). This is the only scenario where the error return
// of this function is not nil. If such an exception is returned, continuing is not an option.
ApplyServiceEventsFromValidatedSeals(seals []*flow.Seal) error
}