Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd): use rpc client instead of http.Request #2521

Merged
merged 13 commits into from
Aug 7, 2023
236 changes: 236 additions & 0 deletions cmd/celestia/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package main

import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"strings"

"github.com/spf13/cobra"

"github.com/celestiaorg/celestia-node/api/rpc/client"
"github.com/celestiaorg/celestia-node/blob"
"github.com/celestiaorg/celestia-node/share"
)

type response struct {
Result interface{} `json:"result"`
Error string `json:"error,omitempty"`
}
Wondertan marked this conversation as resolved.
Show resolved Hide resolved

var rpcClient *client.Client
Wondertan marked this conversation as resolved.
Show resolved Hide resolved

var blobCmd = &cobra.Command{
Use: "blob [command]",
Short: "Allows to interact with the Blob Service via JSON-RPC",
Args: cobra.NoArgs,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
client, err := getRPCClient(cmd.Context())
if err != nil {
return err
}

rpcClient = client
return nil
},
PersistentPostRun: func(_ *cobra.Command, _ []string) {
rpcClient.Close()
},
}

var getCmd = &cobra.Command{
Use: "Get [params]",
Wondertan marked this conversation as resolved.
Show resolved Hide resolved
Args: cobra.ExactArgs(3),
Short: "Returns the blob for the given namespace by commitment at a particular height.",
Run: func(cmd *cobra.Command, args []string) {
num, err := strconv.ParseUint(args[0], 10, 64)
Wondertan marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
fmt.Fprintln(os.Stderr,
fmt.Errorf("error parsing height: uint64 could not be parsed"),
)
os.Exit(1)
Wondertan marked this conversation as resolved.
Show resolved Hide resolved
}

// 2. Namespace
namespace, err := parseV0Namespace(args[1])
if err != nil {
fmt.Fprintln(os.Stderr,
fmt.Errorf("error parsing namespace: %v", err),
)
os.Exit(1)
}

// 3: Commitment
commitment, err := base64.StdEncoding.DecodeString(args[2])
if err != nil {
fmt.Fprintln(os.Stderr,
errors.New("error decoding commitment: base64 string could not be decoded"),
)
os.Exit(1)
}

blob, err := rpcClient.Blob.Get(cmd.Context(), num, namespace, commitment)
if err == nil {
if data, decodeErr := tryDecode(blob.Data); decodeErr == nil {
blob.Data = data
}
}

output := prepareOutput(blob, err)
fmt.Fprintln(os.Stdout, string(output))
},
}

var getAllCmd = &cobra.Command{
Use: "GetAll [params]",
Args: cobra.ExactArgs(2),
Short: "Returns all blobs for the given namespace at a particular height.",
Run: func(cmd *cobra.Command, args []string) {
num, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
fmt.Fprintln(os.Stderr,
fmt.Errorf("error parsing height: uint64 could not be parsed"),
)
os.Exit(1)
}

// 2. Namespace
namespace, err := parseV0Namespace(args[1])
if err != nil {
fmt.Fprintln(os.Stderr,
fmt.Errorf("error parsing namespace: %v", err),
)
os.Exit(1)
}

blobs, err := rpcClient.Blob.GetAll(cmd.Context(), num, []share.Namespace{namespace})
if err == nil {
for _, b := range blobs {
if data, err := tryDecode(b.Data); err == nil {
b.Data = data
}
}
}

output := prepareOutput(blobs, err)
fmt.Fprintln(os.Stdout, string(output))
},
}

