fix: populate oldest_block_identifier and wrap jsonrpc errors (#13832)
## Description Closes: #13830 #13827 - Rosetta `/network/status` response: * populates `oldest_block_identifier` with `EarliestBlockHeight` from rpc syncInfo * tries to populate `genesis_block_identifier` with block of height 1 * if block is not queryable, sets value of `InitialHeight` from genesis doc * if not queryable sets same value as `oldest_block_identifier` - Wrap `github.com/tendermint/tendermint/rpc/jsonrpc/types` errors to rosetta errors - Split services into its own go files (account, block, mempool, network) --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
a926be1c13
commit
b00cc29baa
@ -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.**
|
||||
|
||||
@ -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)
|
||||
@ -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())
|
||||
@ -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
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
@ -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),
|
||||
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)
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
tmtypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
||||
)
|
||||
|
||||
func TestRegisterError(t *testing.T) {
|
||||
@ -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{}
|
||||
|
||||
60
tools/rosetta/lib/internal/service/account.go
Normal file
60
tools/rosetta/lib/internal/service/account.go
Normal file
@ -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
tools/rosetta/lib/internal/service/block.go
Normal file
78
tools/rosetta/lib/internal/service/block.go
Normal file
@ -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
|
||||
}
|
||||
@ -1,182 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Mempool fetches the transactions contained in the mempool
|
||||
func (on OnlineNetwork) Mempool(ctx context.Context, _ *types.NetworkRequest) (*types.MempoolResponse, *types.Error) {
|
||||
txs, err := on.client.Mempool(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
return &types.MempoolResponse{
|
||||
TransactionIdentifiers: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MempoolTransaction fetches a single transaction in the mempool
|
||||
// NOTE: it is not implemented yet
|
||||
func (on OnlineNetwork) MempoolTransaction(ctx context.Context, request *types.MempoolTransactionRequest) (*types.MempoolTransactionResponse, *types.Error) {
|
||||
tx, err := on.client.GetUnconfirmedTx(ctx, request.TransactionIdentifier.Hash)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
return &types.MempoolTransactionResponse{
|
||||
Transaction: tx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (on OnlineNetwork) NetworkList(_ context.Context, _ *types.MetadataRequest) (*types.NetworkListResponse, *types.Error) {
|
||||
return &types.NetworkListResponse{NetworkIdentifiers: []*types.NetworkIdentifier{on.network}}, nil
|
||||
}
|
||||
|
||||
func (on OnlineNetwork) NetworkOptions(_ context.Context, _ *types.NetworkRequest) (*types.NetworkOptionsResponse, *types.Error) {
|
||||
return on.networkOptions, nil
|
||||
}
|
||||
|
||||
func (on OnlineNetwork) NetworkStatus(ctx context.Context, _ *types.NetworkRequest) (*types.NetworkStatusResponse, *types.Error) {
|
||||
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)
|
||||
}
|
||||
|
||||
peers, err := on.client.Peers(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
return &types.NetworkStatusResponse{
|
||||
CurrentBlockIdentifier: block.Block,
|
||||
CurrentBlockTimestamp: block.MillisecondTimestamp,
|
||||
GenesisBlockIdentifier: on.genesisBlockIdentifier,
|
||||
OldestBlockIdentifier: nil,
|
||||
SyncStatus: syncStatus,
|
||||
Peers: peers,
|
||||
}, nil
|
||||
}
|
||||
33
tools/rosetta/lib/internal/service/mempool.go
Normal file
33
tools/rosetta/lib/internal/service/mempool.go
Normal file
@ -0,0 +1,33 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"cosmossdk.io/tools/rosetta/lib/errors"
|
||||
"github.com/coinbase/rosetta-sdk-go/types"
|
||||
)
|
||||
|
||||
// Mempool fetches the transactions contained in the mempool
|
||||
func (on OnlineNetwork) Mempool(ctx context.Context, _ *types.NetworkRequest) (*types.MempoolResponse, *types.Error) {
|
||||
txs, err := on.client.Mempool(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
return &types.MempoolResponse{
|
||||
TransactionIdentifiers: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MempoolTransaction fetches a single transaction in the mempool
|
||||
// NOTE: it is not implemented yet
|
||||
func (on OnlineNetwork) MempoolTransaction(ctx context.Context, request *types.MempoolTransactionRequest) (*types.MempoolTransactionResponse, *types.Error) {
|
||||
tx, err := on.client.GetUnconfirmedTx(ctx, request.TransactionIdentifier.Hash)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
return &types.MempoolTransactionResponse{
|
||||
Transaction: tx,
|
||||
}, nil
|
||||
}
|
||||
55
tools/rosetta/lib/internal/service/network.go
Normal file
55
tools/rosetta/lib/internal/service/network.go
Normal file
@ -0,0 +1,55 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"cosmossdk.io/tools/rosetta/lib/errors"
|
||||
"github.com/coinbase/rosetta-sdk-go/types"
|
||||
)
|
||||
|
||||
func (on OnlineNetwork) NetworkList(_ context.Context, _ *types.MetadataRequest) (*types.NetworkListResponse, *types.Error) {
|
||||
return &types.NetworkListResponse{NetworkIdentifiers: []*types.NetworkIdentifier{on.network}}, nil
|
||||
}
|
||||
|
||||
func (on OnlineNetwork) NetworkOptions(_ context.Context, _ *types.NetworkRequest) (*types.NetworkOptionsResponse, *types.Error) {
|
||||
return on.networkOptions, nil
|
||||
}
|
||||
|
||||
func (on OnlineNetwork) NetworkStatus(ctx context.Context, _ *types.NetworkRequest) (*types.NetworkStatusResponse, *types.Error) {
|
||||
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)
|
||||
}
|
||||
|
||||
oldestBlockIdentifier, err := on.client.OldestBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
genesisBlock, err := on.client.GenesisBlock(ctx)
|
||||
if err != nil {
|
||||
genesisBlock, err = on.client.InitialHeightBlock(ctx)
|
||||
if err != nil {
|
||||
genesisBlock = oldestBlockIdentifier
|
||||
}
|
||||
}
|
||||
|
||||
peers, err := on.client.Peers(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.ToRosetta(err)
|
||||
}
|
||||
|
||||
return &types.NetworkStatusResponse{
|
||||
CurrentBlockIdentifier: block.Block,
|
||||
CurrentBlockTimestamp: block.MillisecondTimestamp,
|
||||
GenesisBlockIdentifier: genesisBlock.Block,
|
||||
OldestBlockIdentifier: oldestBlockIdentifier.Block,
|
||||
SyncStatus: syncStatus,
|
||||
Peers: peers,
|
||||
}, nil
|
||||
}
|
||||
@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/coinbase/rosetta-sdk-go/types"
|
||||
@ -16,7 +15,6 @@ import (
|
||||
// genesisBlockFetchTimeout defines a timeout to fetch the genesis block
|
||||
const (
|
||||
genesisBlockFetchTimeout = 15 * time.Second
|
||||
genesisHashEnv = "GENESIS_HASH"
|
||||
)
|
||||
|
||||
// NewOnlineNetwork builds a single network adapter.
|
||||
@ -25,26 +23,16 @@ func NewOnlineNetwork(network *types.NetworkIdentifier, client crgtypes.Client,
|
||||
ctx, cancel := context.WithTimeout(context.Background(), genesisBlockFetchTimeout)
|
||||
defer cancel()
|
||||
|
||||
var genesisHeight int64 = -1 // to use initial_height in genesis.json
|
||||
block, err := client.BlockByHeight(ctx, &genesisHeight)
|
||||
var genesisHeight int64 = 1 // to get genesis block height
|
||||
genesisBlock, err := client.BlockByHeight(ctx, &genesisHeight)
|
||||
if err != nil {
|
||||
return OnlineNetwork{}, err
|
||||
logger.Error(fmt.Sprintf("Could not get genesis block height. %v", err))
|
||||
}
|
||||
|
||||
// Get genesis hash from ENV. It should be set by an external script since is not possible to get
|
||||
// using tendermint API
|
||||
genesisHash := os.Getenv(genesisHashEnv)
|
||||
if genesisHash == "" {
|
||||
logger.Error(fmt.Sprintf("Genesis hash env '%s' is not properly set!", genesisHashEnv))
|
||||
}
|
||||
|
||||
block.Block.Hash = genesisHash
|
||||
|
||||
return OnlineNetwork{
|
||||
client: client,
|
||||
network: network,
|
||||
networkOptions: networkOptionsFromClient(client, block.Block),
|
||||
genesisBlockIdentifier: block.Block,
|
||||
client: client,
|
||||
network: network,
|
||||
networkOptions: networkOptionsFromClient(client, genesisBlock.Block),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -54,14 +42,6 @@ type OnlineNetwork struct {
|
||||
|
||||
network *types.NetworkIdentifier // identifies the network, it's static
|
||||
networkOptions *types.NetworkOptionsResponse // identifies the network options, it's static
|
||||
|
||||
genesisBlockIdentifier *types.BlockIdentifier // identifies genesis block, it's static
|
||||
}
|
||||
|
||||
// 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, crgerrs.ToRosetta(crgerrs.ErrOffline)
|
||||
}
|
||||
|
||||
// networkOptionsFromClient builds network options given the client
|
||||
|
||||
@ -30,6 +30,13 @@ type Client interface {
|
||||
// when the rosetta instance might come up before the node itself
|
||||
// the servicer must return nil if the node is ready
|
||||
Ready() error
|
||||
// GenesisBlock gets the genesis block of the chain
|
||||
GenesisBlock(ctx context.Context) (BlockResponse, error)
|
||||
// InitialHeightBlock gets block with height InitialHeight
|
||||
// from GenesisDoc by downloading the blob
|
||||
InitialHeightBlock(ctx context.Context) (BlockResponse, error)
|
||||
// OldestBlock gets block with height EarliestBlockHeight from syncStatus
|
||||
OldestBlock(ctx context.Context) (BlockResponse, error)
|
||||
|
||||
// Data API
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user