Skip to content

Commit

Permalink
feat: embed rosetta library (as is)
Browse files Browse the repository at this point in the history
taken from v1.0.0 release branch of the library: https://github.com/tendermint/cosmos-rosetta-gateway/tree/release/v1.0.0

see:
#9300
  • Loading branch information
noandrea committed May 12, 2021
1 parent b4125d1 commit ff363a4
Show file tree
Hide file tree
Showing 8 changed files with 883 additions and 0 deletions.
152 changes: 152 additions & 0 deletions server/rosetta/lib/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package errors

// errors.go contains all the errors returned by the adapter implementation
// plus some extra utilities to parse those errors

import (
"fmt"

grpccodes "google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"

"github.com/coinbase/rosetta-sdk-go/types"
)

// ListErrors lists all the registered errors
func ListErrors() []*types.Error {
return registry.list()
}

// SealAndListErrors seals the registry and lists its errors
func SealAndListErrors() []*types.Error {
registry.seal()
return registry.list()
}

// Error defines an error that can be converted to a Rosetta API error.
type Error struct {
rosErr *types.Error
}

func (e *Error) Error() string {
if e.rosErr == nil {
return ErrUnknown.Error()
}
return fmt.Sprintf("rosetta: (%d) %s", e.rosErr.Code, e.rosErr.Message)
}

// Is implements errors.Is for *Error, two errors are considered equal
// if their error codes are identical
func (e *Error) Is(err error) bool {
// check if one is nil and the other isn't
if (e == nil && err != nil) || (err == nil && e != nil) {
return false
}
// assert it can be casted
rosErr, ok := err.(*Error)
if !ok {
return false
}
// check that both *Error's are correctly initialized to avoid dereference panics
if (rosErr.rosErr == nil && e.rosErr != nil) || (e.rosErr == nil && rosErr.rosErr != nil) {
return false
}
// messages are equal if their error codes match
return rosErr.rosErr.Code == e.rosErr.Code
}

// WrapError wraps the rosetta error with additional context
func WrapError(err *Error, msg string) *Error {
return &Error{rosErr: &types.Error{
Code: err.rosErr.Code,
Message: err.rosErr.Message,
Description: err.rosErr.Description,
Retriable: err.rosErr.Retriable,
Details: map[string]interface{}{
"info": msg,
},
}}
}

// ToRosetta attempts to converting an error into a rosetta
// error, if the error cannot be converted it will be parsed as unknown
func ToRosetta(err error) *types.Error {
if err == nil {
return nil
}
rosErr, ok := err.(*Error)
if !ok {
return ToRosetta(WrapError(ErrUnknown, err.Error()))
}
return rosErr.rosErr
}

// FromGRPCToRosettaError converts a gRPC error to rosetta error
func FromGRPCToRosettaError(err error) *Error {
status, ok := grpcstatus.FromError(err)
if !ok {
return WrapError(ErrUnknown, err.Error())
}
switch status.Code() {
case grpccodes.NotFound:
return WrapError(ErrNotFound, status.Message())
case grpccodes.FailedPrecondition:
return WrapError(ErrBadArgument, status.Message())
case grpccodes.InvalidArgument:
return WrapError(ErrBadArgument, status.Message())
case grpccodes.Internal:
return WrapError(ErrInternal, status.Message())
default:
return WrapError(ErrUnknown, status.Message())
}
}

func RegisterError(code int32, message string, retryable bool, description string) *Error {
e := &Error{rosErr: &types.Error{
Code: code,
Message: message,
Description: &description,
Retriable: retryable,
Details: nil,
}}
registry.add(e)
return e
}

