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

     1  package getters
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"sync/atomic"
     9  
    10  	"github.com/ipfs/boxo/blockservice"
    11  	"go.opentelemetry.io/otel/attribute"
    12  	"go.opentelemetry.io/otel/trace"
    13  
    14  	"github.com/celestiaorg/rsmt2d"
    15  
    16  	"github.com/celestiaorg/celestia-node/header"
    17  	"github.com/celestiaorg/celestia-node/libs/utils"
    18  	"github.com/celestiaorg/celestia-node/share"
    19  	"github.com/celestiaorg/celestia-node/share/eds"
    20  	"github.com/celestiaorg/celestia-node/share/eds/byzantine"
    21  	"github.com/celestiaorg/celestia-node/share/ipld"
    22  )
    23  
    24  var _ share.Getter = (*IPLDGetter)(nil)
    25  
    26  // IPLDGetter is a share.Getter that retrieves shares from the bitswap network. Result caching is
    27  // handled by the provided blockservice. A blockservice session will be created for retrieval if the
    28  // passed context is wrapped with WithSession.
    29  type IPLDGetter struct {
    30  	rtrv  *eds.Retriever
    31  	bServ blockservice.BlockService
    32  }
    33  
    34  // NewIPLDGetter creates a new share.Getter that retrieves shares from the bitswap network.
    35  func NewIPLDGetter(bServ blockservice.BlockService) *IPLDGetter {
    36  	return &IPLDGetter{
    37  		rtrv:  eds.NewRetriever(bServ),
    38  		bServ: bServ,
    39  	}
    40  }
    41  
    42  // GetShare gets a single share at the given EDS coordinates from the bitswap network.
    43  func (ig *IPLDGetter) GetShare(ctx context.Context, header *header.ExtendedHeader, row, col int) (share.Share, error) {
    44  	var err error
    45  	ctx, span := tracer.Start(ctx, "ipld/get-share", trace.WithAttributes(
    46  		attribute.Int("row", row),
    47  		attribute.Int("col", col),
    48  	))
    49  	defer func() {
    50  		utils.SetStatusAndEnd(span, err)
    51  	}()
    52  
    53  	dah := header.DAH
    54  	upperBound := len(dah.RowRoots)
    55  	if row >= upperBound || col >= upperBound {
    56  		err := share.ErrOutOfBounds
    57  		span.RecordError(err)
    58  		return nil, err
    59  	}
    60  	root, leaf := ipld.Translate(dah, row, col)
    61  
    62  	// wrap the blockservice in a session if it has been signaled in the context.
    63  	blockGetter := getGetter(ctx, ig.bServ)
    64  	s, err := ipld.GetShare(ctx, blockGetter, root, leaf, len(dah.RowRoots))
    65  	if errors.Is(err, ipld.ErrNodeNotFound) {
    66  		// convert error to satisfy getter interface contract
    67  		err = share.ErrNotFound
    68  	}
    69  	if err != nil {
    70  		return nil, fmt.Errorf("getter/ipld: failed to retrieve share: %w", err)
    71  	}
    72  
    73  	return s, nil
    74  }
    75  
    76  func (ig *IPLDGetter) GetEDS(
    77  	ctx context.Context,
    78  	header *header.ExtendedHeader,
    79  ) (eds *rsmt2d.ExtendedDataSquare, err error) {
    80  	ctx, span := tracer.Start(ctx, "ipld/get-eds")
    81  	defer func() {
    82  		utils.SetStatusAndEnd(span, err)
    83  	}()
    84  
    85  	// rtrv.Retrieve calls shares.GetShares until enough shares are retrieved to reconstruct the EDS
    86  	eds, err = ig.rtrv.Retrieve(ctx, header.DAH)
    87  	if errors.Is(err, ipld.ErrNodeNotFound) {
    88  		// convert error to satisfy getter interface contract
    89  		err = share.ErrNotFound
    90  	}
    91  	var errByz *byzantine.ErrByzantine
    92  	if errors.As(err, &errByz) {
    93  		return nil, err
    94  	}
    95  	if err != nil {
    96  		return nil, fmt.Errorf("getter/ipld: failed to retrieve eds: %w", err)
    97  	}
    98  	return eds, nil
    99  }
   100  
   101  func (ig *IPLDGetter) GetSharesByNamespace(
   102  	ctx context.Context,
   103  	header *header.ExtendedHeader,
   104  	namespace share.Namespace,
   105  ) (shares share.NamespacedShares, err error) {
   106  	ctx, span := tracer.Start(ctx, "ipld/get-shares-by-namespace", trace.WithAttributes(
   107  		attribute.String("namespace", namespace.String()),
   108  	))
   109  	defer func() {
   110  		utils.SetStatusAndEnd(span, err)
   111  	}()
   112  
   113  	if err = namespace.ValidateForData(); err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	// wrap the blockservice in a session if it has been signaled in the context.
   118  	blockGetter := getGetter(ctx, ig.bServ)
   119  	shares, err = eds.CollectSharesByNamespace(ctx, blockGetter, header.DAH, namespace)
   120  	if errors.Is(err, ipld.ErrNodeNotFound) {
   121  		// convert error to satisfy getter interface contract
   122  		err = share.ErrNotFound
   123  	}
   124  	if err != nil {
   125  		return nil, fmt.Errorf("getter/ipld: failed to retrieve shares by namespace: %w", err)
   126  	}
   127  	return shares, nil
   128  }
   129  
   130  var sessionKey = &session{}
   131  
   132  // session is a struct that can optionally be passed by context to the share.Getter methods using
   133  // WithSession to indicate that a blockservice session should be created.
   134  type session struct {
   135  	sync.Mutex
   136  	atomic.Pointer[blockservice.Session]
   137  	ctx context.Context
   138  }
   139  
   140  // WithSession stores an empty session in the context, indicating that a blockservice session should
   141  // be created.
   142  func WithSession(ctx context.Context) context.Context {
   143  	return context.WithValue(ctx, sessionKey, &session{ctx: ctx})
   144  }
   145  
   146  func getGetter(ctx context.Context, service blockservice.BlockService) blockservice.BlockGetter {
   147  	s, ok := ctx.Value(sessionKey).(*session)
   148  	if !ok {
   149  		return service
   150  	}
   151  
   152  	val := s.Load()
   153  	if val != nil {
   154  		return val
   155  	}
   156  
   157  	s.Lock()
   158  	defer s.Unlock()
   159  	val = s.Load()
   160  	if val == nil {
   161  		val = blockservice.NewSession(s.ctx, service)
   162  		s.Store(val)
   163  	}
   164  	return val
   165  }