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  }