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

     1  package cmd
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  	"path/filepath"
     8  	"reflect"
     9  	"strconv"
    10  
    11  	"github.com/spf13/cobra"
    12  
    13  	"github.com/celestiaorg/celestia-node/blob"
    14  	cmdnode "github.com/celestiaorg/celestia-node/cmd"
    15  	"github.com/celestiaorg/celestia-node/share"
    16  )
    17  
    18  var (
    19  	base64Flag bool
    20  
    21  	gasPrice float64
    22  
    23  	// flagFileInput allows the user to provide file path to the json file
    24  	// for submitting multiple blobs.
    25  	flagFileInput = "input-file"
    26  )
    27  
    28  func init() {
    29  	Cmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd)
    30  
    31  	getCmd.PersistentFlags().BoolVar(
    32  		&base64Flag,
    33  		"base64",
    34  		false,
    35  		"printed blob's data a base64 string",
    36  	)
    37  
    38  	getAllCmd.PersistentFlags().BoolVar(
    39  		&base64Flag,
    40  		"base64",
    41  		false,
    42  		"printed blob's data as a base64 string",
    43  	)
    44  
    45  	submitCmd.PersistentFlags().Float64Var(
    46  		&gasPrice,
    47  		"gas.price",
    48  		float64(blob.DefaultGasPrice()),
    49  		"specifies gas price (in utia) for blob submission.\n"+
    50  			"Gas price will be set to default (0.002) if no value is passed",
    51  	)
    52  
    53  	submitCmd.PersistentFlags().String(flagFileInput, "", "Specify the file input")
    54  }
    55  
    56  var Cmd = &cobra.Command{
    57  	Use:               "blob [command]",
    58  	Short:             "Allows to interact with the Blob Service via JSON-RPC",
    59  	Args:              cobra.NoArgs,
    60  	PersistentPreRunE: cmdnode.InitClient,
    61  }
    62  
    63  var getCmd = &cobra.Command{
    64  	Use:   "get [height] [namespace] [commitment]",
    65  	Args:  cobra.ExactArgs(3),
    66  	Short: "Returns the blob for the given namespace by commitment at a particular height.",
    67  	RunE: func(cmd *cobra.Command, args []string) error {
    68  		client, err := cmdnode.ParseClientFromCtx(cmd.Context())
    69  		if err != nil {
    70  			return err
    71  		}
    72  		defer client.Close()
    73  
    74  		height, err := strconv.ParseUint(args[0], 10, 64)
    75  		if err != nil {
    76  			return fmt.Errorf("error parsing a height:%v", err)
    77  		}
    78  
    79  		namespace, err := cmdnode.ParseV0Namespace(args[1])
    80  		if err != nil {
    81  			return fmt.Errorf("error parsing a namespace:%v", err)
    82  		}
    83  
    84  		commitment, err := base64.StdEncoding.DecodeString(args[2])
    85  		if err != nil {
    86  			return fmt.Errorf("error parsing a commitment:%v", err)
    87  		}
    88  
    89  		blob, err := client.Blob.Get(cmd.Context(), height, namespace, commitment)
    90  
    91  		formatter := formatData
    92  		if base64Flag || err != nil {
    93  			formatter = nil
    94  		}
    95  		return cmdnode.PrintOutput(blob, err, formatter)
    96  	},
    97  }
    98  
    99  var getAllCmd = &cobra.Command{
   100  	Use:   "get-all [height] [namespace]",
   101  	Args:  cobra.ExactArgs(2),
   102  	Short: "Returns all blobs for the given namespace at a particular height.",
   103  	RunE: func(cmd *cobra.Command, args []string) error {
   104  		client, err := cmdnode.ParseClientFromCtx(cmd.Context())
   105  		if err != nil {
   106  			return err
   107  		}
   108  		defer client.Close()
   109  
   110  		height, err := strconv.ParseUint(args[0], 10, 64)
   111  		if err != nil {
   112  			return fmt.Errorf("error parsing a height:%v", err)
   113  		}
   114  
   115  		namespace, err := cmdnode.ParseV0Namespace(args[1])
   116  		if err != nil {
   117  			return fmt.Errorf("error parsing a namespace:%v", err)
   118  		}
   119  
   120  		blobs, err := client.Blob.GetAll(cmd.Context(), height, []share.Namespace{namespace})
   121  		formatter := formatData
   122  		if base64Flag || err != nil {
   123  			formatter = nil
   124  		}
   125  		return cmdnode.PrintOutput(blobs, err, formatter)
   126  	},
   127  }
   128  
   129  var submitCmd = &cobra.Command{
   130  	Use: "submit [namespace] [blobData]",
   131  	Args: func(cmd *cobra.Command, args []string) error {
   132  		path, err := cmd.Flags().GetString(flagFileInput)
   133  		if err != nil {
   134  			return err
   135  		}
   136  
   137  		// If there is a file path input we'll check for the file extension
   138  		if path != "" {
   139  			if filepath.Ext(path) != ".json" {
   140  				return fmt.Errorf("invalid file extension, require json got %s", filepath.Ext(path))
   141  			}
   142  
   143  			return nil
   144  		}
   145  
   146  		if len(args) < 2 {
   147  			return errors.New("submit requires two arguments: namespace and blobData")
   148  		}
   149  
   150  		return nil
   151  	},
   152  	Short: "Submit the blob(s) at the given namespace(s).\n" +
   153  		"User can use namespace and blobData as argument for single blob submission \n" +
   154  		"or use --input-file flag with the path to a json file for multiple blobs submission, \n" +
   155  		`where the json file contains: 
   156  
   157  		{
   158  			"Blobs": [
   159  				{
   160  					"namespace": "0x00010203040506070809",
   161  					"blobData": "0x676d"
   162  				},
   163  				{
   164  					"namespace": "0x42690c204d39600fddd3",
   165  					"blobData": "0x676d"
   166  				}
   167  			]
   168  		}` +
   169  		"Note:\n" +
   170  		"* fee and gas limit params will be calculated automatically.\n",
   171  	RunE: func(cmd *cobra.Command, args []string) error {
   172  		client, err := cmdnode.ParseClientFromCtx(cmd.Context())
   173  		if err != nil {
   174  			return err
   175  		}
   176  		defer client.Close()
   177  
   178  		path, err := cmd.Flags().GetString(flagFileInput)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		jsonBlobs := make([]blobJSON, 0)
   184  		// In case of there is a file input, get the namespace and blob from the arguments
   185  		if path != "" {
   186  			paresdBlobs, err := parseSubmitBlobs(path)
   187  			if err != nil {
   188  				return err
   189  			}
   190  
   191  			jsonBlobs = append(jsonBlobs, paresdBlobs...)
   192  		} else {
   193  			jsonBlobs = append(jsonBlobs, blobJSON{Namespace: args[0], BlobData: args[1]})
   194  		}
   195  
   196  		var blobs []*blob.Blob
   197  		var commitments []blob.Commitment
   198  		for _, jsonBlob := range jsonBlobs {
   199  			blob, err := getBlobFromArguments(jsonBlob.Namespace, jsonBlob.BlobData)
   200  			if err != nil {
   201  				return err
   202  			}
   203  			blobs = append(blobs, blob)
   204  			commitments = append(commitments, blob.Commitment)
   205  		}
   206  
   207  		height, err := client.Blob.Submit(
   208  			cmd.Context(),
   209  			blobs,
   210  			blob.GasPrice(gasPrice),
   211  		)
   212  
   213  		response := struct {
   214  			Height      uint64            `json:"height"`
   215  			Commitments []blob.Commitment `json:"commitments"`
   216  		}{
   217  			Height:      height,
   218  			Commitments: commitments,
   219  		}
   220  		return cmdnode.PrintOutput(response, err, nil)
   221  	},
   222  }
   223  
   224  func getBlobFromArguments(namespaceArg, blobArg string) (*blob.Blob, error) {
   225  	namespace, err := cmdnode.ParseV0Namespace(namespaceArg)
   226  	if err != nil {
   227  		return nil, fmt.Errorf("error parsing a namespace:%v", err)
   228  	}
   229  
   230  	parsedBlob, err := blob.NewBlobV0(namespace, []byte(blobArg))
   231  	if err != nil {
   232  		return nil, fmt.Errorf("error creating a blob:%v", err)
   233  	}
   234  
   235  	return parsedBlob, nil
   236  }
   237  
   238  var getProofCmd = &cobra.Command{
   239  	Use:   "get-proof [height] [namespace] [commitment]",
   240  	Args:  cobra.ExactArgs(3),
   241  	Short: "Retrieves the blob in the given namespaces at the given height by commitment and returns its Proof.",
   242  	RunE: func(cmd *cobra.Command, args []string) error {
   243  		client, err := cmdnode.ParseClientFromCtx(cmd.Context())
   244  		if err != nil {
   245  			return err
   246  		}
   247  		defer client.Close()
   248  
   249  		height, err := strconv.ParseUint(args[0], 10, 64)
   250  		if err != nil {
   251  			return fmt.Errorf("error parsing a height:%v", err)
   252  		}
   253  
   254  		namespace, err := cmdnode.ParseV0Namespace(args[1])
   255  		if err != nil {
   256  			return fmt.Errorf("error parsing a namespace:%v", err)
   257  		}
   258  
   259  		commitment, err := base64.StdEncoding.DecodeString(args[2])
   260  		if err != nil {
   261  			return fmt.Errorf("error parsing a commitment:%v", err)
   262  		}
   263  
   264  		proof, err := client.Blob.GetProof(cmd.Context(), height, namespace, commitment)
   265  		return cmdnode.PrintOutput(proof, err, nil)
   266  	},
   267  }
   268  
   269  func formatData(data interface{}) interface{} {
   270  	type tempBlob struct {
   271  		Namespace    []byte `json:"namespace"`
   272  		Data         string `json:"data"`
   273  		ShareVersion uint32 `json:"share_version"`
   274  		Commitment   []byte `json:"commitment"`
   275  	}
   276  
   277  	if reflect.TypeOf(data).Kind() == reflect.Slice {
   278  		blobs := data.([]*blob.Blob)
   279  		result := make([]tempBlob, len(blobs))
   280  		for i, b := range blobs {
   281  			result[i] = tempBlob{
   282  				Namespace:    b.Namespace(),
   283  				Data:         string(b.Data),
   284  				ShareVersion: b.ShareVersion,
   285  				Commitment:   b.Commitment,
   286  			}
   287  		}
   288  		return result
   289  	}
   290  
   291  	b := data.(*blob.Blob)
   292  	return tempBlob{
   293  		Namespace:    b.Namespace(),
   294  		Data:         string(b.Data),
   295  		ShareVersion: b.ShareVersion,
   296  		Commitment:   b.Commitment,
   297  	}
   298  }