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

fix: populate oldest_block_identifier and wrap jsonrpc errors #13832

Merged
merged 10 commits into from
Nov 12, 2022
8 changes: 8 additions & 0 deletions tools/rosetta/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Improvements

* [#13832](https://github.com/cosmos/cosmos-sdk/pull/13832) Correctly populates rosetta's `/network/status` endpoint response. Roseta data api is divided into its own go files (account, block, mempool, network).

### Bug Fixes

* [#13832](https://github.com/cosmos/cosmos-sdk/pull/13832) Wrap tendermint RPC errors to rosetta errors.

## v0.1.0 2022-11-04

**From `v0.1.0` the minimum version of Tendermint is `v0.37+`, due event type changes.**
Expand Down
53 changes: 25 additions & 28 deletions tools/rosetta/client_online.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,31 @@ func (c *Client) Ready() error {
return nil
}

func (c *Client) GenesisBlock(ctx context.Context) (crgtypes.BlockResponse, error) {
var genesisHeight int64 = 1
return c.BlockByHeight(ctx, &genesisHeight)
}

func (c *Client) InitialHeightBlock(ctx context.Context) (crgtypes.BlockResponse, error) {
genesisChunk, err := c.tmRPC.GenesisChunked(ctx, 0)
if err != nil {
return crgtypes.BlockResponse{}, err
}
heightNum, err := extractInitialHeightFromGenesisChunk(genesisChunk.Data)
if err != nil {
return crgtypes.BlockResponse{}, err
}
return c.BlockByHeight(ctx, &heightNum)
}

func (c *Client) OldestBlock(ctx context.Context) (crgtypes.BlockResponse, error) {
status, err := c.tmRPC.Status(ctx)
if err != nil {
return crgtypes.BlockResponse{}, err
}
return c.BlockByHeight(ctx, &status.SyncInfo.EarliestBlockHeight)
}

func (c *Client) accountInfo(ctx context.Context, addr string, height *int64) (*SignerData, error) {
if height != nil {
strHeight := strconv.FormatInt(*height, 10)
Expand Down Expand Up @@ -200,11 +225,6 @@ func (c *Client) BlockByHash(ctx context.Context, hash string) (crgtypes.BlockRe
}

func (c *Client) BlockByHeight(ctx context.Context, height *int64) (crgtypes.BlockResponse, error) {
height, err := c.getHeight(ctx, height)

if err != nil {
return crgtypes.BlockResponse{}, crgerrs.WrapError(crgerrs.ErrBadGateway, err.Error())
}
block, err := c.tmRPC.Block(ctx, height)
if err != nil {
return crgtypes.BlockResponse{}, crgerrs.WrapError(crgerrs.ErrInternal, err.Error())
Expand All @@ -224,10 +244,6 @@ func (c *Client) BlockTransactionsByHash(ctx context.Context, hash string) (crgt
}

func (c *Client) BlockTransactionsByHeight(ctx context.Context, height *int64) (crgtypes.BlockTransactionsResponse, error) {
height, err := c.getHeight(ctx, height)
if err != nil {
return crgtypes.BlockTransactionsResponse{}, crgerrs.WrapError(crgerrs.ErrBadGateway, err.Error())
}
blockTxResp, err := c.blockTxs(ctx, height)
if err != nil {
return crgtypes.BlockTransactionsResponse{}, err
Expand Down Expand Up @@ -535,25 +551,6 @@ func (c *Client) blockTxs(ctx context.Context, height *int64) (crgtypes.BlockTra
}, nil
}

func (c *Client) getHeight(ctx context.Context, height *int64) (realHeight *int64, err error) {
if height != nil && *height == -1 {
genesisChunk, err := c.tmRPC.GenesisChunked(ctx, 0)
if err != nil {
return nil, err
}

heightNum, err := extractInitialHeightFromGenesisChunk(genesisChunk.Data)
if err != nil {
return nil, err
}

realHeight = &heightNum
} else {
realHeight = height
}
return
}

var initialHeightRE = regexp.MustCompile(`"initial_height":"(\d+)"`)

func extractInitialHeightFromGenesisChunk(genesisChunk string) (int64, error) {
Expand Down
16 changes: 16 additions & 0 deletions tools/rosetta/lib/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
grpcstatus "google.golang.org/grpc/status"

"github.com/coinbase/rosetta-sdk-go/types"
tmtypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)

// ListErrors lists all the registered errors
Expand Down Expand Up @@ -70,11 +71,26 @@ func ToRosetta(err error) *types.Error {
// if it's null or not known
rosErr, ok := err.(*Error)
if rosErr == nil || !ok {
tmErr, ok := err.(*tmtypes.RPCError)
if tmErr != nil && ok {
return fromTendermintToRosettaError(tmErr).rosErr
}
return ToRosetta(WrapError(ErrUnknown, ErrUnknown.Error()))
}
return rosErr.rosErr
}

// fromTendermintToRosettaError converts a tendermint jsonrpc error to rosetta error
func fromTendermintToRosettaError(err *tmtypes.RPCError) *Error {
return &Error{rosErr: &types.Error{
Code: int32(err.Code),

Check failure

Code scanning / gosec

Potential integer overflow by integer type conversion

Potential integer overflow by integer type conversion
Message: err.Message,
Details: map[string]interface{}{
"info": err.Data,
},
}}
}

// FromGRPCToRosettaError converts a gRPC error to rosetta error
func FromGRPCToRosettaError(err error) *Error {
status, ok := grpcstatus.FromError(err)
Expand Down
5 changes: 5 additions & 0 deletions tools/rosetta/lib/errors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
tmtypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)

func TestRegisterError(t *testing.T) {
Expand Down Expand Up @@ -55,6 +56,10 @@ func TestToRosetta(t *testing.T) {
assert.NotNil(t, ToRosetta(error))
// wrong type
assert.NotNil(t, ToRosetta(&MyError{}))

tmErr := &tmtypes.RPCError{}
// RpcError case
assert.NotNil(t, ToRosetta(tmErr))
}

type MyError struct{}
Expand Down
60 changes: 60 additions & 0 deletions tools/rosetta/lib/internal/service/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package service

import (
"context"

"cosmossdk.io/tools/rosetta/lib/errors"
crgtypes "cosmossdk.io/tools/rosetta/lib/types"
"github.com/coinbase/rosetta-sdk-go/types"
)

// AccountBalance retrieves the account balance of an address
// rosetta requires us to fetch the block information too
func (on OnlineNetwork) AccountBalance(ctx context.Context, request *types.AccountBalanceRequest) (*types.AccountBalanceResponse, *types.Error) {
var (
height int64
block crgtypes.BlockResponse
err error
)

switch {
case request.BlockIdentifier == nil:
syncStatus, err := on.client.Status(ctx)
if err != nil {
return nil, errors.ToRosetta(err)
}
block, err = on.client.BlockByHeight(ctx, syncStatus.CurrentIndex)
if err != nil {
return nil, errors.ToRosetta(err)
}
case request.BlockIdentifier.Hash != nil:
block, err = on.client.BlockByHash(ctx, *request.BlockIdentifier.Hash)
if err != nil {
return nil, errors.ToRosetta(err)
}
height = block.Block.Index
case request.BlockIdentifier.Index != nil:
height = *request.BlockIdentifier.Index
block, err = on.client.BlockByHeight(ctx, &height)
if err != nil {
return nil, errors.ToRosetta(err)
}
}

accountCoins, err := on.client.Balances(ctx, request.AccountIdentifier.Address, &height)
if err != nil {
return nil, errors.ToRosetta(err)
}

return &types.AccountBalanceResponse{
BlockIdentifier: block.Block,
Balances: accountCoins,
Metadata: nil,
}, nil
}

// AccountsCoins - relevant only for UTXO based chain
// see https://www.rosetta-api.org/docs/AccountApi.html#accountcoins
func (o OnlineNetwork) AccountCoins(_ context.Context, _ *types.AccountCoinsRequest) (*types.AccountCoinsResponse, *types.Error) {
return nil, errors.ToRosetta(errors.ErrOffline)
}
78 changes: 78 additions & 0 deletions tools/rosetta/lib/internal/service/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package service

import (
"context"

"cosmossdk.io/tools/rosetta/lib/errors"
crgtypes "cosmossdk.io/tools/rosetta/lib/types"
"github.com/coinbase/rosetta-sdk-go/types"
)

// Block gets the transactions in the given block
func (on OnlineNetwork) Block(ctx context.Context, request *types.BlockRequest) (*types.BlockResponse, *types.Error) {
var (
blockResponse crgtypes.BlockTransactionsResponse
err error
)

// When fetching data by BlockIdentifier, it may be possible to only specify the index or hash.
// If neither property is specified, it is assumed that the client is making a request at the current block.
switch {
case request.BlockIdentifier == nil: // unlike AccountBalance(), BlockIdentifer is mandatory by spec 1.4.10.
err := errors.WrapError(errors.ErrBadArgument, "block identifier needs to be specified")
return nil, errors.ToRosetta(err)

case request.BlockIdentifier.Hash != nil:
blockResponse, err = on.client.BlockTransactionsByHash(ctx, *request.BlockIdentifier.Hash)
if err != nil {
return nil, errors.ToRosetta(err)
}
case request.BlockIdentifier.Index != nil:
blockResponse, err = on.client.BlockTransactionsByHeight(ctx, request.BlockIdentifier.Index)
if err != nil {
return nil, errors.ToRosetta(err)
}

default:
// both empty
blockResponse, err = on.client.BlockTransactionsByHeight(ctx, nil)
if err != nil {
return nil, errors.ToRosetta(err)
}
}

// Both of index and hash can be specified in reuqest, so make sure they are not mismatching.
if request.BlockIdentifier.Index != nil && *request.BlockIdentifier.Index != blockResponse.Block.Index {
err := errors.WrapError(errors.ErrBadArgument, "mismatching index")
return nil, errors.ToRosetta(err)
}

if request.BlockIdentifier.Hash != nil && *request.BlockIdentifier.Hash != blockResponse.Block.Hash {
err := errors.WrapError(errors.ErrBadArgument, "mismatching hash")
return nil, errors.ToRosetta(err)
}

return &types.BlockResponse{
Block: &types.Block{
BlockIdentifier: blockResponse.Block,
ParentBlockIdentifier: blockResponse.ParentBlock,
Timestamp: blockResponse.MillisecondTimestamp,
Transactions: blockResponse.Transactions,
Metadata: nil,
},
OtherTransactions: nil,
}, nil
}

// BlockTransaction gets the given transaction in the specified block, we do not need to check the block itself too
// due to the fact that tendermint achieves instant finality
func (on OnlineNetwork) BlockTransaction(ctx context.Context, request *types.BlockTransactionRequest) (*types.BlockTransactionResponse, *types.Error) {
tx, err := on.client.GetTx(ctx, request.TransactionIdentifier.Hash)
if err != nil {
return nil, errors.ToRosetta(err)
}

return &types.BlockTransactionResponse{
Transaction: tx,
}, nil
}
Loading