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 }