@@ -5,6 +5,7 @@ package blockstore
5
5
import (
6
6
"context"
7
7
"errors"
8
+ "fmt"
8
9
"sync"
9
10
"sync/atomic"
10
11
@@ -64,6 +65,12 @@ type Blockstore interface {
64
65
HashOnRead (enabled bool )
65
66
}
66
67
68
+ // GetManyBlockstore is a blockstore interface that supports a GetMany method
69
+ type GetManyBlockstore interface {
70
+ Blockstore
71
+ GetMany (context.Context , []cid.Cid ) ([]blocks.Block , []cid.Cid , error )
72
+ }
73
+
67
74
// Viewer can be implemented by blockstores that offer zero-copy access to
68
75
// values.
69
76
//
@@ -310,6 +317,227 @@ func (bs *blockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
310
317
return output , nil
311
318
}
312
319
320
+ // GetManyOption is a getManyBlockStore option implementation
321
+ type GetManyOption struct {
322
+ f func (bs * getManyBlockStore )
323
+ }
324
+
325
+ // NewGetManyBlockstore returns a default GetManyBlockstore implementation
326
+ // using the provided datastore.TxnDatastore backend.
327
+ func NewGetManyBlockstore (d ds.TxnDatastore , opts ... GetManyOption ) GetManyBlockstore {
328
+ bs := & getManyBlockStore {
329
+ datastore : d ,
330
+ }
331
+
332
+ for _ , o := range opts {
333
+ o .f (bs )
334
+ }
335
+
336
+ if ! bs .noPrefix {
337
+ bs .datastore = dsns .WrapTxnDatastore (bs .datastore , BlockPrefix )
338
+ }
339
+ return bs
340
+ }
341
+
342
+ type getManyBlockStore struct {
343
+ datastore ds.TxnDatastore
344
+
345
+ rehash atomic.Bool
346
+ writeThrough bool
347
+ noPrefix bool
348
+ }
349
+
350
+ func (bs * getManyBlockStore ) HashOnRead (enabled bool ) {
351
+ bs .rehash .Store (enabled )
352
+ }
353
+
354
+ func (bs * getManyBlockStore ) Get (ctx context.Context , k cid.Cid ) (blocks.Block , error ) {
355
+ if ! k .Defined () {
356
+ logger .Error ("undefined cid in blockstore" )
357
+ return nil , ipld.ErrNotFound {Cid : k }
358
+ }
359
+ bdata , err := bs .datastore .Get (ctx , dshelp .MultihashToDsKey (k .Hash ()))
360
+ if err == ds .ErrNotFound {
361
+ return nil , ipld.ErrNotFound {Cid : k }
362
+ }
363
+ if err != nil {
364
+ return nil , err
365
+ }
366
+ if bs .rehash .Load () {
367
+ rbcid , err := k .Prefix ().Sum (bdata )
368
+ if err != nil {
369
+ return nil , err
370
+ }
371
+
372
+ if ! rbcid .Equals (k ) {
373
+ return nil , ErrHashMismatch
374
+ }
375
+
376
+ return blocks .NewBlockWithCid (bdata , rbcid )
377
+ }
378
+ return blocks .NewBlockWithCid (bdata , k )
379
+ }
380
+
381
+ func (bs * getManyBlockStore ) GetMany (ctx context.Context , cs []cid.Cid ) ([]blocks.Block , []cid.Cid , error ) {
382
+ if len (cs ) == 1 {
383
+ // performance fast-path
384
+ block , err := bs .Get (ctx , cs [0 ])
385
+ return []blocks.Block {block }, nil , err
386
+ }
387
+
388
+ t , err := bs .datastore .NewTransaction (ctx , false )
389
+ if err != nil {
390
+ return nil , nil , err
391
+ }
392
+ blks := make ([]blocks.Block , 0 , len (cs ))
393
+ missingCIDs := make ([]cid.Cid , 0 , len (cs ))
394
+ for _ , c := range cs {
395
+ if ! c .Defined () {
396
+ logger .Error ("undefined cid in blockstore" )
397
+ return nil , nil , ipld.ErrNotFound {Cid : c }
398
+ }
399
+ bdata , err := t .Get (ctx , dshelp .MultihashToDsKey (c .Hash ()))
400
+ if err != nil {
401
+ if err == ds .ErrNotFound {
402
+ missingCIDs = append (missingCIDs , c )
403
+ } else {
404
+ return nil , nil , err
405
+ }
406
+ } else {
407
+ if bs .rehash .Load () {
408
+ rbcid , err := c .Prefix ().Sum (bdata )
409
+ if err != nil {
410
+ return nil , nil , err
411
+ }
412
+
413
+ if ! rbcid .Equals (c ) {
414
+ return nil , nil , fmt .Errorf ("block in storage has different hash (%x) than requested (%x)" , rbcid .Hash (), c .Hash ())
415
+ }
416
+
417
+ blk , err := blocks .NewBlockWithCid (bdata , rbcid )
418
+ if err != nil {
419
+ return nil , nil , err
420
+ }
421
+
422
+ blks = append (blks , blk )
423
+ } else {
424
+ blk , err := blocks .NewBlockWithCid (bdata , c )
425
+ if err != nil {
426
+ return nil , nil , err
427
+ }
428
+
429
+ blks = append (blks , blk )
430
+ }
431
+ }
432
+ }
433
+ return blks , missingCIDs , t .Commit (ctx )
434
+ }
435
+
436
+ func (bs * getManyBlockStore ) Put (ctx context.Context , block blocks.Block ) error {
437
+ k := dshelp .MultihashToDsKey (block .Cid ().Hash ())
438
+
439
+ // Has is cheaper than Put, so see if we already have it
440
+ if ! bs .writeThrough {
441
+ exists , err := bs .datastore .Has (ctx , k )
442
+ if err == nil && exists {
443
+ return nil // already stored.
444
+ }
445
+ }
446
+ return bs .datastore .Put (ctx , k , block .RawData ())
447
+ }
448
+
449
+ func (bs * getManyBlockStore ) PutMany (ctx context.Context , blocks []blocks.Block ) error {
450
+ if len (blocks ) == 1 {
451
+ // performance fast-path
452
+ return bs .Put (ctx , blocks [0 ])
453
+ }
454
+
455
+ t , err := bs .datastore .NewTransaction (ctx , false )
456
+ if err != nil {
457
+ return err
458
+ }
459
+ for _ , b := range blocks {
460
+ k := dshelp .MultihashToDsKey (b .Cid ().Hash ())
461
+
462
+ if ! bs .writeThrough {
463
+ exists , err := bs .datastore .Has (ctx , k )
464
+ if err == nil && exists {
465
+ continue
466
+ }
467
+ }
468
+
469
+ err = t .Put (ctx , k , b .RawData ())
470
+ if err != nil {
471
+ return err
472
+ }
473
+ }
474
+ return t .Commit (ctx )
475
+ }
476
+
477
+ func (bs * getManyBlockStore ) Has (ctx context.Context , k cid.Cid ) (bool , error ) {
478
+ return bs .datastore .Has (ctx , dshelp .MultihashToDsKey (k .Hash ()))
479
+ }
480
+
481
+ func (bs * getManyBlockStore ) GetSize (ctx context.Context , k cid.Cid ) (int , error ) {
482
+ size , err := bs .datastore .GetSize (ctx , dshelp .MultihashToDsKey (k .Hash ()))
483
+ if err == ds .ErrNotFound {
484
+ return - 1 , ipld.ErrNotFound {Cid : k }
485
+ }
486
+ return size , err
487
+ }
488
+
489
+ func (bs * getManyBlockStore ) DeleteBlock (ctx context.Context , k cid.Cid ) error {
490
+ return bs .datastore .Delete (ctx , dshelp .MultihashToDsKey (k .Hash ()))
491
+ }
492
+
493
+ // AllKeysChan runs a query for keys from the blockstore.
494
+ // this is very simplistic, in the future, take dsq.Query as a param?
495
+ //
496
+ // AllKeysChan respects context.
497
+ func (bs * getManyBlockStore ) AllKeysChan (ctx context.Context ) (<- chan cid.Cid , error ) {
498
+
499
+ // KeysOnly, because that would be _a lot_ of data.
500
+ q := dsq.Query {KeysOnly : true }
501
+ res , err := bs .datastore .Query (ctx , q )
502
+ if err != nil {
503
+ return nil , err
504
+ }
505
+
506
+ output := make (chan cid.Cid , dsq .KeysOnlyBufSize )
507
+ go func () {
508
+ defer func () {
509
+ res .Close () // ensure exit (signals early exit, too)
510
+ close (output )
511
+ }()
512
+
513
+ for {
514
+ e , ok := res .NextSync ()
515
+ if ! ok {
516
+ return
517
+ }
518
+ if e .Error != nil {
519
+ logger .Errorf ("blockstore.AllKeysChan got err: %s" , e .Error )
520
+ return
521
+ }
522
+
523
+ // need to convert to key.Key using key.KeyFromDsKey.
524
+ bk , err := dshelp .BinaryFromDsKey (ds .RawKey (e .Key ))
525
+ if err != nil {
526
+ logger .Warnf ("error parsing key from binary: %s" , err )
527
+ continue
528
+ }
529
+ k := cid .NewCidV1 (cid .Raw , bk )
530
+ select {
531
+ case <- ctx .Done ():
532
+ return
533
+ case output <- k :
534
+ }
535
+ }
536
+ }()
537
+
538
+ return output , nil
539
+ }
540
+
313
541
// NewGCLocker returns a default implementation of
314
542
// GCLocker using standard [RW] mutexes.
315
543
func NewGCLocker () GCLocker {
0 commit comments