// Default error list
var (
// ErrUnknown defines an unknown error, if this is returned it means
// the library is ignoring an error
ErrUnknown = RegisterError(0, "unknown", false, "unknown error")
// ErrOffline is returned when there is an attempt to query an endpoint in offline mode
ErrOffline = RegisterError(1, "cannot query endpoint in offline mode", false, "returned when querying an online endpoint in offline mode")
// ErrNetworkNotSupported is returned when there is an attempt to query a network which is not supported
ErrNetworkNotSupported = RegisterError(2, "network is not supported", false, "returned when querying a non supported network")
// ErrCodec is returned when there's an error while marshalling or unmarshalling data
ErrCodec = RegisterError(3, "encode/decode error", true, "returned when there are errors encoding or decoding information to and from the node")
// ErrInvalidOperation is returned when the operation supplied to rosetta is not a valid one
ErrInvalidOperation = RegisterError(4, "invalid operation", false, "returned when the operation is not valid")
// ErrInvalidTransaction is returned when the provided hex bytes of a TX are not valid
ErrInvalidTransaction = RegisterError(5, "invalid transaction", false, "returned when the transaction is invalid")
// ErrInvalidAddress is returned when the byte of the address are bad
ErrInvalidAddress = RegisterError(7, "invalid address", false, "returned when the address is malformed")
// ErrInvalidPubkey is returned when the public key is invalid
ErrInvalidPubkey = RegisterError(8, "invalid pubkey", false, "returned when the public key is invalid")
// ErrInterpreting is returned when there are errors interpreting the data from the node, most likely related to breaking changes, version incompatibilities
ErrInterpreting = RegisterError(9, "error interpreting data from node", false, "returned when there are issues interpreting requests or response from node")
ErrInvalidMemo = RegisterError(11, "invalid memo", false, "returned when the memo is invalid")
// ErrBadArgument is returned when the request is malformed
ErrBadArgument = RegisterError(400, "bad argument", false, "request is malformed")
// ErrNotFound is returned when the required object was not found
// retry is set to true because something that is not found now
// might be found later, example: a TX
ErrNotFound = RegisterError(404, "not found", true, "returned when the node does not find what the client is asking for")
// ErrInternal is returned when the node is experiencing internal errors
ErrInternal = RegisterError(500, "internal error", false, "returned when the node experiences internal errors")
// ErrBadGateway is returned when there are problems interacting with the nodes
ErrBadGateway = RegisterError(502, "bad gateway", true, "return when the node is unreachable")
// ErrNotImplemented is returned when a method is not implemented yet
ErrNotImplemented = RegisterError(14, "not implemented", false, "returned when querying an endpoint which is not implemented")
// ErrUnsupportedCurve is returned when the curve specified is not supported
ErrUnsupportedCurve = RegisterError(15, "unsupported curve, expected secp256k1", false, "returned when using an unsupported crypto curve")
)
48 changes: 48 additions & 0 deletions server/rosetta/lib/errors/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package errors

import (
"fmt"
"os"
"sync"

"github.com/coinbase/rosetta-sdk-go/types"
)

type errorRegistry struct {
mu *sync.RWMutex
sealed bool
errors map[int32]*types.Error
}

func (r errorRegistry) add(err *Error) {
r.mu.Lock()
defer r.mu.Unlock()
if r.sealed {
_, _ = fmt.Fprintln(os.Stderr, "[ROSETTA] WARNING: attempts to register errors after seal will be ignored")
}
if _, ok := r.errors[err.rosErr.Code]; ok {
_, _ = fmt.Fprintln(os.Stderr, "[ROSETTA] WARNING: attempts to register an already registered error will be ignored, code: ", err.rosErr.Code)
}
r.errors[err.rosErr.Code] = err.rosErr
}

func (r errorRegistry) list() []*types.Error {
r.mu.RLock()
defer r.mu.RUnlock()
rosErrs := make([]*types.Error, 0, len(registry.errors))
for _, v := range r.errors {
rosErrs = append(rosErrs, v)
}
return rosErrs
}

func (r errorRegistry) seal() {
r.mu.Lock()
defer r.mu.Unlock()
r.sealed = true
}

var registry = errorRegistry{
mu: new(sync.RWMutex),
errors: make(map[int32]*types.Error),
}
118 changes: 118 additions & 0 deletions server/rosetta/lib/internal/service/construction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package service

import (
"context"
"crypto/sha256"
"encoding/hex"
"strings"

"github.com/coinbase/rosetta-sdk-go/types"
"github.com/cosmos/cosmos-sdk/server/rosetta/lib/errors"
)

