github.com/celestiaorg/celestia-node@v0.15.0-beta.1/share/eds/blockstore.go (about)

     1  package eds
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	bstore "github.com/ipfs/boxo/blockstore"
     9  	"github.com/ipfs/boxo/datastore/dshelp"
    10  	blocks "github.com/ipfs/go-block-format"
    11  	"github.com/ipfs/go-cid"
    12  	"github.com/ipfs/go-datastore"
    13  	"github.com/ipfs/go-datastore/namespace"
    14  	ipld "github.com/ipfs/go-ipld-format"
    15  )
    16  
    17  var _ bstore.Blockstore = (*blockstore)(nil)
    18  
    19  var (
    20  	blockstoreCacheKey      = datastore.NewKey("bs-cache")
    21  	errUnsupportedOperation = errors.New("unsupported operation")
    22  )
    23  
    24  // blockstore implements the store.Blockstore interface on an EDSStore.
    25  // The lru cache approach is heavily inspired by the existing implementation upstream.
    26  // We simplified the design to not support multiple shards per key, call GetSize directly on the
    27  // underlying RO blockstore, and do not throw errors on Put/PutMany. Also, we do not abstract away
    28  // the blockstore operations.
    29  //
    30  // The intuition here is that each CAR file is its own blockstore, so we need this top level
    31  // implementation to allow for the blockstore operations to be routed to the underlying stores.
    32  type blockstore struct {
    33  	store *Store
    34  	ds    datastore.Batching
    35  }
    36  
    37  func newBlockstore(store *Store, ds datastore.Batching) *blockstore {
    38  	return &blockstore{
    39  		store: store,
    40  		ds:    namespace.Wrap(ds, blockstoreCacheKey),
    41  	}
    42  }
    43  
    44  func (bs *blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) {
    45  	keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash())
    46  	if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) {
    47  		// key wasn't found in top level blockstore, but could be in datastore while being reconstructed
    48  		dsHas, dsErr := bs.ds.Has(ctx, dshelp.MultihashToDsKey(cid.Hash()))
    49  		if dsErr != nil {
    50  			return false, nil
    51  		}
    52  		return dsHas, nil
    53  	}
    54  	if err != nil {
    55  		return false, err
    56  	}
    57  
    58  	return len(keys) > 0, nil
    59  }
    60  
    61  func (bs *blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) {
    62  	blockstr, err := bs.getReadOnlyBlockstore(ctx, cid)
    63  	if err == nil {
    64  		defer closeAndLog("blockstore", blockstr)
    65  		return blockstr.Get(ctx, cid)
    66  	}
    67  
    68  	if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) {
    69  		k := dshelp.MultihashToDsKey(cid.Hash())
    70  		blockData, err := bs.ds.Get(ctx, k)
    71  		if err == nil {
    72  			return blocks.NewBlockWithCid(blockData, cid)
    73  		}
    74  		// nmt's GetNode expects an ipld.ErrNotFound when a cid is not found.
    75  		return nil, ipld.ErrNotFound{Cid: cid}
    76  	}
    77  
    78  	log.Debugf("failed to get blockstore for cid %s: %s", cid, err)
    79  	return nil, err
    80  }
    81  
    82  func (bs *blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) {
    83  	blockstr, err := bs.getReadOnlyBlockstore(ctx, cid)
    84  	if err == nil {
    85  		defer closeAndLog("blockstore", blockstr)
    86  		return blockstr.GetSize(ctx, cid)
    87  	}
    88  
    89  	if errors.Is(err, ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) {
    90  		k := dshelp.MultihashToDsKey(cid.Hash())
    91  		size, err := bs.ds.GetSize(ctx, k)
    92  		if err == nil {
    93  			return size, nil
    94  		}
    95  		// nmt's GetSize expects an ipld.ErrNotFound when a cid is not found.
    96  		return 0, ipld.ErrNotFound{Cid: cid}
    97  	}
    98  
    99  	log.Debugf("failed to get size for cid %s: %s", cid, err)
   100  	return 0, err
   101  }
   102  
   103  func (bs *blockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error {
   104  	k := dshelp.MultihashToDsKey(cid.Hash())
   105  	return bs.ds.Delete(ctx, k)
   106  }
   107  
   108  func (bs *blockstore) Put(ctx context.Context, blk blocks.Block) error {
   109  	k := dshelp.MultihashToDsKey(blk.Cid().Hash())
   110  	// note: we leave duplicate resolution to the underlying datastore
   111  	return bs.ds.Put(ctx, k, blk.RawData())
   112  }
   113  
   114  func (bs *blockstore) PutMany(ctx context.Context, blocks []blocks.Block) error {
   115  	if len(blocks) == 1 {
   116  		// performance fast-path
   117  		return bs.Put(ctx, blocks[0])
   118  	}
   119  
   120  	t, err := bs.ds.Batch(ctx)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	for _, b := range blocks {
   125  		k := dshelp.MultihashToDsKey(b.Cid().Hash())
   126  		err = t.Put(ctx, k, b.RawData())
   127  		if err != nil {
   128  			return err
   129  		}
   130  	}
   131  	return t.Commit(ctx)
   132  }
   133  
   134  // AllKeysChan is a noop on the EDS blockstore because the keys are not stored in a single CAR file.
   135  func (bs *blockstore) AllKeysChan(context.Context) (<-chan cid.Cid, error) {
   136  	return nil, errUnsupportedOperation
   137  }
   138  
   139  // HashOnRead is a noop on the EDS blockstore but an error cannot be returned due to the method
   140  // signature from the blockstore interface.
   141  func (bs *blockstore) HashOnRead(bool) {
   142  	log.Warnf("HashOnRead is a noop on the EDS blockstore")
   143  }
   144  
   145  // getReadOnlyBlockstore finds the underlying blockstore of the shard that contains the given CID.
   146  func (bs *blockstore) getReadOnlyBlockstore(ctx context.Context, cid cid.Cid) (*BlockstoreCloser, error) {
   147  	keys, err := bs.store.dgstr.ShardsContainingMultihash(ctx, cid.Hash())
   148  	if errors.Is(err, datastore.ErrNotFound) || errors.Is(err, ErrNotFoundInIndex) {
   149  		return nil, ErrNotFound
   150  	}
   151  	if err != nil {
   152  		return nil, fmt.Errorf("failed to find shards containing multihash: %w", err)
   153  	}
   154  
   155  	// check if either cache contains an accessor
   156  	shardKey := keys[0]
   157  	accessor, err := bs.store.cache.Load().Get(shardKey)
   158  	if err == nil {
   159  		return blockstoreCloser(accessor)
   160  	}
   161  
   162  	// load accessor to the blockstore cache and use it as blockstoreCloser
   163  	accessor, err = bs.store.cache.Load().Second().GetOrLoad(ctx, shardKey, bs.store.getAccessor)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("failed to get accessor for shard %s: %w", shardKey, err)
   166  	}
   167  	return blockstoreCloser(accessor)
   168  }