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:
Julián Toledano 2022-11-12 11:39:37 +01:00 committed by GitHub
parent a926be1c13
commit b00cc29baa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 293 additions and 236 deletions

View File

@ -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.**

View File

@ -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) {

View File

@ -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)

View File

@ -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{}

View 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)
}

View 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
}

View File

@ -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
}

View 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
}

View 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
}

View File

@ -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

View File

@ -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