github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/badger/validity.go (about) 1 package badger 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/flow-go/consensus/hotstuff/committees" 7 "github.com/onflow/flow-go/consensus/hotstuff/signature" 8 "github.com/onflow/flow-go/consensus/hotstuff/validator" 9 "github.com/onflow/flow-go/consensus/hotstuff/verification" 10 "github.com/onflow/flow-go/model/flow" 11 "github.com/onflow/flow-go/model/flow/filter" 12 "github.com/onflow/flow-go/state/protocol" 13 ) 14 15 // IsValidRootSnapshot checks internal consistency of root state snapshot 16 // if verifyResultID allows/disallows Result ID verification 17 func IsValidRootSnapshot(snap protocol.Snapshot, verifyResultID bool) error { 18 19 segment, err := snap.SealingSegment() 20 if err != nil { 21 return fmt.Errorf("could not get sealing segment: %w", err) 22 } 23 result, seal, err := snap.SealedResult() 24 if err != nil { 25 return fmt.Errorf("could not latest sealed result: %w", err) 26 } 27 28 err = segment.Validate() 29 if err != nil { 30 return fmt.Errorf("invalid root sealing segment: %w", err) 31 } 32 33 highest := segment.Highest() // reference block of the snapshot 34 lowest := segment.Sealed() // last sealed block 35 highestID := highest.ID() 36 lowestID := lowest.ID() 37 38 if result.BlockID != lowestID { 39 return fmt.Errorf("root execution result for wrong block (%x != %x)", result.BlockID, lowest.ID()) 40 } 41 42 if seal.BlockID != lowestID { 43 return fmt.Errorf("root block seal for wrong block (%x != %x)", seal.BlockID, lowest.ID()) 44 } 45 46 if verifyResultID { 47 if seal.ResultID != result.ID() { 48 return fmt.Errorf("root block seal for wrong execution result (%x != %x)", seal.ResultID, result.ID()) 49 } 50 } 51 52 // identities must be canonically ordered 53 identities, err := snap.Identities(filter.Any) 54 if err != nil { 55 return fmt.Errorf("could not get identities for root snapshot: %w", err) 56 } 57 if !identities.Sorted(flow.Canonical[flow.Identity]) { 58 return fmt.Errorf("identities are not canonically ordered") 59 } 60 61 // root qc must be for reference block of snapshot 62 qc, err := snap.QuorumCertificate() 63 if err != nil { 64 return fmt.Errorf("could not get qc for root snapshot: %w", err) 65 } 66 if qc.BlockID != highestID { 67 return fmt.Errorf("qc is for wrong block (got: %x, expected: %x)", qc.BlockID, highestID) 68 } 69 70 firstView, err := snap.Epochs().Current().FirstView() 71 if err != nil { 72 return fmt.Errorf("could not get first view: %w", err) 73 } 74 finalView, err := snap.Epochs().Current().FinalView() 75 if err != nil { 76 return fmt.Errorf("could not get final view: %w", err) 77 } 78 79 // the segment must be fully within the current epoch 80 if firstView > lowest.Header.View { 81 return fmt.Errorf("lowest block of sealing segment has lower view than first view of epoch") 82 } 83 if highest.Header.View >= finalView { 84 return fmt.Errorf("final view of epoch less than first block view") 85 } 86 87 err = validateVersionBeacon(snap) 88 if err != nil { 89 return err 90 } 91 92 return nil 93 } 94 95 // IsValidRootSnapshotQCs checks internal consistency of QCs that are included in the root state snapshot 96 // It verifies QCs for main consensus and for each collection cluster. 97 func IsValidRootSnapshotQCs(snap protocol.Snapshot) error { 98 // validate main consensus QC 99 err := validateRootQC(snap) 100 if err != nil { 101 return fmt.Errorf("invalid root QC: %w", err) 102 } 103 104 // validate each collection cluster separately 105 curEpoch := snap.Epochs().Current() 106 clusters, err := curEpoch.Clustering() 107 if err != nil { 108 return fmt.Errorf("could not get clustering for root snapshot: %w", err) 109 } 110 for clusterIndex := range clusters { 111 cluster, err := curEpoch.Cluster(uint(clusterIndex)) 112 if err != nil { 113 return fmt.Errorf("could not get cluster %d for root snapshot: %w", clusterIndex, err) 114 } 115 err = validateClusterQC(cluster) 116 if err != nil { 117 return fmt.Errorf("invalid cluster qc %d: %w", clusterIndex, err) 118 } 119 } 120 return nil 121 } 122 123 // validateRootQC performs validation of root QC 124 // Returns nil on success 125 func validateRootQC(snap protocol.Snapshot) error { 126 identities, err := snap.Identities(filter.IsVotingConsensusCommitteeMember) 127 if err != nil { 128 return fmt.Errorf("could not get root snapshot identities: %w", err) 129 } 130 131 rootQC, err := snap.QuorumCertificate() 132 if err != nil { 133 return fmt.Errorf("could not get root QC: %w", err) 134 } 135 136 dkg, err := snap.Epochs().Current().DKG() 137 if err != nil { 138 return fmt.Errorf("could not get DKG for root snapshot: %w", err) 139 } 140 141 committee, err := committees.NewStaticCommitteeWithDKG(identities, flow.Identifier{}, dkg) 142 if err != nil { 143 return fmt.Errorf("could not create static committee: %w", err) 144 } 145 verifier := verification.NewCombinedVerifier(committee, signature.NewConsensusSigDataPacker(committee)) 146 hotstuffValidator := validator.New(committee, verifier) 147 err = hotstuffValidator.ValidateQC(rootQC) 148 if err != nil { 149 return fmt.Errorf("could not validate root qc: %w", err) 150 } 151 return nil 152 } 153 154 // validateClusterQC performs QC validation of single collection cluster 155 // Returns nil on success 156 func validateClusterQC(cluster protocol.Cluster) error { 157 committee, err := committees.NewStaticReplicas(cluster.Members(), flow.Identifier{}, nil, nil) 158 if err != nil { 159 return fmt.Errorf("could not create static committee: %w", err) 160 } 161 verifier := verification.NewStakingVerifier() 162 hotstuffValidator := validator.New(committee, verifier) 163 err = hotstuffValidator.ValidateQC(cluster.RootQC()) 164 if err != nil { 165 return fmt.Errorf("could not validate root qc: %w", err) 166 } 167 return nil 168 } 169 170 // validateVersionBeacon returns an InvalidServiceEventError if the snapshot 171 // version beacon is invalid 172 func validateVersionBeacon(snap protocol.Snapshot) error { 173 errf := func(msg string, args ...any) error { 174 return protocol.NewInvalidServiceEventErrorf(msg, args) 175 } 176 177 versionBeacon, err := snap.VersionBeacon() 178 if err != nil { 179 return errf("could not get version beacon: %w", err) 180 } 181 182 if versionBeacon == nil { 183 return nil 184 } 185 186 head, err := snap.Head() 187 if err != nil { 188 return errf("could not get snapshot head: %w", err) 189 } 190 191 // version beacon must be included in a past block to be effective 192 if versionBeacon.SealHeight > head.Height { 193 return errf("version table height higher than highest height") 194 } 195 196 err = versionBeacon.Validate() 197 if err != nil { 198 return errf("version beacon is invalid: %w", err) 199 } 200 201 return nil 202 } 203 204 // ValidRootSnapshotContainsEntityExpiryRange performs a sanity check to make sure the 205 // root snapshot has enough history to encompass at least one full entity expiry window. 206 // Entities (in particular transactions and collections) may reference a block within 207 // the past `flow.DefaultTransactionExpiry` blocks, so a new node must begin with at least 208 // this many blocks worth of history leading up to the snapshot's root block. 209 // 210 // Currently, Access Nodes and Consensus Nodes require root snapshots passing this validator function. 211 // 212 // - Consensus Nodes because they process guarantees referencing past blocks 213 // - Access Nodes because they index transactions referencing past blocks 214 // 215 // One of the following conditions must be satisfied to pass this validation: 216 // 1. This is a snapshot build from a first block of spork 217 // -> there is no earlier history which transactions/collections could reference 218 // 2. This snapshot sealing segment contains at least one expiry window of blocks 219 // -> all possible reference blocks in future transactions/collections will be within the initial history. 220 // 3. This snapshot sealing segment includes the spork root block 221 // -> there is no earlier history which transactions/collections could reference 222 func ValidRootSnapshotContainsEntityExpiryRange(snapshot protocol.Snapshot) error { 223 isSporkRootSnapshot, err := protocol.IsSporkRootSnapshot(snapshot) 224 if err != nil { 225 return fmt.Errorf("could not check if root snapshot is a spork root snapshot: %w", err) 226 } 227 // Condition 1 satisfied 228 if isSporkRootSnapshot { 229 return nil 230 } 231 232 head, err := snapshot.Head() 233 if err != nil { 234 return fmt.Errorf("could not query root snapshot head: %w", err) 235 } 236 sporkRootBlockHeight := snapshot.Params().SporkRootBlockHeight() 237 sealingSegment, err := snapshot.SealingSegment() 238 if err != nil { 239 return fmt.Errorf("could not query sealing segment: %w", err) 240 } 241 242 sealingSegmentLength := uint64(len(sealingSegment.AllBlocks())) 243 transactionExpiry := uint64(flow.DefaultTransactionExpiry) 244 blocksInSpork := head.Height - sporkRootBlockHeight + 1 // range is inclusive on both ends 245 246 // Condition 3: 247 // check if head.Height - sporkRootBlockHeight < flow.DefaultTransactionExpiry 248 // this is the case where we bootstrap early into the spork and there is simply not enough blocks 249 if blocksInSpork < transactionExpiry { 250 // the distance to spork root is less than transaction expiry, we need all blocks back to the spork root. 251 if sealingSegmentLength != blocksInSpork { 252 return fmt.Errorf("invalid root snapshot length, expecting exactly (%d), got (%d)", blocksInSpork, sealingSegmentLength) 253 } 254 } else { 255 // Condition 2: 256 // the distance to spork root is more than transaction expiry, we need at least `transactionExpiry` many blocks 257 if sealingSegmentLength < transactionExpiry { 258 return fmt.Errorf("invalid root snapshot length, expecting at least (%d), got (%d)", 259 transactionExpiry, sealingSegmentLength) 260 } 261 } 262 return nil 263 }