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  }