github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/validity.go (about) 1 package protocol 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/flow-go/model/flow" 7 "github.com/onflow/flow-go/model/flow/factory" 8 "github.com/onflow/flow-go/model/flow/filter" 9 ) 10 11 // IsValidExtendingEpochSetup checks whether an EpochSetup service event being added to the state is valid. 12 // In addition to intrinsic validity, we also check that it is valid w.r.t. the previous epoch setup event, 13 // and the current epoch status. 14 // CAUTION: This function assumes that all inputs besides extendingCommit are already validated. 15 // Expected errors during normal operations: 16 // * protocol.InvalidServiceEventError if the input service event is invalid to extend the currently active epoch status 17 func IsValidExtendingEpochSetup(extendingSetup *flow.EpochSetup, protocolStateEntry *flow.ProtocolStateEntry, currentEpochSetupEvent *flow.EpochSetup) error { 18 // Enforce EpochSetup is valid w.r.t to current epoch state 19 if protocolStateEntry.NextEpoch != nil { // We should only have a single epoch setup event per epoch. 20 // true iff EpochSetup event for NEXT epoch was already included before 21 return NewInvalidServiceEventErrorf("duplicate epoch setup service event: %x", protocolStateEntry.NextEpoch.SetupID) 22 } 23 if extendingSetup.Counter != currentEpochSetupEvent.Counter+1 { // The setup event should have the counter increased by one. 24 return NewInvalidServiceEventErrorf("next epoch setup has invalid counter (%d => %d)", currentEpochSetupEvent.Counter, extendingSetup.Counter) 25 } 26 if extendingSetup.FirstView != currentEpochSetupEvent.FinalView+1 { // The first view needs to be exactly one greater than the current epoch final view 27 return NewInvalidServiceEventErrorf( 28 "next epoch first view must be exactly 1 more than current epoch final view (%d != %d+1)", 29 extendingSetup.FirstView, 30 currentEpochSetupEvent.FinalView, 31 ) 32 } 33 34 // Enforce the EpochSetup event is syntactically correct 35 err := IsValidEpochSetup(extendingSetup, true) 36 if err != nil { 37 return NewInvalidServiceEventErrorf("invalid epoch setup: %w", err) 38 } 39 return nil 40 } 41 42 // IsValidEpochSetup checks whether an `EpochSetup` event is syntactically correct. The boolean parameter `verifyNetworkAddress` 43 // controls, whether we want to permit nodes to share a networking address. 44 // This is a side-effect-free function. Any error return indicates that the EpochSetup event is not compliant with protocol rules. 45 func IsValidEpochSetup(setup *flow.EpochSetup, verifyNetworkAddress bool) error { 46 // 1. CHECK: Enforce protocol compliance of Epoch parameters: 47 // - RandomSource of entropy in Epoch Setup event should the protocol-prescribed length 48 // - first view must be before final view 49 if len(setup.RandomSource) != flow.EpochSetupRandomSourceLength { 50 return fmt.Errorf("seed has incorrect length (%d != %d)", len(setup.RandomSource), flow.EpochSetupRandomSourceLength) 51 } 52 if setup.FirstView >= setup.FinalView { 53 return fmt.Errorf("first view (%d) must be before final view (%d)", setup.FirstView, setup.FinalView) 54 } 55 56 // 2. CHECK: Enforce protocol compliance active participants: 57 // (a) each has a unique node ID, 58 // (b) each has a unique network address (if `verifyNetworkAddress` is true), 59 // (c) participants are sorted in canonical order. 60 // Note that the system smart contracts manage the identity table as an unordered set! For the protocol state, we desire a fixed 61 // ordering to simplify various implementation details, like the DKG. Therefore, we order identities in `flow.EpochSetup` during 62 // conversion from cadence to Go in the function `convert.ServiceEvent(flow.ChainID, flow.Event)` in package `model/convert` 63 identLookup := make(map[flow.Identifier]struct{}) 64 for _, participant := range setup.Participants { // (a) enforce uniqueness of NodeIDs 65 _, ok := identLookup[participant.NodeID] 66 if ok { 67 return fmt.Errorf("duplicate node identifier (%x)", participant.NodeID) 68 } 69 identLookup[participant.NodeID] = struct{}{} 70 } 71 72 if verifyNetworkAddress { // (b) enforce uniqueness of networking address 73 addrLookup := make(map[string]struct{}) 74 for _, participant := range setup.Participants { 75 _, ok := addrLookup[participant.Address] 76 if ok { 77 return fmt.Errorf("duplicate node address (%x)", participant.Address) 78 } 79 addrLookup[participant.Address] = struct{}{} 80 } 81 } 82 83 if !setup.Participants.Sorted(flow.Canonical[flow.IdentitySkeleton]) { // (c) enforce canonical ordering 84 return fmt.Errorf("participants are not canonically ordered") 85 } 86 87 // 3. CHECK: Enforce sufficient number of nodes for each role 88 // IMPORTANT: here we remove all nodes with zero weight, as they are allowed to partake in communication but not in respective node functions 89 activeParticipants := setup.Participants.Filter(filter.HasInitialWeight[flow.IdentitySkeleton](true)) 90 activeNodeCountByRole := make(map[flow.Role]uint) 91 for _, participant := range activeParticipants { 92 activeNodeCountByRole[participant.Role]++ 93 } 94 if activeNodeCountByRole[flow.RoleConsensus] < 1 { 95 return fmt.Errorf("need at least one consensus node") 96 } 97 if activeNodeCountByRole[flow.RoleCollection] < 1 { 98 return fmt.Errorf("need at least one collection node") 99 } 100 if activeNodeCountByRole[flow.RoleExecution] < 1 { 101 return fmt.Errorf("need at least one execution node") 102 } 103 if activeNodeCountByRole[flow.RoleVerification] < 1 { 104 return fmt.Errorf("need at least one verification node") 105 } 106 107 // 4. CHECK: Enforce protocol compliance of collector cluster assignment 108 // (0) there is at least one collector cluster 109 // (a) assignment only contains nodes with collector role and positive weight 110 // (b) collectors have unique node IDs 111 // (c) each collector is assigned exactly to one cluster and is only listed once within that cluster 112 // (d) cluster contains at least one collector (i.e. is not empty) 113 // (e) cluster is composed of known nodes 114 // (f) cluster assignment lists the nodes in canonical ordering 115 if len(setup.Assignments) == 0 { // enforce (0): at least one cluster 116 return fmt.Errorf("need at least one collection cluster") 117 } 118 // Unpacking the cluster assignments (NodeIDs → IdentitySkeletons) enforces (a) - (f) 119 _, err := factory.NewClusterList(setup.Assignments, activeParticipants.Filter(filter.HasRole[flow.IdentitySkeleton](flow.RoleCollection))) 120 if err != nil { 121 return fmt.Errorf("invalid cluster assignments: %w", err) 122 } 123 return nil 124 } 125 126 // IsValidExtendingEpochCommit checks whether an EpochCommit service event being added to the state is valid. 127 // In addition to intrinsic validity, we also check that it is valid w.r.t. the previous epoch setup event, and 128 // the current epoch status. 129 // CAUTION: This function assumes that all inputs besides extendingCommit are already validated. 130 // Expected errors during normal operations: 131 // * protocol.InvalidServiceEventError if the input service event is invalid to extend the currently active epoch 132 func IsValidExtendingEpochCommit(extendingCommit *flow.EpochCommit, protocolStateEntry *flow.ProtocolStateEntry, nextEpochSetupEvent *flow.EpochSetup) error { 133 // The epoch setup event needs to happen before the commit. 134 if protocolStateEntry.NextEpoch == nil { 135 return NewInvalidServiceEventErrorf("missing epoch setup for epoch commit") 136 } 137 // Enforce EpochSetup is valid w.r.t to current epoch state 138 if protocolStateEntry.NextEpoch.CommitID != flow.ZeroID { // We should only have a single epoch commit event per epoch. 139 return NewInvalidServiceEventErrorf("duplicate epoch commit service event: %x", protocolStateEntry.NextEpoch.CommitID) 140 } 141 // Enforce the EpochSetup event is syntactically correct and compatible with the respective EpochSetup 142 err := IsValidEpochCommit(extendingCommit, nextEpochSetupEvent) 143 if err != nil { 144 return NewInvalidServiceEventErrorf("invalid epoch commit: %s", err) 145 } 146 return nil 147 } 148 149 // IsValidEpochCommit checks whether an epoch commit service event is intrinsically valid. 150 // Assumes the input flow.EpochSetup event has already been validated. 151 // Expected errors during normal operations: 152 // * protocol.InvalidServiceEventError if the EpochCommit is invalid 153 func IsValidEpochCommit(commit *flow.EpochCommit, setup *flow.EpochSetup) error { 154 if len(setup.Assignments) != len(commit.ClusterQCs) { 155 return NewInvalidServiceEventErrorf("number of clusters (%d) does not number of QCs (%d)", len(setup.Assignments), len(commit.ClusterQCs)) 156 } 157 158 if commit.Counter != setup.Counter { 159 return NewInvalidServiceEventErrorf("inconsistent epoch counter between commit (%d) and setup (%d) events in same epoch", commit.Counter, setup.Counter) 160 } 161 162 // make sure we have a valid DKG public key 163 if commit.DKGGroupKey == nil { 164 return NewInvalidServiceEventErrorf("missing DKG public group key") 165 } 166 167 participants := setup.Participants.Filter(filter.IsValidDKGParticipant) 168 if len(participants) != len(commit.DKGParticipantKeys) { 169 return NewInvalidServiceEventErrorf("participant list (len=%d) does not match dkg key list (len=%d)", len(participants), len(commit.DKGParticipantKeys)) 170 } 171 return nil 172 }