var submitCmd = &cobra.Command{
Use: "Submit [params]",
Args: cobra.ExactArgs(2),
Short: "Submit the blob at the given namespace. Note: only one blob is allowed to submit through the RPC.",
Run: func(cmd *cobra.Command, args []string) {
// 1. Namespace
namespace, err := parseV0Namespace(args[0])
if err != nil {
fmt.Fprintln(os.Stderr,
fmt.Errorf("error parsing namespace: %v", err),
)
os.Exit(1)
}

// 2. Blob data
var blobData []byte
switch {
case strings.HasPrefix(args[1], "0x"):
decoded, err := hex.DecodeString(args[1][2:])
if err != nil {
fmt.Fprintln(os.Stderr, errors.New("error decoding blob: hex string could not be decoded"))
os.Exit(1)
}
blobData = decoded
case strings.HasPrefix(args[1], "\""):
// user input an utf string that needs to be encoded to base64
src := []byte(args[1])
blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src)))
base64.StdEncoding.Encode(blobData, []byte(args[1]))
Wondertan marked this conversation as resolved.
Show resolved Hide resolved
default:
// otherwise, we assume the user has already encoded their input to base64
blobData, err = base64.StdEncoding.DecodeString(args[1])
if err != nil {
fmt.Fprintln(os.Stderr, errors.New("error decoding blob data: base64 string could not be decoded"))
os.Exit(1)
}
}
Wondertan marked this conversation as resolved.
Show resolved Hide resolved

parsedBlob, err := blob.NewBlobV0(namespace, blobData)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("error creating blob: %v", err))
os.Exit(1)
}

height, err := rpcClient.Blob.Submit(cmd.Context(), []*blob.Blob{parsedBlob})
output := prepareOutput(height, err)

fmt.Fprintln(os.Stdout, string(output))
},
}

var getProofCmd = &cobra.Command{
Use: "GetProof [params]",
Args: cobra.ExactArgs(3),
Short: "Retrieves the blob in the given namespaces at the given height by commitment and returns its Proof.",
Run: func(cmd *cobra.Command, args []string) {
// 1. Height
num, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("error parsing height: uint64 could not be parsed"))
os.Exit(1)
}

// 2. NamespaceID
namespace, err := parseV0Namespace(args[1])
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Errorf("error parsing namespace: %v", err))
os.Exit(1)
}

// 3: Commitment
commitment, err := base64.StdEncoding.DecodeString(args[2])
if err != nil {
fmt.Fprintln(os.Stderr, errors.New("error decoding commitment: base64 string could not be decoded"))
}

proof, err := rpcClient.Blob.GetProof(cmd.Context(), num, namespace, commitment)
output := prepareOutput(proof, err)
fmt.Fprintln(os.Stdout, string(output))
},
}

func prepareOutput(data interface{}, err error) []byte {
errStr := ""
if err != nil {
errStr = err.Error()
}

bytes, err := json.MarshalIndent(response{
Result: data,
Error: errStr,
}, "", " ")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return bytes
}

// Golang marshals `[]byte` as a base64-encoded string, so without additional encoding data will
// not match the expectations. We need this functionality in order to get the original data.
func tryDecode(data []byte) ([]byte, error) {
return base64.StdEncoding.DecodeString(string(data))
}

func getRPCClient(ctx context.Context) (*client.Client, error) {
if authTokenFlag == "" {
authTokenFlag = os.Getenv(authEnvKey)
}

return client.NewClient(ctx, requestURL, authTokenFlag)
}
113 changes: 2 additions & 111 deletions cmd/celestia/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/spf13/cobra"

"github.com/celestiaorg/celestia-node/api/rpc/client"
"github.com/celestiaorg/celestia-node/blob"
"github.com/celestiaorg/celestia-node/share"
"github.com/celestiaorg/celestia-node/state"
)
Expand Down Expand Up @@ -61,6 +60,8 @@ func init() {
false,
"Print JSON-RPC request along with the response",
)
blobCmd.AddCommand(getCmd, getAllCmd, submitCmd, getProofCmd)
Wondertan marked this conversation as resolved.
Show resolved Hide resolved
rpcCmd.AddCommand(blobCmd)
vgonkivs marked this conversation as resolved.
Show resolved Hide resolved
rootCmd.AddCommand(rpcCmd)
}

