github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/storehouse/register_store.go (about)

     1  package storehouse
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"go.uber.org/atomic"
     8  
     9  	"github.com/rs/zerolog"
    10  
    11  	"github.com/onflow/flow-go/engine/execution"
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/storage"
    14  )
    15  
    16  type RegisterStore struct {
    17  	memStore   *InMemoryRegisterStore
    18  	diskStore  execution.OnDiskRegisterStore
    19  	wal        execution.ExecutedFinalizedWAL
    20  	finalized  execution.FinalizedReader
    21  	log        zerolog.Logger
    22  	finalizing *atomic.Bool // making sure only one goroutine is finalizing at a time
    23  	notifier   execution.RegisterStoreNotifier
    24  }
    25  
    26  var _ execution.RegisterStore = (*RegisterStore)(nil)
    27  
    28  type NoopNotifier struct{}
    29  
    30  func NewNoopNotifier() *NoopNotifier { return &NoopNotifier{} }
    31  
    32  func (n *NoopNotifier) OnFinalizedAndExecutedHeightUpdated(height uint64) {}
    33  
    34  var _ execution.RegisterStoreNotifier = (*NoopNotifier)(nil)
    35  
    36  func NewRegisterStore(
    37  	diskStore execution.OnDiskRegisterStore,
    38  	wal execution.ExecutedFinalizedWAL,
    39  	finalized execution.FinalizedReader,
    40  	log zerolog.Logger,
    41  	notifier execution.RegisterStoreNotifier,
    42  ) (*RegisterStore, error) {
    43  	if notifier == nil {
    44  		return nil, fmt.Errorf("notifier is empty, use NoopNotifier if you don't need it")
    45  	}
    46  
    47  	// replay the executed and finalized blocks from the write ahead logs
    48  	// to the OnDiskRegisterStore
    49  	height, err := syncDiskStore(wal, diskStore, log)
    50  	if err != nil {
    51  		return nil, fmt.Errorf("cannot sync disk store: %w", err)
    52  	}
    53  
    54  	// fetch the last executed and finalized block ID
    55  	finalizedID, err := finalized.FinalizedBlockIDAtHeight(height)
    56  	if err != nil {
    57  		return nil, fmt.Errorf("cannot get finalized block ID at height %d: %w", height, err)
    58  	}
    59  
    60  	// init the memStore with the last executed and finalized block ID
    61  	memStore := NewInMemoryRegisterStore(height, finalizedID)
    62  
    63  	log.Info().Msgf("initialized in memory register store at block %v, height %v", finalizedID, height)
    64  
    65  	return &RegisterStore{
    66  		memStore:   memStore,
    67  		diskStore:  diskStore,
    68  		wal:        wal,
    69  		finalized:  finalized,
    70  		finalizing: atomic.NewBool(false),
    71  		log:        log.With().Str("module", "register-store").Logger(),
    72  		notifier:   notifier,
    73  	}, nil
    74  }
    75  
    76  // GetRegister first try to get the register from InMemoryRegisterStore, then OnDiskRegisterStore
    77  // 1. below pruned height, and is conflicting
    78  // 2. below pruned height, and is finalized
    79  // 3. above pruned height, and is not executed
    80  // 4. above pruned height, and is executed, and register is updated
    81  // 5. above pruned height, and is executed, but register is not updated since pruned height
    82  // It returns:
    83  //   - (value, nil) if the register value is found at the given block
    84  //   - (nil, nil) if the register is not found
    85  //   - (nil, storage.ErrHeightNotIndexed) if the height is below the first height that is indexed.
    86  //   - (nil, storehouse.ErrNotExecuted) if the block is not executed yet
    87  //   - (nil, storehouse.ErrNotExecuted) if the block is conflicting iwth finalized block
    88  //   - (nil, err) for any other exceptions
    89  func (r *RegisterStore) GetRegister(height uint64, blockID flow.Identifier, register flow.RegisterID) (flow.RegisterValue, error) {
    90  	reg, err := r.memStore.GetRegister(height, blockID, register)
    91  	// the height might be lower than the lowest height in memStore,
    92  	// or the register might not be found in memStore.
    93  	if err == nil {
    94  		// this register was updated before its block is finalized
    95  		return reg, nil
    96  	}
    97  
    98  	prunedError, ok := IsPrunedError(err)
    99  	if !ok {
   100  		// this means we ran into an exception. finding a register from in-memory store should either
   101  		// getting the register value or getting a ErrPruned error.
   102  		return flow.RegisterValue{}, fmt.Errorf("cannot get register from memStore: %w", err)
   103  	}
   104  
   105  	// if in memory store returns PrunedError, and register height is above the pruned height,
   106  	// then it means the block is connected to the pruned block of in memory store, which is
   107  	// a finalized block and executed block, so we can get its value from on disk store.
   108  	if height > prunedError.PrunedHeight {
   109  		return r.getAndConvertNotFoundErr(register, prunedError.PrunedHeight)
   110  	}
   111  
   112  	// if the block is below or equal to the pruned height, then there are two cases:
   113  	// the block is a finalized block, or a conflicting block.
   114  	// In order to distinguish, we need to query the finalized block ID at that height
   115  
   116  	var finalizedID flow.Identifier
   117  	if height == prunedError.PrunedHeight {
   118  		// if the block is at the pruned height, then the finalized ID is the pruned ID from in memory store,
   119  		// this saves a DB query
   120  		finalizedID = prunedError.PrunedID
   121  	} else {
   122  		// if the block is below the pruned height, we query the finalized ID from the finalized reader
   123  		finalizedID, err = r.finalized.FinalizedBlockIDAtHeight(height)
   124  		if err != nil {
   125  			return nil, fmt.Errorf("cannot get finalized block ID at height %d: %w", height, err)
   126  		}
   127  	}
   128  
   129  	isConflictingBlock := blockID != finalizedID
   130  	if isConflictingBlock {
   131  		// conflicting blocks are considered as un-executed
   132  		return flow.RegisterValue{}, fmt.Errorf("getting registers from conflicting block %v at height %v: %w", blockID, height, ErrNotExecuted)
   133  	}
   134  	return r.getAndConvertNotFoundErr(register, height)
   135  }
   136  
   137  // getAndConvertNotFoundErr returns nil if the register is not found from storage
   138  func (r *RegisterStore) getAndConvertNotFoundErr(register flow.RegisterID, height uint64) (flow.RegisterValue, error) {
   139  	val, err := r.diskStore.Get(register, height)
   140  	if errors.Is(err, storage.ErrNotFound) {
   141  		// FVM expects the error to be nil when register is not found
   142  		return nil, nil
   143  	}
   144  	return val, err
   145  }
   146  
   147  // SaveRegisters saves to InMemoryRegisterStore first, then trigger the same check as OnBlockFinalized
   148  // Depend on InMemoryRegisterStore.SaveRegisters
   149  // It returns:
   150  // - nil if the registers are saved successfully
   151  // - exception is the block is above the pruned height but does not connect to the pruned height (conflicting block).
   152  // - exception if the block is below the pruned height
   153  // - exception if the save block is saved again
   154  // - exception for any other exception
   155  func (r *RegisterStore) SaveRegisters(header *flow.Header, registers flow.RegisterEntries) error {
   156  	err := r.memStore.SaveRegisters(header.Height, header.ID(), header.ParentID, registers)
   157  	if err != nil {
   158  		return fmt.Errorf("cannot save register to memStore: %w", err)
   159  	}
   160  
   161  	err = r.OnBlockFinalized()
   162  	if err != nil {
   163  		return fmt.Errorf("cannot trigger OnBlockFinalized: %w", err)
   164  	}
   165  	return nil
   166  }
   167  
   168  // Depend on FinalizedReader's FinalizedBlockIDAtHeight
   169  // Depend on ExecutedFinalizedWAL.Append
   170  // Depend on OnDiskRegisterStore.SaveRegisters
   171  // OnBlockFinalized trigger the check of whether a block at the next height becomes finalized and executed.
   172  // the next height is the existing finalized and executed block's height + 1.
   173  // If a block at next height becomes finalized and executed, then:
   174  // 1. write the registers to write ahead logs
   175  // 2. save the registers of the block to OnDiskRegisterStore
   176  // 3. prune the height in InMemoryRegisterStore
   177  func (r *RegisterStore) OnBlockFinalized() error {
   178  	// only one goroutine can execute OnBlockFinalized at a time
   179  	if !r.finalizing.CompareAndSwap(false, true) {
   180  		return nil
   181  	}
   182  
   183  	defer r.finalizing.Store(false)
   184  	return r.onBlockFinalized()
   185  }
   186  
   187  func (r *RegisterStore) onBlockFinalized() error {
   188  	latest := r.diskStore.LatestHeight()
   189  	next := latest + 1
   190  	blockID, err := r.finalized.FinalizedBlockIDAtHeight(next)
   191  	if errors.Is(err, storage.ErrNotFound) {
   192  		// next block is not finalized yet
   193  		return nil
   194  	}
   195  
   196  	regs, err := r.memStore.GetUpdatedRegisters(next, blockID)
   197  	if errors.Is(err, ErrNotExecuted) {
   198  		// next block is not executed yet
   199  		return nil
   200  	}
   201  
   202  	// TODO: append WAL
   203  	// err = r.wal.Append(next, regs)
   204  	// if err != nil {
   205  	// 	return fmt.Errorf("cannot write %v registers to write ahead logs for height %v: %w", len(regs), next, err)
   206  	// }
   207  
   208  	err = r.diskStore.Store(regs, next)
   209  	if err != nil {
   210  		return fmt.Errorf("cannot save %v registers to disk store for height %v: %w", len(regs), next, err)
   211  	}
   212  
   213  	r.notifier.OnFinalizedAndExecutedHeightUpdated(next)
   214  
   215  	err = r.memStore.Prune(next, blockID)
   216  	if err != nil {
   217  		return fmt.Errorf("cannot prune memStore for height %v: %w", next, err)
   218  	}
   219  
   220  	return r.onBlockFinalized() // check again until there is no more finalized block
   221  }
   222  
   223  // LastFinalizedAndExecutedHeight returns the height of the last finalized and executed block,
   224  // which has been saved in OnDiskRegisterStore
   225  func (r *RegisterStore) LastFinalizedAndExecutedHeight() uint64 {
   226  	// diskStore caches the latest height in memory
   227  	return r.diskStore.LatestHeight()
   228  }
   229  
   230  // IsBlockExecuted returns true if the block is executed, false if not executed
   231  // Note: it returns (true, nil) even if the block has been pruned from on disk register store,
   232  func (r *RegisterStore) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) {
   233  	executed, err := r.memStore.IsBlockExecuted(height, blockID)
   234  	if err != nil {
   235  		// the only error memStore would return is when the given height is lower than the pruned height in memStore.
   236  		// Since the pruned height in memStore is a finalized and executed height, in order to know if the block
   237  		// is executed, we just need to check if this block is the finalized blcok at the given height.
   238  		executed, err = r.isBlockFinalized(height, blockID)
   239  		return executed, err
   240  	}
   241  
   242  	return executed, nil
   243  }
   244  
   245  func (r *RegisterStore) isBlockFinalized(height uint64, blockID flow.Identifier) (bool, error) {
   246  	finalizedID, err := r.finalized.FinalizedBlockIDAtHeight(height)
   247  	if err != nil {
   248  		return false, fmt.Errorf("cannot get finalized block ID at height %d: %w", height, err)
   249  	}
   250  	return finalizedID == blockID, nil
   251  }
   252  
   253  // syncDiskStore replay WAL to disk store
   254  func syncDiskStore(
   255  	wal execution.ExecutedFinalizedWAL,
   256  	diskStore execution.OnDiskRegisterStore,
   257  	log zerolog.Logger,
   258  ) (uint64, error) {
   259  	// TODO: replace diskStore.Latest with wal.Latest
   260  	// latest, err := r.wal.Latest()
   261  	var err error
   262  	latest := diskStore.LatestHeight() // tmp
   263  	if err != nil {
   264  		return 0, fmt.Errorf("cannot get latest height from write ahead logs: %w", err)
   265  	}
   266  
   267  	stored := diskStore.LatestHeight()
   268  
   269  	if stored > latest {
   270  		return 0, fmt.Errorf("latest height in storehouse %v is larger than latest height %v in write ahead logs", stored, latest)
   271  	}
   272  
   273  	if stored < latest {
   274  		// replay
   275  		reader := wal.GetReader(stored + 1)
   276  		for {
   277  			height, registers, err := reader.Next()
   278  			// TODO: to rename
   279  			if errors.Is(err, storage.ErrNotFound) {
   280  				break
   281  			}
   282  			if err != nil {
   283  				return 0, fmt.Errorf("cannot read registers from write ahead logs: %w", err)
   284  			}
   285  
   286  			err = diskStore.Store(registers, height)
   287  			if err != nil {
   288  				return 0, fmt.Errorf("cannot save registers to disk store at height %v : %w", height, err)
   289  			}
   290  		}
   291  	}
   292  
   293  	return latest, nil
   294  }