github.com/onflow/flow-go@v0.33.17/engine/execution/ingestion/loader/unexecuted_loader.go (about)

     1  package loader
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/rs/zerolog"
     8  
     9  	"github.com/onflow/flow-go/engine/execution/state"
    10  	"github.com/onflow/flow-go/model/flow"
    11  	"github.com/onflow/flow-go/state/protocol"
    12  	"github.com/onflow/flow-go/storage"
    13  	"github.com/onflow/flow-go/utils/logging"
    14  )
    15  
    16  // deprecated. Storehouse is going to use unfinalized loader instead
    17  type UnexecutedLoader struct {
    18  	log       zerolog.Logger
    19  	state     protocol.State
    20  	headers   storage.Headers
    21  	execState state.ExecutionState
    22  }
    23  
    24  func NewUnexecutedLoader(
    25  	log zerolog.Logger,
    26  	state protocol.State,
    27  	headers storage.Headers,
    28  	execState state.ExecutionState,
    29  ) *UnexecutedLoader {
    30  	return &UnexecutedLoader{
    31  		log:       log.With().Str("component", "ingestion_engine_unexecuted_loader").Logger(),
    32  		state:     state,
    33  		headers:   headers,
    34  		execState: execState,
    35  	}
    36  }
    37  
    38  // LoadUnexecuted loads all unexecuted and validated blocks
    39  // any error returned are exceptions
    40  func (e *UnexecutedLoader) LoadUnexecuted(ctx context.Context) ([]flow.Identifier, error) {
    41  	// saving an executed block is currently not transactional, so it's possible
    42  	// the block is marked as executed but the receipt might not be saved during a crash.
    43  	// in order to mitigate this problem, we always re-execute the last executed and finalized
    44  	// block.
    45  	// there is an exception, if the last executed block is a root block, then don't execute it,
    46  	// because the root has already been executed during bootstrapping phase. And re-executing
    47  	// a root block will fail, because the root block doesn't have a parent block, and could not
    48  	// get the result of it.
    49  	// TODO: remove this, when saving a executed block is transactional
    50  	lastExecutedHeight, lastExecutedID, err := e.execState.GetHighestExecutedBlockID(ctx)
    51  	if err != nil {
    52  		return nil, fmt.Errorf("could not get last executed: %w", err)
    53  	}
    54  
    55  	last, err := e.headers.ByBlockID(lastExecutedID)
    56  	if err != nil {
    57  		return nil, fmt.Errorf("could not get last executed final by ID: %w", err)
    58  	}
    59  
    60  	// don't reload root block
    61  	rootBlock, err := e.state.Params().SealedRoot()
    62  	if err != nil {
    63  		return nil, fmt.Errorf("failed to retrieve root block: %w", err)
    64  	}
    65  
    66  	blockIDs := make([]flow.Identifier, 0)
    67  	isRoot := rootBlock.ID() == last.ID()
    68  	if !isRoot {
    69  		executed, err := e.execState.IsBlockExecuted(lastExecutedHeight, lastExecutedID)
    70  		if err != nil {
    71  			return nil, fmt.Errorf("cannot check is last exeucted final block has been executed %v: %w", lastExecutedID, err)
    72  		}
    73  		if !executed {
    74  			// this should not happen, but if it does, execution should still work
    75  			e.log.Warn().
    76  				Hex("block_id", lastExecutedID[:]).
    77  				Msg("block marked as highest executed one, but not executable - internal inconsistency")
    78  
    79  			blockIDs = append(blockIDs, lastExecutedID)
    80  		}
    81  	}
    82  
    83  	finalized, pending, err := e.unexecutedBlocks(ctx)
    84  	if err != nil {
    85  		return nil, fmt.Errorf("could not reload unexecuted blocks: %w", err)
    86  	}
    87  
    88  	unexecuted := append(finalized, pending...)
    89  
    90  	log := e.log.With().
    91  		Int("total", len(unexecuted)).
    92  		Int("finalized", len(finalized)).
    93  		Int("pending", len(pending)).
    94  		Uint64("last_executed", lastExecutedHeight).
    95  		Hex("last_executed_id", lastExecutedID[:]).
    96  		Logger()
    97  
    98  	log.Info().Msg("reloading unexecuted blocks")
    99  
   100  	for _, blockID := range unexecuted {
   101  		blockIDs = append(blockIDs, blockID)
   102  		e.log.Debug().Hex("block_id", blockID[:]).Msg("reloaded block")
   103  	}
   104  
   105  	log.Info().Msg("all unexecuted have been successfully reloaded")
   106  
   107  	return blockIDs, nil
   108  }
   109  
   110  func (e *UnexecutedLoader) unexecutedBlocks(ctx context.Context) (
   111  	finalized []flow.Identifier,
   112  	pending []flow.Identifier,
   113  	err error,
   114  ) {
   115  	// pin the snapshot so that finalizedUnexecutedBlocks and pendingUnexecutedBlocks are based
   116  	// on the same snapshot.
   117  	snapshot := e.state.Final()
   118  
   119  	finalized, err = e.finalizedUnexecutedBlocks(ctx, snapshot)
   120  	if err != nil {
   121  		return nil, nil, fmt.Errorf("could not read finalized unexecuted blocks")
   122  	}
   123  
   124  	pending, err = e.pendingUnexecutedBlocks(ctx, snapshot)
   125  	if err != nil {
   126  		return nil, nil, fmt.Errorf("could not read pending unexecuted blocks")
   127  	}
   128  
   129  	return finalized, pending, nil
   130  }
   131  
   132  func (e *UnexecutedLoader) finalizedUnexecutedBlocks(ctx context.Context, finalized protocol.Snapshot) (
   133  	[]flow.Identifier,
   134  	error,
   135  ) {
   136  	// get finalized height
   137  	final, err := finalized.Head()
   138  	if err != nil {
   139  		return nil, fmt.Errorf("could not get finalized block: %w", err)
   140  	}
   141  
   142  	// find the first unexecuted and finalized block
   143  	// We iterate from the last finalized, check if it has been executed,
   144  	// if not, keep going to the lower height, until we find an executed
   145  	// block, and then the next height is the first unexecuted.
   146  	// If there is only one finalized, and it's executed (i.e. root block),
   147  	// then the firstUnexecuted is a unfinalized block, which is ok,
   148  	// because the next loop will ensure it only iterates through finalized
   149  	// blocks.
   150  	lastExecuted := final.Height
   151  
   152  	// dynamically bootstrapped execution node will reload blocks from
   153  	// [sealedRoot.Height + 1, finalizedRoot.Height] and execute them on startup.
   154  	rootBlock, err := e.state.Params().SealedRoot()
   155  	if err != nil {
   156  		return nil, fmt.Errorf("failed to retrieve root block: %w", err)
   157  	}
   158  
   159  	for ; lastExecuted > rootBlock.Height; lastExecuted-- {
   160  		finalizedID, err := e.headers.BlockIDByHeight(lastExecuted)
   161  		if err != nil {
   162  			return nil, fmt.Errorf("could not get header at height: %v, %w", lastExecuted, err)
   163  		}
   164  
   165  		executed, err := e.execState.IsBlockExecuted(lastExecuted, finalizedID)
   166  		if err != nil {
   167  			return nil, fmt.Errorf("could not check whether block is executed: %w", err)
   168  		}
   169  
   170  		if executed {
   171  			break
   172  		}
   173  	}
   174  
   175  	firstUnexecuted := lastExecuted + 1
   176  
   177  	unexecuted := make([]flow.Identifier, 0)
   178  
   179  	// starting from the first unexecuted block, go through each unexecuted and finalized block
   180  	// reload its block to execution queues
   181  	for height := firstUnexecuted; height <= final.Height; height++ {
   182  		finalizedID, err := e.headers.BlockIDByHeight(height)
   183  		if err != nil {
   184  			return nil, fmt.Errorf("could not get header at height: %v, %w", height, err)
   185  		}
   186  
   187  		unexecuted = append(unexecuted, finalizedID)
   188  	}
   189  
   190  	e.log.Info().
   191  		Uint64("last_finalized", final.Height).
   192  		Uint64("last_finalized_executed", lastExecuted).
   193  		Uint64("sealed_root_height", rootBlock.Height).
   194  		Hex("sealed_root_id", logging.Entity(rootBlock)).
   195  		Uint64("first_unexecuted", firstUnexecuted).
   196  		Int("total_finalized_unexecuted", len(unexecuted)).
   197  		Msgf("finalized unexecuted blocks")
   198  
   199  	return unexecuted, nil
   200  }
   201  
   202  func (e *UnexecutedLoader) pendingUnexecutedBlocks(ctx context.Context, finalized protocol.Snapshot) (
   203  	[]flow.Identifier,
   204  	error,
   205  ) {
   206  	pendings, err := finalized.Descendants()
   207  	if err != nil {
   208  		return nil, fmt.Errorf("could not get pending blocks: %w", err)
   209  	}
   210  
   211  	unexecuted := make([]flow.Identifier, 0)
   212  
   213  	for _, pending := range pendings {
   214  		p, err := e.headers.ByBlockID(pending)
   215  		if err != nil {
   216  			return nil, fmt.Errorf("could not get header by block id: %w", err)
   217  		}
   218  		executed, err := e.execState.IsBlockExecuted(p.Height, pending)
   219  		if err != nil {
   220  			return nil, fmt.Errorf("could not check block executed or not: %w", err)
   221  		}
   222  
   223  		if !executed {
   224  			unexecuted = append(unexecuted, pending)
   225  		}
   226  	}
   227  
   228  	return unexecuted, nil
   229  }