作者

  • Jonathan Gimeno (@jgimeno)
  • David Grierson (@senormonito)
  • Alessio Treglia (@alessio)
  • Frojdy Dymylja (@fdymylja)

变更记录

背景

Rosetta API 是由 Coinbase 开发的一套开源规范和工具,用于标准化区块链交互。 通过使用一套用于集成区块链应用的标准 API,它将能够:
  • 让用户更容易与特定区块链交互
  • 让交易所能够快速且轻松地集成新的区块链
  • 让应用开发者能够以显著更低的成本和工作量构建跨区块链应用,例如区块浏览器、钱包和 dApp

决策

很明显,为 Cosmos SDK 增加 Rosetta API 支持将为生态中的所有开发者以及基于 Cosmos SDK 的链带来价值。关键在于如何实现。 所提设计的指导原则如下:
  1. 可扩展性: 应尽可能低风险、低负担地让应用开发者配置网络,以暴露符合 Rosetta API 规范的服务。
  2. 长期支持: 本提案旨在为所有受支持的 Cosmos SDK 发布系列提供支持。
  3. 成本效率: 需要降低将 Rosetta API 规范变更从 master 回移植到 Cosmos SDK 各个稳定分支的成本。
我们将通过以下方式落实这些原则:
  1. 将提供一个 rosetta/lib 包, 用于实现 Rosetta API 的核心功能,尤其包括: a. 类型和接口(Client、OfflineClient…),以将设计与实现细节分离。 b. Server 功能,因为它独立于 Cosmos SDK 版本。 c. Online/OfflineNetwork,它不会被导出,并通过 Client 接口实现 rosetta API,用于查询节点、构建交易等。 d. errors 包,用于扩展 rosetta 错误。
  2. 由于 Cosmos 各发布系列之间存在差异,每个系列都将拥有各自特定的 Client 接口实现。
  3. 应用中启动 API 服务将提供两种方式: a. API 与应用共享同一进程 b. API 专用进程

架构

外部仓库

本节将介绍所提议的外部库,包括服务实现以及定义的类型与接口。

Server

Server 是一个简单的 struct,启动后会监听配置中指定的端口。它的设计目标是在所有仍受支持的 Cosmos SDK 版本中通用。 构造函数如下: func NewServer(settings Settings) (Server, error) 用于构造新服务端的 Settings 如下:
// 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 包使用了 rosetta 类型与自定义类型包装器的混合形式,客户端在执行操作时必须解析并返回这些类型。
接口
每个 SDK 版本在连接方式(rpc、gRPC 等)、查询和构建交易方面都使用不同格式,我们已将这些差异抽象到 Client 接口中。 客户端使用 rosetta 类型,而 Online/OfflineNetwork 负责返回正确解析后的 rosetta 响应和错误。 每个 Cosmos SDK 发布系列都会有自己的 Client 实现。 开发者也可以根据需要实现自定义 Client。
// 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)
}

2. Cosmos SDK 实现

Cosmos SDK 的实现会根据版本负责满足 Client 接口。 在 Stargate、Launchpad 和 0.37 中,我们引入了 rosetta.Msg 的概念;该消息不在共享仓库中,因为 sdk.Msg 类型在不同 Cosmos SDK 版本之间有所不同。 rosetta.Msg 接口如下:
// 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)
}
因此,希望扩展 rosetta 已支持操作集合的开发者,只需为其模块的 sdk.Msg 扩展 ToOperations 和 FromOperations 方法。

3. API 服务调用

如开头所述,应用开发者将有两种方式调用 Rosetta API 服务:
  1. 应用与 API 共享进程
  2. 独立的 API 服务

共享进程(仅限 Stargate)

Rosetta API 服务可以与应用运行在同一个执行进程中。这可通过 app.toml 配置启用;如果未启用 gRPC,则 rosetta 实例将以离线模式启动(仅具备交易构建能力)。

独立 API 服务

客户端应用开发者也可以编写一个新命令,以单独进程的形式启动 Rosetta API 服务端,使用 /server/rosetta 包中包含的 rosetta 命令。该命令的构造取决于 Cosmos SDK 版本。示例可见于 stargate 的 simd,以及其他发布系列中的 contrib/rosetta/simapp。

状态

提案中

影响

积极影响

  • Cosmos SDK 原生提供 Rosetta API 支持。
  • 区块链接口标准化

参考资料


Authors

  • Jonathan Gimeno (@jgimeno)
  • David Grierson (@senormonito)
  • Alessio Treglia (@alessio)
  • Frojdy Dymylja (@fdymylja)

Changelog

Context

Rosetta API is an open-source specification and set of tools developed by Coinbase to standardise blockchain interactions. Through the use of a standard API for integrating blockchain applications it will
  • Be easier for a user to interact with a given blockchain
  • Allow exchanges to integrate new blockchains quickly and easily
  • Enable application developers to build cross-blockchain applications such as block explorers, wallets and dApps at considerably lower cost and effort.

Decision

It is clear that adding Rosetta API support to the Cosmos SDK will bring value to all the developers and Cosmos SDK based chains in the ecosystem. How it is implemented is key. The driving principles of the proposed design are:
  1. Extensibility: it must be as riskless and painless as possible for application developers to set-up network configurations to expose Rosetta API-compliant services.
  2. Long term support: This proposal aims to provide support for all the supported Cosmos SDK release series.
  3. Cost-efficiency: Backporting changes to Rosetta API specifications from master to the various stable branches of Cosmos SDK is a cost that needs to be reduced.
We will achieve these delivering on these principles by the following:
  1. There will be a package rosetta/lib for the implementation of the core Rosetta API features, particularly: 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.

Architecture

The External Repo

As section will describe the proposed external library, including the service implementation, plus the defined types and interfaces.

Server

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 NewServer(settings Settings) (Server, error) Settings, which are used to construct a new server, are the following:
// 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

Package types uses a mixture of rosetta types and custom defined type wrappers, that the client must parse and return while executing operations.
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, while 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 Clients as required.
// 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)
}

2. Cosmos SDK Implementation

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. The rosetta.Msg interface follows:
// 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:
  1. Shared process for both application and API
  2. Standalone API service

Shared Process (Only Stargate)

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

Separate API service

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.

Status

Proposed

Consequences

Positive

  • Out-of-the-box Rosetta API support within Cosmos SDK.
  • Blockchain interface standardisation

References