func (on OnlineNetwork) ConstructionCombine(ctx context.Context, request *types.ConstructionCombineRequest) (*types.ConstructionCombineResponse, *types.Error) {
txBytes, err := hex.DecodeString(request.UnsignedTransaction)
if err != nil {
return nil, errors.ToRosetta(err)
}

signedTx, err := on.client.SignedTx(ctx, txBytes, request.Signatures)
if err != nil {
return nil, errors.ToRosetta(err)
}

return &types.ConstructionCombineResponse{
SignedTransaction: hex.EncodeToString(signedTx),
}, nil
}

func (on OnlineNetwork) ConstructionDerive(_ context.Context, request *types.ConstructionDeriveRequest) (*types.ConstructionDeriveResponse, *types.Error) {
account, err := on.client.AccountIdentifierFromPublicKey(request.PublicKey)
if err != nil {
return nil, errors.ToRosetta(err)
}
return &types.ConstructionDeriveResponse{
AccountIdentifier: account,
Metadata: nil,
}, nil
}

func (on OnlineNetwork) ConstructionHash(ctx context.Context, request *types.ConstructionHashRequest) (*types.TransactionIdentifierResponse, *types.Error) {
bz, err := hex.DecodeString(request.SignedTransaction)
if err != nil {
return nil, errors.ToRosetta(errors.WrapError(errors.ErrInvalidTransaction, "error decoding tx"))
}

hash := sha256.Sum256(bz)
bzHash := hash[:]
hashString := hex.EncodeToString(bzHash)

return &types.TransactionIdentifierResponse{
TransactionIdentifier: &types.TransactionIdentifier{
Hash: strings.ToUpper(hashString),
},
}, nil
}

func (on OnlineNetwork) ConstructionMetadata(ctx context.Context, request *types.ConstructionMetadataRequest) (*types.ConstructionMetadataResponse, *types.Error) {
metadata, err := on.client.ConstructionMetadataFromOptions(ctx, request.Options)
if err != nil {
return nil, errors.ToRosetta(err)
}

return &types.ConstructionMetadataResponse{
Metadata: metadata,
}, nil
}

func (on OnlineNetwork) ConstructionParse(ctx context.Context, request *types.ConstructionParseRequest) (*types.ConstructionParseResponse, *types.Error) {
txBytes, err := hex.DecodeString(request.Transaction)
if err != nil {
err := errors.WrapError(errors.ErrInvalidTransaction, err.Error())
return nil, errors.ToRosetta(err)
}
ops, signers, err := on.client.TxOperationsAndSignersAccountIdentifiers(request.Signed, txBytes)
if err != nil {
return nil, errors.ToRosetta(err)
}
return &types.ConstructionParseResponse{
Operations: ops,
AccountIdentifierSigners: signers,
Metadata: nil,
}, nil

}

func (on OnlineNetwork) ConstructionPayloads(ctx context.Context, request *types.ConstructionPayloadsRequest) (*types.ConstructionPayloadsResponse, *types.Error) {
payload, err := on.client.ConstructionPayload(ctx, request)
if err != nil {
return nil, errors.ToRosetta(err)
}
return payload, nil
}

func (on OnlineNetwork) ConstructionPreprocess(ctx context.Context, request *types.ConstructionPreprocessRequest) (*types.ConstructionPreprocessResponse, *types.Error) {
options, err := on.client.PreprocessOperationsToOptions(ctx, request)
if err != nil {
return nil, errors.ToRosetta(err)
}

return options, nil
}

func (on OnlineNetwork) ConstructionSubmit(ctx context.Context, request *types.ConstructionSubmitRequest) (*types.TransactionIdentifierResponse, *types.Error) {
txBytes, err := hex.DecodeString(request.SignedTransaction)
if err != nil {
return nil, errors.ToRosetta(err)
}

res, meta, err := on.client.PostTx(txBytes)
if err != nil {
return nil, errors.ToRosetta(err)
}

return &types.TransactionIdentifierResponse{
TransactionIdentifier: res,
Metadata: meta,
}, nil
}
Loading

0 comments on commit ff363a4

Please sign in to comment.