Expand Down Expand Up @@ -115,116 +116,6 @@ func parseParams(method string, params []string) []interface{} {
panic(fmt.Sprintf("Error parsing namespace: %v", err))
}
parsedParams[1] = namespace
case "Submit":
vgonkivs marked this conversation as resolved.
Show resolved Hide resolved
// 1. Namespace
var err error
namespace, err := parseV0Namespace(params[0])
if err != nil {
panic(fmt.Sprintf("Error parsing namespace: %v", err))
}
// 2. Blob data
var blobData []byte
switch {
case strings.HasPrefix(params[1], "0x"):
decoded, err := hex.DecodeString(params[1][2:])
if err != nil {
panic("Error decoding blob: hex string could not be decoded.")
}
blobData = decoded
case strings.HasPrefix(params[1], "\""):
// user input an utf string that needs to be encoded to base64
src := []byte(params[1])
blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src)))
base64.StdEncoding.Encode(blobData, []byte(params[1]))
default:
// otherwise, we assume the user has already encoded their input to base64
blobData, err = base64.StdEncoding.DecodeString(params[1])
if err != nil {
panic("Error decoding blob data: base64 string could not be decoded.")
}
}
parsedBlob, err := blob.NewBlobV0(namespace, blobData)
if err != nil {
panic(fmt.Sprintf("Error creating blob: %v", err))
}
parsedParams[0] = []*blob.Blob{parsedBlob}
// param count doesn't match input length, so cut off nil values
return parsedParams[:1]
case "SubmitPayForBlob":
// 1. Fee (state.Int is a string)
parsedParams[0] = params[0]
// 2. GasLimit (uint64)
num, err := strconv.ParseUint(params[1], 10, 64)
if err != nil {
panic("Error parsing gas limit: uint64 could not be parsed.")
}
parsedParams[1] = num
// 3. Namespace
namespace, err := parseV0Namespace(params[2])
if err != nil {
panic(fmt.Sprintf("Error parsing namespace: %v", err))
}
// 4. Blob data
var blobData []byte
switch {
case strings.HasPrefix(params[3], "0x"):
decoded, err := hex.DecodeString(params[3][2:])
if err != nil {
panic("Error decoding blob: hex string could not be decoded.")
}
blobData = decoded
case strings.HasPrefix(params[3], "\""):
// user input an utf string that needs to be encoded to base64
src := []byte(params[1])
blobData = make([]byte, base64.StdEncoding.EncodedLen(len(src)))
base64.StdEncoding.Encode(blobData, []byte(params[3]))
default:
// otherwise, we assume the user has already encoded their input to base64
blobData, err = base64.StdEncoding.DecodeString(params[3])
if err != nil {
panic("Error decoding blob: base64 string could not be decoded.")
}
}
parsedBlob, err := blob.NewBlobV0(namespace, blobData)
if err != nil {
panic(fmt.Sprintf("Error creating blob: %v", err))
}
parsedParams[2] = []*blob.Blob{parsedBlob}
return parsedParams[:3]
case "Get":
// 1. Height
num, err := strconv.ParseUint(params[0], 10, 64)
if err != nil {
panic("Error parsing height: uint64 could not be parsed.")
}
parsedParams[0] = num
// 2. NamespaceID
namespace, err := parseV0Namespace(params[1])
if err != nil {
panic(fmt.Sprintf("Error parsing namespace: %v", err))
}
parsedParams[1] = namespace
// 3: Commitment
commitment, err := base64.StdEncoding.DecodeString(params[2])
if err != nil {
panic("Error decoding commitment: base64 string could not be decoded.")
}
parsedParams[2] = commitment
return parsedParams
case "GetAll": // NOTE: Over the cli, you can only pass one namespace
// 1. Height
num, err := strconv.ParseUint(params[0], 10, 64)
if err != nil {
panic("Error parsing height: uint64 could not be parsed.")
}
parsedParams[0] = num
// 2. Namespace
namespace, err := parseV0Namespace(params[1])
if err != nil {
panic(fmt.Sprintf("Error parsing namespace: %v", err))
}
parsedParams[1] = []share.Namespace{namespace}
return parsedParams
case "QueryDelegation", "QueryUnbonding", "BalanceForAddress":
var err error
parsedParams[0], err = parseAddressFromString(params[0])
Expand Down
Loading