github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/inmem/convert.go (about) 1 package inmem 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/flow-go/model/encodable" 7 "github.com/onflow/flow-go/model/flow" 8 "github.com/onflow/flow-go/model/flow/filter" 9 "github.com/onflow/flow-go/module/signature" 10 "github.com/onflow/flow-go/state/protocol" 11 "github.com/onflow/flow-go/state/protocol/protocol_state" 12 "github.com/onflow/flow-go/state/protocol/protocol_state/kvstore" 13 ) 14 15 // FromSnapshot generates a memory-backed snapshot from the input snapshot. 16 // Typically, this would be used to convert a database-backed snapshot to 17 // one that can easily be serialized to disk or to network. 18 // TODO error docs 19 func FromSnapshot(from protocol.Snapshot) (*Snapshot, error) { 20 var ( 21 snap EncodableSnapshot 22 err error 23 ) 24 25 // convert top-level fields 26 snap.Head, err = from.Head() 27 if err != nil { 28 return nil, fmt.Errorf("could not get head: %w", err) 29 } 30 snap.LatestResult, snap.LatestSeal, err = from.SealedResult() 31 if err != nil { 32 return nil, fmt.Errorf("could not get seal: %w", err) 33 } 34 35 snap.SealingSegment, err = from.SealingSegment() 36 if err != nil { 37 return nil, fmt.Errorf("could not get sealing segment: %w", err) 38 } 39 snap.QuorumCertificate, err = from.QuorumCertificate() 40 if err != nil { 41 return nil, fmt.Errorf("could not get qc: %w", err) 42 } 43 44 // convert global state parameters 45 params, err := FromParams(from.Params()) 46 if err != nil { 47 return nil, fmt.Errorf("could not get params: %w", err) 48 } 49 snap.Params = params.enc 50 51 // convert version beacon 52 versionBeacon, err := from.VersionBeacon() 53 if err != nil { 54 return nil, fmt.Errorf("could not get version beacon: %w", err) 55 } 56 57 snap.SealedVersionBeacon = versionBeacon 58 59 return &Snapshot{snap}, nil 60 } 61 62 // FromParams converts any protocol.GlobalParams to a memory-backed Params. 63 // TODO error docs 64 func FromParams(from protocol.GlobalParams) (*Params, error) { 65 params := EncodableParams{ 66 ChainID: from.ChainID(), 67 SporkID: from.SporkID(), 68 SporkRootBlockHeight: from.SporkRootBlockHeight(), 69 ProtocolVersion: from.ProtocolVersion(), 70 EpochCommitSafetyThreshold: from.EpochCommitSafetyThreshold(), 71 } 72 return &Params{params}, nil 73 } 74 75 // FromCluster converts any protocol.Cluster to a memory-backed Cluster. 76 // No errors are expected during normal operation. 77 func FromCluster(from protocol.Cluster) (*Cluster, error) { 78 cluster := EncodableCluster{ 79 Counter: from.EpochCounter(), 80 Index: from.Index(), 81 Members: from.Members(), 82 RootBlock: from.RootBlock(), 83 RootQC: from.RootQC(), 84 } 85 return &Cluster{cluster}, nil 86 } 87 88 // FromDKG converts any protocol.DKG to a memory-backed DKG. 89 // 90 // The given participant list must exactly match the DKG members. 91 // All errors indicate inconsistent or invalid inputs. 92 // No errors are expected during normal operation. 93 func FromDKG(from protocol.DKG, participants flow.IdentitySkeletonList) (*DKG, error) { 94 var dkg EncodableDKG 95 dkg.GroupKey = encodable.RandomBeaconPubKey{PublicKey: from.GroupKey()} 96 97 lookup, err := protocol.ToDKGParticipantLookup(from, participants) 98 if err != nil { 99 return nil, fmt.Errorf("could not generate dkg participant lookup: %w", err) 100 } 101 dkg.Participants = lookup 102 103 return &DKG{dkg}, nil 104 } 105 106 // DKGFromEncodable returns a DKG backed by the given encodable representation. 107 func DKGFromEncodable(enc EncodableDKG) (*DKG, error) { 108 return &DKG{enc}, nil 109 } 110 111 // EncodableDKGFromEvents returns an EncodableDKG constructed from epoch setup and commit events. 112 // No errors are expected during normal operations. 113 func EncodableDKGFromEvents(setup *flow.EpochSetup, commit *flow.EpochCommit) (EncodableDKG, error) { 114 // filter initial participants to valid DKG participants 115 participants := setup.Participants.Filter(filter.IsValidDKGParticipant) 116 lookup, err := flow.ToDKGParticipantLookup(participants, commit.DKGParticipantKeys) 117 if err != nil { 118 return EncodableDKG{}, fmt.Errorf("could not construct dkg lookup: %w", err) 119 } 120 121 return EncodableDKG{ 122 GroupKey: encodable.RandomBeaconPubKey{ 123 PublicKey: commit.DKGGroupKey, 124 }, 125 Participants: lookup, 126 }, nil 127 } 128 129 // ClusterFromEncodable returns a Cluster backed by the given encodable representation. 130 func ClusterFromEncodable(enc EncodableCluster) (*Cluster, error) { 131 return &Cluster{enc}, nil 132 } 133 134 // SnapshotFromBootstrapState generates a protocol.Snapshot representing a 135 // root bootstrap state. This is used to bootstrap the protocol state for 136 // genesis or post-spork states. 137 func SnapshotFromBootstrapState(root *flow.Block, result *flow.ExecutionResult, seal *flow.Seal, qc *flow.QuorumCertificate) (*Snapshot, error) { 138 version := flow.DefaultProtocolVersion 139 threshold, err := protocol.DefaultEpochCommitSafetyThreshold(root.Header.ChainID) 140 if err != nil { 141 return nil, fmt.Errorf("could not get default epoch commit safety threshold: %w", err) 142 } 143 return SnapshotFromBootstrapStateWithParams(root, result, seal, qc, version, threshold, kvstore.NewDefaultKVStore) 144 } 145 146 // SnapshotFromBootstrapStateWithParams is SnapshotFromBootstrapState 147 // with a caller-specified protocol version. 148 func SnapshotFromBootstrapStateWithParams( 149 root *flow.Block, 150 result *flow.ExecutionResult, 151 seal *flow.Seal, 152 qc *flow.QuorumCertificate, 153 protocolVersion uint, 154 epochCommitSafetyThreshold uint64, 155 kvStoreFactory func(epochStateID flow.Identifier) protocol_state.KVStoreAPI, 156 ) (*Snapshot, error) { 157 setup, ok := result.ServiceEvents[0].Event.(*flow.EpochSetup) 158 if !ok { 159 return nil, fmt.Errorf("invalid setup event type (%T)", result.ServiceEvents[0].Event) 160 } 161 commit, ok := result.ServiceEvents[1].Event.(*flow.EpochCommit) 162 if !ok { 163 return nil, fmt.Errorf("invalid commit event type (%T)", result.ServiceEvents[1].Event) 164 } 165 166 clustering, err := ClusteringFromSetupEvent(setup) 167 if err != nil { 168 return nil, fmt.Errorf("setup event has invalid clustering: %w", err) 169 } 170 171 // sanity check the commit event has the same number of cluster QC as the number clusters 172 if len(clustering) != len(commit.ClusterQCs) { 173 return nil, fmt.Errorf("mismatching number of ClusterQCs, expect %v but got %v", 174 len(clustering), len(commit.ClusterQCs)) 175 } 176 177 // sanity check the QC in the commit event, which should be found in the identities in 178 // the setup event 179 for i, cluster := range clustering { 180 rootQCVoteData := commit.ClusterQCs[i] 181 _, err = signature.EncodeSignersToIndices(cluster.NodeIDs(), rootQCVoteData.VoterIDs) 182 if err != nil { 183 return nil, fmt.Errorf("mismatching cluster and qc: %w", err) 184 } 185 } 186 187 params := EncodableParams{ 188 ChainID: root.Header.ChainID, // chain ID must match the root block 189 SporkID: root.ID(), // use root block ID as the unique spork identifier 190 SporkRootBlockHeight: root.Header.Height, // use root block height as the spork root block height 191 ProtocolVersion: protocolVersion, // major software version for this spork 192 EpochCommitSafetyThreshold: epochCommitSafetyThreshold, // see protocol.Params for details 193 } 194 195 rootEpochState := ProtocolStateFromEpochServiceEvents(setup, commit) 196 rootEpochStateID := rootEpochState.ID() 197 rootKvStore := kvStoreFactory(rootEpochStateID) 198 if rootKvStore.ID() != root.Payload.ProtocolStateID { 199 return nil, fmt.Errorf("incorrect protocol state ID in root block, expected (%x) but got (%x)", 200 root.Payload.ProtocolStateID, rootKvStore.ID()) 201 } 202 kvStoreVersion, kvStoreData, err := rootKvStore.VersionedEncode() 203 if err != nil { 204 return nil, fmt.Errorf("could not encode kvstore: %w", err) 205 } 206 207 richRootEpochState, err := flow.NewRichProtocolStateEntry(rootEpochState, nil, nil, setup, commit, nil, nil) 208 if err != nil { 209 return nil, fmt.Errorf("could not construct root rich epoch state entry: %w", err) 210 } 211 212 rootProtocolStateEntryWrapper := &flow.ProtocolStateEntryWrapper{ 213 KVStore: flow.PSKeyValueStoreData{ 214 Version: kvStoreVersion, 215 Data: kvStoreData, 216 }, 217 EpochEntry: richRootEpochState, 218 } 219 220 snap := SnapshotFromEncodable(EncodableSnapshot{ 221 Head: root.Header, 222 LatestSeal: seal, 223 LatestResult: result, 224 SealingSegment: &flow.SealingSegment{ 225 Blocks: []*flow.Block{root}, 226 ExecutionResults: flow.ExecutionResultList{result}, 227 LatestSeals: map[flow.Identifier]flow.Identifier{root.ID(): seal.ID()}, 228 ProtocolStateEntries: map[flow.Identifier]*flow.ProtocolStateEntryWrapper{ 229 rootKvStore.ID(): rootProtocolStateEntryWrapper, 230 }, 231 FirstSeal: seal, 232 ExtraBlocks: make([]*flow.Block, 0), 233 }, 234 QuorumCertificate: qc, 235 Params: params, 236 SealedVersionBeacon: nil, 237 }) 238 239 return snap, nil 240 } 241 242 // ProtocolStateFromEpochServiceEvents generates a protocol.ProtocolStateEntry for a root protocol state which is used for bootstrapping. 243 // 244 // CONTEXT: The EpochSetup event contains the IdentitySkeletons for each participant, thereby specifying active epoch members. 245 // While ejection status is not part of the EpochSetup event, we can supplement this information as follows: 246 // - Per convention, service events are delivered (asynchronously) in an *order-preserving* manner. Furthermore, 247 // node ejection is also mediated by system smart contracts and delivered via service events. 248 // - Therefore, the EpochSetup event contains the up-to-date snapshot of the epoch participants. Any node ejection 249 // that happened before should be reflected in the EpochSetup event. Specifically, ejected 250 // nodes should be no longer listed in the EpochSetup event. 251 // Hence, when the EpochSetup event is emitted / processed, the ejected flag is false for all epoch participants. 252 func ProtocolStateFromEpochServiceEvents(setup *flow.EpochSetup, commit *flow.EpochCommit) *flow.ProtocolStateEntry { 253 identities := make(flow.DynamicIdentityEntryList, 0, len(setup.Participants)) 254 for _, identity := range setup.Participants { 255 identities = append(identities, &flow.DynamicIdentityEntry{ 256 NodeID: identity.NodeID, 257 Ejected: false, 258 }) 259 } 260 return &flow.ProtocolStateEntry{ 261 PreviousEpoch: nil, 262 CurrentEpoch: flow.EpochStateContainer{ 263 SetupID: setup.ID(), 264 CommitID: commit.ID(), 265 ActiveIdentities: identities, 266 }, 267 NextEpoch: nil, 268 InvalidEpochTransitionAttempted: false, 269 } 270 }