Rosetta API implementation (#7695)

Ref: #7492

Co-authored-by: Jonathan Gimeno <jgimeno@gmail.com>
Co-authored-by: Alessio Treglia <alessio@tendermint.com>
Co-authored-by: Frojdi Dymylja <33157909+fdymylja@users.noreply.github.com>
Co-authored-by: Robert Zaremba <robert@zaremba.ch>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
SaReN
2021-01-21 09:33:02 +00:00
committed by GitHub
co-authored by Jonathan Gimeno Alessio Treglia Frojdi Dymylja Robert Zaremba Federico Kunze
parent d226254578
commit 57f5e96570
46 changed files with 2887 additions and 203 deletions
+114 -178
View File
@@ -5,6 +5,7 @@
- Jonathan Gimeno (@jgimeno)
- David Grierson (@senormonito)
- Alessio Treglia (@alessio)
- Frojdy Dymylja (@fdymylja)
## Context
@@ -35,9 +36,11 @@ We will achieve these delivering on these principles by the following:
1. There will be an external repo called [cosmos-rosetta-gateway](https://github.com/tendermint/cosmos-rosetta-gateway)
for the implementation of the core Rosetta API features, particularly:
a. The types and interfaces. This separates design from implementation detail.
b. Some core implementations: specifically, the `Service` functionality as this is independent of the Cosmos SDK version.
2. Due to differences between the Cosmos release series, each series will have its own specific API implementations of `Network` struct and `Adapter` interface.
a. The types and interfaces (`Client`, `OfflineClient`...), this separates design from implementation detail.
b. The `Server` functionality as this is independent of the Cosmos SDK version.
c. The `Online/OfflineNetwork`, which is not exported, and implements the rosetta API using the `Client` interface to query the node, build tx and so on.
d. The `errors` package to extend rosetta errors.
2. Due to differences between the Cosmos release series, each series will have its own specific implementation of `Client` interface.
3. There will be two options for starting an API service in applications:
a. API shares the application process
b. API-specific process.
@@ -49,143 +52,130 @@ We will achieve these delivering on these principles by the following:
As section will describe the proposed external library, including the service implementation, plus the defined types and interfaces.
#### Service
#### Server
`Service` is a simple `struct` that is started and listens to the port specified in the options. This is meant to be used across all the Cosmos SDK versions that are actively supported.
`Server` is a simple `struct` that is started and listens to the port specified in the settings. This is meant to be used across all the Cosmos SDK versions that are actively supported.
The constructor follows:
`func New(options Options, network Network) (*Service, error)`
`func NewServer(settings Settings) (Server, error)`
`Settings`, which are used to construct a new server, are the following:
```go
// Settings define the rosetta server settings
type Settings struct {
// Network contains the information regarding the network
Network *types.NetworkIdentifier
// Client is the online API handler
Client crgtypes.Client
// Listen is the address the handler will listen at
Listen string
// Offline defines if the rosetta service should be exposed in offline mode
Offline bool
// Retries is the number of readiness checks that will be attempted when instantiating the handler
// valid only for online API
Retries int
// RetryWait is the time that will be waited between retries
RetryWait time.Duration
}
```
#### Types
`Service` accepts an `Options` `struct` that holds service configuration values, such as the port the service would be listening to:
Package types uses a mixture of rosetta types and custom defined type wrappers, that the client must parse and return while executing operations.
```golang
type Options struct {
ListenAddress string
##### Interfaces
Every SDK version uses a different format to connect (rpc, gRPC, etc), query and build transactions, we have abstracted this in what is the `Client` interface.
The client uses rosetta types, whilst the `Online/OfflineNetwork` takes care of returning correctly parsed rosetta responses and errors.
Each Cosmos SDK release series will have their own `Client` implementations.
Developers can implement their own custom `Client`s as required.
```go
// Client defines the API the client implementation should provide.
type Client interface {
// Needed if the client needs to perform some action before connecting.
Bootstrap() error
// Ready checks if the servicer constraints for queries are satisfied
// for example the node might still not be ready, it's useful in process
// when the rosetta instance might come up before the node itself
// the servicer must return nil if the node is ready
Ready() error
// Data API
// Balances fetches the balance of the given address
// if height is not nil, then the balance will be displayed
// at the provided height, otherwise last block balance will be returned
Balances(ctx context.Context, addr string, height *int64) ([]*types.Amount, error)
// BlockByHashAlt gets a block and its transaction at the provided height
BlockByHash(ctx context.Context, hash string) (BlockResponse, error)
// BlockByHeightAlt gets a block given its height, if height is nil then last block is returned
BlockByHeight(ctx context.Context, height *int64) (BlockResponse, error)
// BlockTransactionsByHash gets the block, parent block and transactions
// given the block hash.
BlockTransactionsByHash(ctx context.Context, hash string) (BlockTransactionsResponse, error)
// BlockTransactionsByHash gets the block, parent block and transactions
// given the block hash.
BlockTransactionsByHeight(ctx context.Context, height *int64) (BlockTransactionsResponse, error)
// GetTx gets a transaction given its hash
GetTx(ctx context.Context, hash string) (*types.Transaction, error)
// GetUnconfirmedTx gets an unconfirmed Tx given its hash
// NOTE(fdymylja): NOT IMPLEMENTED YET!
GetUnconfirmedTx(ctx context.Context, hash string) (*types.Transaction, error)
// Mempool returns the list of the current non confirmed transactions
Mempool(ctx context.Context) ([]*types.TransactionIdentifier, error)
// Peers gets the peers currently connected to the node
Peers(ctx context.Context) ([]*types.Peer, error)
// Status returns the node status, such as sync data, version etc
Status(ctx context.Context) (*types.SyncStatus, error)
// Construction API
// PostTx posts txBytes to the node and returns the transaction identifier plus metadata related
// to the transaction itself.
PostTx(txBytes []byte) (res *types.TransactionIdentifier, meta map[string]interface{}, err error)
// ConstructionMetadataFromOptions
ConstructionMetadataFromOptions(ctx context.Context, options map[string]interface{}) (meta map[string]interface{}, err error)
OfflineClient
}
// OfflineClient defines the functionalities supported without having access to the node
type OfflineClient interface {
NetworkInformationProvider
// SignedTx returns the signed transaction given the tx bytes (msgs) plus the signatures
SignedTx(ctx context.Context, txBytes []byte, sigs []*types.Signature) (signedTxBytes []byte, err error)
// TxOperationsAndSignersAccountIdentifiers returns the operations related to a transaction and the account
// identifiers if the transaction is signed
TxOperationsAndSignersAccountIdentifiers(signed bool, hexBytes []byte) (ops []*types.Operation, signers []*types.AccountIdentifier, err error)
// ConstructionPayload returns the construction payload given the request
ConstructionPayload(ctx context.Context, req *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error)
// PreprocessOperationsToOptions returns the options given the preprocess operations
PreprocessOperationsToOptions(ctx context.Context, req *types.ConstructionPreprocessRequest) (options map[string]interface{}, err error)
// AccountIdentifierFromPublicKey returns the account identifier given the public key
AccountIdentifierFromPublicKey(pubKey *types.PublicKey) (*types.AccountIdentifier, error)
}
```
The `Network` type holds network-specific properties (i.e. configuration values) and adapters. Pre-configured concrete types will be available for each Cosmos SDK release. Applications can also create their own custom types.
```golang
type Network struct {
Properties rosetta.NetworkProperties
Adapter rosetta.Adapter
}
```
A `NetworkProperties` `struct` comprises basic values that are required by a Rosetta API `Service`:
```golang
type NetworkProperties struct {
// Mandatory properties
Blockchain string
Network string
SupportedOperations []string
}
```
Rosetta API services use `Blockchain` and `Network` as identifiers, e.g. the developers of _gaia_, the application that powers the Cosmos Hub, may want to set those to `Cosmos Hub` and `cosmos-hub-3` respectively.
`SupportedOperations` contains the transaction types that are supported by the library. At the present time,
only `cosmos-sdk/MsgSend` is supported in Launchpad. Additional operations will be added in due time.
For Launchpad we will map the amino type name to the operation supported, in Stargate we will use the protoc one.
#### Interfaces
Every SDK version uses a different format to connect (rpc, gRpc, etc), we have abstracted this in what is called the
Adapter. This is an interface that defines the methods an adapter implementation must provide in order to be used
in the `Network` interface.
Each Cosmos SDK release series will have their own Adapter implementations.
Developers can implement their own custom adapters as required.
```golang
type Adapter interface {
DataAPI
ConstructionAPI
}
type DataAPI interface {
server.NetworkAPIServicer
server.AccountAPIServicer
server.MempoolAPIServicer
server.BlockAPIServicer
server.ConstructionAPIServicer
}
type ConstructionAPI interface {
server.ConstructionAPIServicer
}
```
Example in pseudo-code of an Adapter interface:
```golang
type SomeAdapter struct {
cosmosClient client
tendermintClient client
}
func NewSomeAdapter(cosmosClient client, tendermintClient client) rosetta.Adapter {
return &SomeAdapter{cosmosClient: cosmosClient, tendermintClient: tendermintClient}
}
func (s SomeAdapter) NetworkStatus(ctx context.Context, request *types.NetworkRequest) (*types.NetworkStatusResponse, *types.Error) {
resp := s.tendermintClient.CallStatus()
// ... Parse status Response
// build NetworkStatusResponse
return networkStatusResp, nil
}
func (s SomeAdapter) AccountBalance(ctx context.Context, request *types.AccountBalanceRequest) (*types.AccountBalanceResponse, *types.Error) {
resp := s.cosmosClient.Account()
// ... Parse cosmos specific account response
// build AccountBalanceResponse
return AccountBalanceResponse, nil
}
// And we repeat for all the methods defined in the interface.
```
For further information about the `Servicer` interfaces, please refer to the [Coinbase's rosetta-sdk-go's documentation](https://pkg.go.dev/github.com/coinbase/rosetta-sdk-go@v0.5.9/server).
### 2. Cosmos SDK Implementation
As described, each Cosmos SDK release series will have version specific implementations of `Network` and `Adapter`, as
well as a `NewNetwork` constructor.
The cosmos sdk implementation, based on version, takes care of satisfying the `Client` interface.
In Stargate, Launchpad and 0.37, we have introduced the concept of rosetta.Msg, this message is not in the shared repository as the sdk.Msg type differs between cosmos-sdk versions.
Due to separation of interface and implementation, application developers have the option to override as needed,
using this code as reference.
The rosetta.Msg interface follows:
```golang
// NewNetwork returns the default application configuration.
func NewNetwork(options Options) service.Network {
cosmosClient := cosmos.NewClient(fmt.Sprintf("http://%s", options.CosmosEndpoint))
tendermintClient := tendermint.NewClient(fmt.Sprintf("http://%s", options.TendermintEndpoint))
return service.Network{
Properties: rosetta.NetworkProperties{
Blockchain: options.Blockchain,
Network: options.Network,
SupportedOperations: []string{OperationTransfer},
},
Adapter: newAdapter(
cosmosClient,
tendermintClient,
properties{
Blockchain: options.Blockchain,
Network: options.Network,
OfflineMode: options.OfflineMode,
},
),
}
```go
// Msg represents a cosmos-sdk message that can be converted from and to a rosetta operation.
type Msg interface {
sdk.Msg
ToOperations(withStatus, hasError bool) []*types.Operation
FromOperations(ops []*types.Operation) (sdk.Msg, error)
}
```
Hence developers who want to extend the rosetta set of supported operations just need to extend their module's sdk.Msgs with the `ToOperations` and `FromOperations` methods.
### 3. API service invocation
As stated at the start, application developers will have two methods for invocation of the Rosetta API service:
@@ -195,67 +185,13 @@ As stated at the start, application developers will have two methods for invocat
#### Shared Process (Only Stargate)
Rosetta API service could run within the same execution process as the application. New configuration option and
command line flags would be provided to support this:
Rosetta API service could run within the same execution process as the application. This would be enabled via app.toml settings, and if gRPC is not enabled the rosetta instance would be spinned in offline mode (tx building capabilities only).
```golang
if config.Rosetta.Enable {
....
get contecxt, flags, etc
...
h, err := service.New(
service.Options{ListenAddress: config.Rosetta.ListenAddress},
rosetta.NewNetwork(cdc, options),
)
if err != nil {
}
...
go func() {
if err := h.Start(config); err != nil {
errCh <- err
}
}()
}
```
#### Separate API service
Client application developers can write a new command to launch a Rosetta API server as a separate process too:
Client application developers can write a new command to launch a Rosetta API server as a separate process too, using the rosetta command contained in the `/server/rosetta` package. Construction of the command depends on cosmos sdk version. Examples can be found inside `simd` for stargate, and `contrib/rosetta/simapp` for other release series.
```golang
func RosettaCommand(cdc *codec.Codec) *cobra.Command {
...
cmd := &cobra.Command{
Use: "rosetta",
....
RunE: func(cmd *cobra.Command, args []string) error {
....
get contecxt, flags, etc
...
h, err := service.New(
service.Options{Endpoint: endpoint},
rosetta.NewNetwork(cdc, options),
)
if err != nil {
return err
}
...
h.Start()
}
}
...
}
```
## Status
+1
View File
@@ -13,3 +13,4 @@ This folder contains documentation on how to run a node and interact with it.
1. [Interacting with a Node](./interact-node.md)
1. [Generating, Signing and Broadcasting Transactions](./txs.md)
1. [Cosmos Upgrade Manager](./cosmovisor.md)
1. [Rosetta API](./rosetta.md)
+83
View File
@@ -0,0 +1,83 @@
# Rosetta
Package rosetta implements the rosetta API for the current cosmos sdk release series.
The client satisfies [cosmos-rosetta-gateway](https://github.com/tendermint/cosmos-rosetta-gateway) `Client` interface implementation.
## Extension
There are two ways in which you can customize and extend the implementation with your custom settings.
### Message extension
In order to make an `sdk.Msg` understandable by rosetta the only thing which is required is adding the methods to your message that satisfy the `rosetta.Msg` interface.
Examples on how to do so can be found in the staking types such as `MsgDelegate`, or in bank types such as `MsgSend`.
### Client interface override
In case more customization is required, it's possible to embed the Client type and override the methods which require customizations.
Example:
```go
package custom_client
import (
"context"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/cosmos/cosmos-sdk/server/rosetta"
)
// CustomClient embeds the standard cosmos client
// which means that it implements the cosmos-rosetta-gateway Client
// interface while at the same time allowing to customize certain methods
type CustomClient struct {
*rosetta.Client
}
func (c *CustomClient) ConstructionPayload(_ context.Context, request *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) {
// provide custom signature bytes
panic("implement me")
}
```
### Error extension
Since rosetta requires to provide 'returned' errors to network options. In order to declare a new rosetta error, we use the `errors` package in cosmos-rosetta-gateway.
Example:
```go
package custom_errors
import crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors"
var customErrRetriable = true
var CustomError = crgerrs.RegisterError(100, "custom message", customErrRetriable, "description")
```
Note: errors must be registered before cosmos-rosetta-gateway's `Server`.`Start` method is called. Otherwise the registration will be ignored. Errors with same code will be ignored too.
## Integration in app.go
To integrate rosetta as a command in your application, in app.go, in your root command simply use the `server.RosettaCommand` method.
Example:
```go
package app
import (
"github.com/cosmos/cosmos-sdk/server"
"github.com/spf13/cobra"
)
func buildAppCommand(rootCmd *cobra.Command) {
// more app.go init stuff
// ...
// add rosetta command
rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler))
}
```
A full implementation example can be found in `simapp` package.
NOTE: when using a customized client, the command cannot be used as the constructors required **may** differ, so it's required to create a new one. We intend to provide a way to init a customized client without writing extra code in the future.