github.com/koko1123/flow-go-1@v0.29.6/engine/consensus/ingestion/core.go (about)

     1  // (c) 2019 Dapper Labs - ALL RIGHTS RESERVED
     2  
     3  package ingestion
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/rs/zerolog"
    11  	"go.opentelemetry.io/otel/attribute"
    12  
    13  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
    14  	"github.com/koko1123/flow-go-1/engine"
    15  	"github.com/koko1123/flow-go-1/model/flow"
    16  	"github.com/koko1123/flow-go-1/module"
    17  	"github.com/koko1123/flow-go-1/module/mempool"
    18  	"github.com/koko1123/flow-go-1/module/metrics"
    19  	"github.com/koko1123/flow-go-1/module/signature"
    20  	"github.com/koko1123/flow-go-1/module/trace"
    21  	"github.com/koko1123/flow-go-1/state/protocol"
    22  	"github.com/koko1123/flow-go-1/storage"
    23  )
    24  
    25  // Core represents core logic of the ingestion engine. It contains logic
    26  // for handling single collection which are channeled from engine in concurrent way.
    27  type Core struct {
    28  	log     zerolog.Logger        // used to log relevant actions with context
    29  	tracer  module.Tracer         // used for tracing
    30  	mempool module.MempoolMetrics // used to track mempool metrics
    31  	state   protocol.State        // used to access the protocol state
    32  	headers storage.Headers       // used to retrieve headers
    33  	pool    mempool.Guarantees    // used to keep pending guarantees in pool
    34  }
    35  
    36  func NewCore(
    37  	log zerolog.Logger,
    38  	tracer module.Tracer,
    39  	mempool module.MempoolMetrics,
    40  	state protocol.State,
    41  	headers storage.Headers,
    42  	pool mempool.Guarantees,
    43  ) *Core {
    44  	return &Core{
    45  		log:     log.With().Str("ingestion", "core").Logger(),
    46  		tracer:  tracer,
    47  		mempool: mempool,
    48  		state:   state,
    49  		headers: headers,
    50  		pool:    pool,
    51  	}
    52  }
    53  
    54  // OnGuarantee is used to process collection guarantees received
    55  // from nodes that are not consensus nodes (notably collection nodes).
    56  // Returns expected errors:
    57  //   - engine.InvalidInputError if the collection violates protocol rules
    58  //   - engine.UnverifiableInputError if the reference block of the collection is unknown
    59  //   - engine.OutdatedInputError if the collection is already expired
    60  //
    61  // All other errors are unexpected and potential symptoms of internal state corruption.
    62  func (e *Core) OnGuarantee(originID flow.Identifier, guarantee *flow.CollectionGuarantee) error {
    63  
    64  	span, _ := e.tracer.StartCollectionSpan(context.Background(), guarantee.CollectionID, trace.CONIngOnCollectionGuarantee)
    65  	span.SetAttributes(
    66  		attribute.String("originID", originID.String()),
    67  	)
    68  	defer span.End()
    69  
    70  	guaranteeID := guarantee.ID()
    71  
    72  	log := e.log.With().
    73  		Hex("origin_id", originID[:]).
    74  		Hex("collection_id", guaranteeID[:]).
    75  		Hex("signers", guarantee.SignerIndices).
    76  		Logger()
    77  	log.Info().Msg("collection guarantee received")
    78  
    79  	// skip collection guarantees that are already in our memory pool
    80  	exists := e.pool.Has(guaranteeID)
    81  	if exists {
    82  		log.Debug().Msg("skipping known collection guarantee")
    83  		return nil
    84  	}
    85  
    86  	// check collection guarantee's validity
    87  	err := e.validateOrigin(originID, guarantee) // retrieve and validate the sender of the collection guarantee
    88  	if err != nil {
    89  		return fmt.Errorf("origin validation error: %w", err)
    90  	}
    91  	err = e.validateExpiry(guarantee) // ensure that collection has not expired
    92  	if err != nil {
    93  		return fmt.Errorf("expiry validation error: %w", err)
    94  	}
    95  	err = e.validateGuarantors(guarantee) // ensure the guarantors are allowed to produce this collection
    96  	if err != nil {
    97  		return fmt.Errorf("guarantor validation error: %w", err)
    98  	}
    99  
   100  	// at this point, we can add the guarantee to the memory pool
   101  	added := e.pool.Add(guarantee)
   102  	if !added {
   103  		log.Debug().Msg("discarding guarantee already in pool")
   104  		return nil
   105  	}
   106  	log.Info().Msg("collection guarantee added to pool")
   107  
   108  	e.mempool.MempoolEntries(metrics.ResourceGuarantee, e.pool.Size())
   109  	return nil
   110  }
   111  
   112  // validateExpiry validates that the collection has not expired w.r.t. the local
   113  // latest finalized block.
   114  // Expected errors during normal operation:
   115  //   - engine.UnverifiableInputError if the reference block of the collection is unknown
   116  //   - engine.OutdatedInputError if the collection is already expired
   117  //
   118  // All other errors are unexpected and potential symptoms of internal state corruption.
   119  func (e *Core) validateExpiry(guarantee *flow.CollectionGuarantee) error {
   120  	// get the last finalized header and the reference block header
   121  	final, err := e.state.Final().Head()
   122  	if err != nil {
   123  		return fmt.Errorf("could not get finalized header: %w", err)
   124  	}
   125  	ref, err := e.headers.ByBlockID(guarantee.ReferenceBlockID)
   126  	if errors.Is(err, storage.ErrNotFound) {
   127  		return engine.NewUnverifiableInputError("collection guarantee refers to an unknown block (id=%x): %w", guarantee.ReferenceBlockID, err)
   128  	}
   129  
   130  	// if head has advanced beyond the block referenced by the collection guarantee by more than 'expiry' number of blocks,
   131  	// then reject the collection
   132  	if ref.Height > final.Height {
   133  		return nil // the reference block is newer than the latest finalized one
   134  	}
   135  	if final.Height-ref.Height > flow.DefaultTransactionExpiry {
   136  		return engine.NewOutdatedInputErrorf("collection guarantee expired ref_height=%d final_height=%d", ref.Height, final.Height)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  // validateGuarantors validates that the guarantors of a collection are valid,
   143  // in that they are all from the same cluster and that cluster is allowed to
   144  // produce the given collection w.r.t. the guarantee's reference block.
   145  // Expected errors during normal operation:
   146  //   - engine.InvalidInputError if the origin violates any requirements
   147  //   - engine.UnverifiableInputError if the reference block of the collection is unknown
   148  //
   149  // All other errors are unexpected and potential symptoms of internal state corruption.
   150  //
   151  // TODO: Eventually we should check the signatures, ensure a quorum of the
   152  // cluster, and ensure HotStuff finalization rules. Likely a cluster-specific
   153  // version of the follower will be a good fit for this. For now, collection
   154  // nodes independently decide when a collection is finalized and we only check
   155  // that the guarantors are all from the same cluster. This implementation is NOT BFT.
   156  func (e *Core) validateGuarantors(guarantee *flow.CollectionGuarantee) error {
   157  	// get the clusters to assign the guarantee and check if the guarantor is part of it
   158  	snapshot := e.state.AtBlockID(guarantee.ReferenceBlockID)
   159  	cluster, err := snapshot.Epochs().Current().ClusterByChainID(guarantee.ChainID)
   160  	// reference block not found
   161  	if errors.Is(err, storage.ErrNotFound) {
   162  		return engine.NewUnverifiableInputError(
   163  			"could not get clusters with chainID %v for unknown reference block (id=%x): %w", guarantee.ChainID, guarantee.ReferenceBlockID, err)
   164  	}
   165  	// cluster not found by the chain ID
   166  	if errors.Is(err, protocol.ErrClusterNotFound) {
   167  		return engine.NewInvalidInputErrorf("cluster not found by chain ID %v: %w", guarantee.ChainID, err)
   168  	}
   169  	if err != nil {
   170  		return fmt.Errorf("internal error retrieving collector clusters for guarantee (ReferenceBlockID: %v, ChainID: %v): %w",
   171  			guarantee.ReferenceBlockID, guarantee.ChainID, err)
   172  	}
   173  
   174  	// ensure the guarantors are from the same cluster
   175  	clusterMembers := cluster.Members()
   176  
   177  	// find guarantors by signer indices
   178  	guarantors, err := signature.DecodeSignerIndicesToIdentities(clusterMembers, guarantee.SignerIndices)
   179  	if err != nil {
   180  		if signature.IsInvalidSignerIndicesError(err) {
   181  			return engine.NewInvalidInputErrorf("could not decode guarantor indices: %w", err)
   182  		}
   183  		// unexpected error
   184  		return fmt.Errorf("unexpected internal error decoding signer indices: %w", err)
   185  	}
   186  
   187  	// determine whether signers reach minimally required stake threshold
   188  	threshold := hotstuff.ComputeWeightThresholdForBuildingQC(clusterMembers.TotalWeight()) // compute required stake threshold
   189  	totalStake := flow.IdentityList(guarantors).TotalWeight()
   190  	if totalStake < threshold {
   191  		return engine.NewInvalidInputErrorf("collection guarantee qc signers have insufficient stake of %d (required=%d)", totalStake, threshold)
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  // validateOrigin validates that the message has a valid sender (origin). We
   198  // only accept guarantees from an origin that is part of the identity table
   199  // at the collection's reference block. Furthermore, the origin must be
   200  // an authorized (i.e. positive weight), non-ejected collector node.
   201  // Expected errors during normal operation:
   202  //   - engine.InvalidInputError if the origin violates any requirements
   203  //   - engine.UnverifiableInputError if the reference block of the collection is unknown
   204  //
   205  // All other errors are unexpected and potential symptoms of internal state corruption.
   206  //
   207  // TODO: ultimately, the origin broadcasting a collection is irrelevant, as long as the
   208  // collection itself is valid. The origin is only needed in case the guarantee is found
   209  // to be invalid, in which case we might want to slash the origin.
   210  func (e *Core) validateOrigin(originID flow.Identifier, guarantee *flow.CollectionGuarantee) error {
   211  	refState := e.state.AtBlockID(guarantee.ReferenceBlockID)
   212  	valid, err := protocol.IsNodeAuthorizedWithRoleAt(refState, originID, flow.RoleCollection)
   213  	if err != nil {
   214  		// collection with an unknown reference block is unverifiable
   215  		if errors.Is(err, storage.ErrNotFound) {
   216  			return engine.NewUnverifiableInputError("could not get origin (id=%x) for unknown reference block (id=%x): %w", originID, guarantee.ReferenceBlockID, err)
   217  		}
   218  		return fmt.Errorf("unexpected error checking collection origin %x at reference block %x: %w", originID, guarantee.ReferenceBlockID, err)
   219  	}
   220  	if !valid {
   221  		return engine.NewInvalidInputErrorf("invalid collection origin (id=%x)", originID)
   222  	}
   223  	return nil
   224  }