github.com/celestiaorg/celestia-node@v0.15.0-beta.1/pruner/checkpoint.go (about)

     1  package pruner
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"sync/atomic"
     8  
     9  	"github.com/ipfs/go-datastore"
    10  
    11  	"github.com/celestiaorg/celestia-node/header"
    12  )
    13  
    14  var (
    15  	storePrefix   = datastore.NewKey("pruner")
    16  	checkpointKey = datastore.NewKey("checkpoint")
    17  )
    18  
    19  // checkpoint contains information related to the state of the
    20  // pruner service that is periodically persisted to disk.
    21  type checkpoint struct {
    22  	lastPrunedHeader atomic.Pointer[header.ExtendedHeader]
    23  
    24  	LastPrunedHeight uint64            `json:"last_pruned_height"`
    25  	FailedHeaders    map[uint64]string `json:"failed"`
    26  }
    27  
    28  // initializeCheckpoint initializes the checkpoint, storing the earliest header in the chain.
    29  func (s *Service) initializeCheckpoint(ctx context.Context) error {
    30  	firstHeader, err := s.getter.GetByHeight(ctx, 1)
    31  	if err != nil {
    32  		return fmt.Errorf("failed to initialize checkpoint: %w", err)
    33  	}
    34  
    35  	return s.updateCheckpoint(ctx, firstHeader, nil)
    36  }
    37  
    38  // loadCheckpoint loads the last checkpoint from disk, initializing it if it does not already exist.
    39  func (s *Service) loadCheckpoint(ctx context.Context) error {
    40  	bin, err := s.ds.Get(ctx, checkpointKey)
    41  	if err != nil {
    42  		if err == datastore.ErrNotFound {
    43  			return s.initializeCheckpoint(ctx)
    44  		}
    45  		return fmt.Errorf("failed to load checkpoint: %w", err)
    46  	}
    47  
    48  	var cp *checkpoint
    49  	err = json.Unmarshal(bin, &cp)
    50  	if err != nil {
    51  		return fmt.Errorf("failed to unmarshal checkpoint: %w", err)
    52  	}
    53  	s.checkpoint = cp
    54  
    55  	// load last pruned header based off height
    56  	lastPruned, err := s.getter.GetByHeight(ctx, cp.LastPrunedHeight)
    57  	if err != nil {
    58  		return fmt.Errorf("failed to load last pruned header at height %d: %w", cp.LastPrunedHeight, err)
    59  	}
    60  
    61  	s.checkpoint.lastPrunedHeader.Store(lastPruned)
    62  	return nil
    63  }
    64  
    65  // updateCheckpoint updates the checkpoint with the last pruned header height
    66  // and persists it to disk.
    67  func (s *Service) updateCheckpoint(
    68  	ctx context.Context,
    69  	lastPruned *header.ExtendedHeader,
    70  	failedHeights map[uint64]error,
    71  ) error {
    72  	for height, failErr := range failedHeights {
    73  		// if the height already exists, just update the error
    74  		s.checkpoint.FailedHeaders[height] = failErr.Error()
    75  	}
    76  
    77  	s.checkpoint.lastPrunedHeader.Store(lastPruned)
    78  	s.checkpoint.LastPrunedHeight = lastPruned.Height()
    79  
    80  	bin, err := json.Marshal(s.checkpoint)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	return s.ds.Put(ctx, checkpointKey, bin)
    86  }
    87  
    88  func (s *Service) lastPruned() *header.ExtendedHeader {
    89  	return s.checkpoint.lastPrunedHeader.Load()
    90  }