diff --git a/nodebuilder/blob/cmd/blob.go b/nodebuilder/blob/cmd/blob.go index 7862eaa204..25a102843b 100644 --- a/nodebuilder/blob/cmd/blob.go +++ b/nodebuilder/blob/cmd/blob.go @@ -2,7 +2,9 @@ package cmd import ( "encoding/base64" + "errors" "fmt" + "path/filepath" "reflect" "strconv" @@ -17,6 +19,10 @@ var ( base64Flag bool gasPrice float64 + + // flagFileInput allows the user to provide file path to the json file + // for submitting multiple blobs. + flagFileInput = "input-file" ) func init() { @@ -43,6 +49,8 @@ func init() { "specifies gas price (in utia) for blob submission.\n"+ "Gas price will be set to default (0.002) if no value is passed", ) + + submitCmd.PersistentFlags().String(flagFileInput, "", "Specify the file input") } var Cmd = &cobra.Command{ @@ -119,11 +127,46 @@ var getAllCmd = &cobra.Command{ } var submitCmd = &cobra.Command{ - Use: "submit [namespace] [blobData]", - Args: cobra.ExactArgs(2), - Short: "Submit the blob at the given namespace.\n" + + Use: "submit [namespace] [blobData]", + Args: func(cmd *cobra.Command, args []string) error { + path, err := cmd.Flags().GetString(flagFileInput) + if err != nil { + return err + } + + // If there is a file path input we'll check for the file extension + if path != "" { + if filepath.Ext(path) != ".json" { + return fmt.Errorf("invalid file extension, require json got %s", filepath.Ext(path)) + } + + return nil + } + + if len(args) < 2 { + return errors.New("submit requires two arguments: namespace and blobData") + } + + return nil + }, + Short: "Submit the blob(s) at the given namespace(s).\n" + + "User can use namespace and blobData as argument for single blob submission \n" + + "or use --input-file flag with the path to a json file for multiple blobs submission, \n" + + `where the json file contains: + + { + "Blobs": [ + { + "namespace": "0x00010203040506070809", + "blobData": "0x676d" + }, + { + "namespace": "0x42690c204d39600fddd3", + "blobData": "0x676d" + } + ] + }` + "Note:\n" + - "* only one blob is allowed to submit through the RPC.\n" + "* fee and gas limit params will be calculated automatically.\n", RunE: func(cmd *cobra.Command, args []string) error { client, err := cmdnode.ParseClientFromCtx(cmd.Context()) @@ -132,33 +175,66 @@ var submitCmd = &cobra.Command{ } defer client.Close() - namespace, err := cmdnode.ParseV0Namespace(args[0]) + path, err := cmd.Flags().GetString(flagFileInput) if err != nil { - return fmt.Errorf("error parsing a namespace:%v", err) + return err } - parsedBlob, err := blob.NewBlobV0(namespace, []byte(args[1])) - if err != nil { - return fmt.Errorf("error creating a blob:%v", err) + jsonBlobs := make([]blobJSON, 0) + // In case of there is a file input, get the namespace and blob from the arguments + if path != "" { + paresdBlobs, err := parseSubmitBlobs(path) + if err != nil { + return err + } + + jsonBlobs = append(jsonBlobs, paresdBlobs...) + } else { + jsonBlobs = append(jsonBlobs, blobJSON{Namespace: args[0], BlobData: args[1]}) + } + + var blobs []*blob.Blob + var commitments []blob.Commitment + for _, jsonBlob := range jsonBlobs { + blob, err := getBlobFromArguments(jsonBlob.Namespace, jsonBlob.BlobData) + if err != nil { + return err + } + blobs = append(blobs, blob) + commitments = append(commitments, blob.Commitment) } height, err := client.Blob.Submit( cmd.Context(), - []*blob.Blob{parsedBlob}, + blobs, blob.GasPrice(gasPrice), ) response := struct { - Height uint64 `json:"height"` - Commitment blob.Commitment `json:"commitment"` + Height uint64 `json:"height"` + Commitments []blob.Commitment `json:"commitments"` }{ - Height: height, - Commitment: parsedBlob.Commitment, + Height: height, + Commitments: commitments, } return cmdnode.PrintOutput(response, err, nil) }, } +func getBlobFromArguments(namespaceArg, blobArg string) (*blob.Blob, error) { + namespace, err := cmdnode.ParseV0Namespace(namespaceArg) + if err != nil { + return nil, fmt.Errorf("error parsing a namespace:%v", err) + } + + parsedBlob, err := blob.NewBlobV0(namespace, []byte(blobArg)) + if err != nil { + return nil, fmt.Errorf("error creating a blob:%v", err) + } + + return parsedBlob, nil +} + var getProofCmd = &cobra.Command{ Use: "get-proof [height] [namespace] [commitment]", Args: cobra.ExactArgs(3), diff --git a/nodebuilder/blob/cmd/util.go b/nodebuilder/blob/cmd/util.go new file mode 100644 index 0000000000..a33b9c1a84 --- /dev/null +++ b/nodebuilder/blob/cmd/util.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "encoding/json" + "os" +) + +// Define the raw content from the file input. +type blobs struct { + Blobs []blobJSON +} + +type blobJSON struct { + Namespace string + BlobData string +} + +func parseSubmitBlobs(path string) ([]blobJSON, error) { + var rawBlobs blobs + + content, err := os.ReadFile(path) + if err != nil { + return []blobJSON{}, err + } + + err = json.Unmarshal(content, &rawBlobs) + if err != nil { + return []blobJSON{}, err + } + + return rawBlobs.Blobs, err +}