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 }