github.com/celestiaorg/celestia-node@v0.15.0-beta.1/nodebuilder/da/service.go (about)

     1  package da
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"strings"
     8  
     9  	logging "github.com/ipfs/go-log/v2"
    10  	"github.com/rollkit/go-da"
    11  
    12  	"github.com/celestiaorg/celestia-app/pkg/appconsts"
    13  
    14  	"github.com/celestiaorg/celestia-node/blob"
    15  	nodeblob "github.com/celestiaorg/celestia-node/nodebuilder/blob"
    16  	"github.com/celestiaorg/celestia-node/share"
    17  )
    18  
    19  var _ da.DA = (*Service)(nil)
    20  
    21  var log = logging.Logger("go-da")
    22  
    23  // heightLen is a length (in bytes) of serialized height.
    24  //
    25  // This is 8 as uint64 consist of 8 bytes.
    26  const heightLen = 8
    27  
    28  type Service struct {
    29  	blobServ nodeblob.Module
    30  }
    31  
    32  func NewService(blobMod nodeblob.Module) *Service {
    33  	return &Service{
    34  		blobServ: blobMod,
    35  	}
    36  }
    37  
    38  // MaxBlobSize returns the max blob size
    39  func (s *Service) MaxBlobSize(context.Context) (uint64, error) {
    40  	return appconsts.DefaultMaxBytes, nil
    41  }
    42  
    43  // Get returns Blob for each given ID, or an error.
    44  func (s *Service) Get(ctx context.Context, ids []da.ID, ns da.Namespace) ([]da.Blob, error) {
    45  	blobs := make([]da.Blob, 0, len(ids))
    46  	for _, id := range ids {
    47  		height, commitment := SplitID(id)
    48  		log.Debugw("getting blob", "height", height, "commitment", commitment, "namespace", share.Namespace(ns))
    49  		currentBlob, err := s.blobServ.Get(ctx, height, ns, commitment)
    50  		log.Debugw("got blob", "height", height, "commitment", commitment, "namespace", share.Namespace(ns))
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  		blobs = append(blobs, currentBlob.Data)
    55  	}
    56  	return blobs, nil
    57  }
    58  
    59  // GetIDs returns IDs of all Blobs located in DA at given height.
    60  func (s *Service) GetIDs(ctx context.Context, height uint64, namespace da.Namespace) ([]da.ID, error) {
    61  	var ids []da.ID //nolint:prealloc
    62  	log.Debugw("getting ids", "height", height, "namespace", share.Namespace(namespace))
    63  	blobs, err := s.blobServ.GetAll(ctx, height, []share.Namespace{namespace})
    64  	log.Debugw("got ids", "height", height, "namespace", share.Namespace(namespace))
    65  	if err != nil {
    66  		if strings.Contains(err.Error(), blob.ErrBlobNotFound.Error()) {
    67  			return nil, nil
    68  		}
    69  		return nil, err
    70  	}
    71  	for _, b := range blobs {
    72  		ids = append(ids, MakeID(height, b.Commitment))
    73  	}
    74  	return ids, nil
    75  }
    76  
    77  // GetProofs returns inclusion Proofs for all Blobs located in DA at given height.
    78  func (s *Service) GetProofs(ctx context.Context, ids []da.ID, namespace da.Namespace) ([]da.Proof, error) {
    79  	proofs := make([]da.Proof, len(ids))
    80  	for i, id := range ids {
    81  		height, commitment := SplitID(id)
    82  		proof, err := s.blobServ.GetProof(ctx, height, namespace, commitment)
    83  		if err != nil {
    84  			return nil, err
    85  		}
    86  		proofs[i], err = proof.MarshalJSON()
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  	return proofs, nil
    92  }
    93  
    94  // Commit creates a Commitment for each given Blob.
    95  func (s *Service) Commit(_ context.Context, daBlobs []da.Blob, namespace da.Namespace) ([]da.Commitment, error) {
    96  	_, commitments, err := s.blobsAndCommitments(daBlobs, namespace)
    97  	return commitments, err
    98  }
    99  
   100  // Submit submits the Blobs to Data Availability layer.
   101  func (s *Service) Submit(
   102  	ctx context.Context,
   103  	daBlobs []da.Blob,
   104  	gasPrice float64,
   105  	namespace da.Namespace,
   106  ) ([]da.ID, error) {
   107  	blobs, _, err := s.blobsAndCommitments(daBlobs, namespace)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	height, err := s.blobServ.Submit(ctx, blobs, blob.GasPrice(gasPrice))
   113  	if err != nil {
   114  		log.Error("failed to submit blobs", "height", height, "gas price", gasPrice)
   115  		return nil, err
   116  	}
   117  	log.Info("successfully submitted blobs", "height", height, "gas price", gasPrice)
   118  	ids := make([]da.ID, len(blobs))
   119  	for i, blob := range blobs {
   120  		ids[i] = MakeID(height, blob.Commitment)
   121  	}
   122  	return ids, nil
   123  }
   124  
   125  // blobsAndCommitments converts []da.Blob to []*blob.Blob and generates corresponding
   126  // []da.Commitment
   127  func (s *Service) blobsAndCommitments(
   128  	daBlobs []da.Blob, namespace da.Namespace,
   129  ) ([]*blob.Blob, []da.Commitment, error) {
   130  	blobs := make([]*blob.Blob, 0, len(daBlobs))
   131  	commitments := make([]da.Commitment, 0, len(daBlobs))
   132  	for _, daBlob := range daBlobs {
   133  		b, err := blob.NewBlobV0(namespace, daBlob)
   134  		if err != nil {
   135  			return nil, nil, err
   136  		}
   137  		blobs = append(blobs, b)
   138  
   139  		commitments = append(commitments, b.Commitment)
   140  	}
   141  	return blobs, commitments, nil
   142  }
   143  
   144  // Validate validates Commitments against the corresponding Proofs. This should be possible without
   145  // retrieving the Blobs.
   146  func (s *Service) Validate(
   147  	ctx context.Context,
   148  	ids []da.ID,
   149  	daProofs []da.Proof,
   150  	namespace da.Namespace,
   151  ) ([]bool, error) {
   152  	included := make([]bool, len(ids))
   153  	proofs := make([]*blob.Proof, len(ids))
   154  	for i, daProof := range daProofs {
   155  		blobProof := &blob.Proof{}
   156  		err := blobProof.UnmarshalJSON(daProof)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		proofs[i] = blobProof
   161  	}
   162  	for i, id := range ids {
   163  		height, commitment := SplitID(id)
   164  		// TODO(tzdybal): for some reason, if proof doesn't match commitment, API returns (false, "blob:
   165  		// invalid proof")    but analysis of the code in celestia-node implies this should never happen -
   166  		// maybe it's caused by openrpc?    there is no way of gently handling errors here, but returned
   167  		// value is fine for us
   168  		fmt.Println("proof", proofs[i] == nil, "commitment", commitment == nil)
   169  		isIncluded, _ := s.blobServ.Included(ctx, height, namespace, proofs[i], commitment)
   170  		included = append(included, isIncluded)
   171  	}
   172  	return included, nil
   173  }
   174  
   175  func MakeID(height uint64, commitment da.Commitment) da.ID {
   176  	id := make([]byte, heightLen+len(commitment))
   177  	binary.LittleEndian.PutUint64(id, height)
   178  	copy(id[heightLen:], commitment)
   179  	return id
   180  }
   181  
   182  func SplitID(id da.ID) (uint64, da.Commitment) {
   183  	if len(id) <= heightLen {
   184  		return 0, nil
   185  	}
   186  	commitment := id[heightLen:]
   187  	return binary.LittleEndian.Uint64(id[:heightLen]), commitment
   188  }