github.com/onflow/flow-go@v0.33.17/storage/badger/blocks.go (about)

     1  // (c) 2019 Dapper Labs - ALL RIGHTS RESERVED
     2  
     3  package badger
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/dgraph-io/badger/v2"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/storage"
    13  	"github.com/onflow/flow-go/storage/badger/operation"
    14  	"github.com/onflow/flow-go/storage/badger/transaction"
    15  )
    16  
    17  // Blocks implements a simple block storage around a badger DB.
    18  type Blocks struct {
    19  	db       *badger.DB
    20  	headers  *Headers
    21  	payloads *Payloads
    22  }
    23  
    24  // NewBlocks ...
    25  func NewBlocks(db *badger.DB, headers *Headers, payloads *Payloads) *Blocks {
    26  	b := &Blocks{
    27  		db:       db,
    28  		headers:  headers,
    29  		payloads: payloads,
    30  	}
    31  	return b
    32  }
    33  
    34  func (b *Blocks) StoreTx(block *flow.Block) func(*transaction.Tx) error {
    35  	return func(tx *transaction.Tx) error {
    36  		err := b.headers.storeTx(block.Header)(tx)
    37  		if err != nil {
    38  			return fmt.Errorf("could not store header %v: %w", block.Header.ID(), err)
    39  		}
    40  		err = b.payloads.storeTx(block.ID(), block.Payload)(tx)
    41  		if err != nil {
    42  			return fmt.Errorf("could not store payload: %w", err)
    43  		}
    44  		return nil
    45  	}
    46  }
    47  
    48  func (b *Blocks) retrieveTx(blockID flow.Identifier) func(*badger.Txn) (*flow.Block, error) {
    49  	return func(tx *badger.Txn) (*flow.Block, error) {
    50  		header, err := b.headers.retrieveTx(blockID)(tx)
    51  		if err != nil {
    52  			return nil, fmt.Errorf("could not retrieve header: %w", err)
    53  		}
    54  		payload, err := b.payloads.retrieveTx(blockID)(tx)
    55  		if err != nil {
    56  			return nil, fmt.Errorf("could not retrieve payload: %w", err)
    57  		}
    58  		block := &flow.Block{
    59  			Header:  header,
    60  			Payload: payload,
    61  		}
    62  		return block, nil
    63  	}
    64  }
    65  
    66  // Store ...
    67  func (b *Blocks) Store(block *flow.Block) error {
    68  	return operation.RetryOnConflictTx(b.db, transaction.Update, b.StoreTx(block))
    69  }
    70  
    71  // ByID ...
    72  func (b *Blocks) ByID(blockID flow.Identifier) (*flow.Block, error) {
    73  	tx := b.db.NewTransaction(false)
    74  	defer tx.Discard()
    75  	return b.retrieveTx(blockID)(tx)
    76  }
    77  
    78  // ByHeight ...
    79  func (b *Blocks) ByHeight(height uint64) (*flow.Block, error) {
    80  	tx := b.db.NewTransaction(false)
    81  	defer tx.Discard()
    82  
    83  	blockID, err := b.headers.retrieveIdByHeightTx(height)(tx)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	return b.retrieveTx(blockID)(tx)
    88  }
    89  
    90  // ByCollectionID ...
    91  func (b *Blocks) ByCollectionID(collID flow.Identifier) (*flow.Block, error) {
    92  	var blockID flow.Identifier
    93  	err := b.db.View(operation.LookupCollectionBlock(collID, &blockID))
    94  	if err != nil {
    95  		return nil, fmt.Errorf("could not look up block: %w", err)
    96  	}
    97  	return b.ByID(blockID)
    98  }
    99  
   100  // IndexBlockForCollections ...
   101  func (b *Blocks) IndexBlockForCollections(blockID flow.Identifier, collIDs []flow.Identifier) error {
   102  	for _, collID := range collIDs {
   103  		err := operation.RetryOnConflict(b.db.Update, operation.SkipDuplicates(operation.IndexCollectionBlock(collID, blockID)))
   104  		if err != nil {
   105  			return fmt.Errorf("could not index collection block (%x): %w", collID, err)
   106  		}
   107  	}
   108  	return nil
   109  }
   110  
   111  // InsertLastFullBlockHeightIfNotExists inserts the last full block height
   112  // Calling this function multiple times is a no-op and returns no expected errors.
   113  func (b *Blocks) InsertLastFullBlockHeightIfNotExists(height uint64) error {
   114  	return operation.RetryOnConflict(b.db.Update, func(tx *badger.Txn) error {
   115  		err := operation.InsertLastCompleteBlockHeightIfNotExists(height)(tx)
   116  		if err != nil {
   117  			return fmt.Errorf("could not set LastFullBlockHeight: %w", err)
   118  		}
   119  		return nil
   120  	})
   121  }
   122  
   123  // UpdateLastFullBlockHeight upsert (update or insert) the last full block height
   124  func (b *Blocks) UpdateLastFullBlockHeight(height uint64) error {
   125  	return operation.RetryOnConflict(b.db.Update, func(tx *badger.Txn) error {
   126  
   127  		// try to update
   128  		err := operation.UpdateLastCompleteBlockHeight(height)(tx)
   129  		if err == nil {
   130  			return nil
   131  		}
   132  
   133  		if !errors.Is(err, storage.ErrNotFound) {
   134  			return fmt.Errorf("could not update LastFullBlockHeight: %w", err)
   135  		}
   136  
   137  		// if key does not exist, try insert.
   138  		err = operation.InsertLastCompleteBlockHeight(height)(tx)
   139  		if err != nil {
   140  			return fmt.Errorf("could not insert LastFullBlockHeight: %w", err)
   141  		}
   142  
   143  		return nil
   144  	})
   145  }
   146  
   147  // GetLastFullBlockHeight ...
   148  func (b *Blocks) GetLastFullBlockHeight() (uint64, error) {
   149  	var h uint64
   150  	err := b.db.View(operation.RetrieveLastCompleteBlockHeight(&h))
   151  	if err != nil {
   152  		return 0, fmt.Errorf("failed to retrieve LastFullBlockHeight: %w", err)
   153  	}
   154  	return h, nil
   155  }