剖析Cosmos SDK(八):核心概念(端点、命令行界面)

煤油灯科技2022-07-20 09:51

gRPC、REST 和 Tendermint端点

所有端点的概述

每个节点为用户提供以下端点来与节点进行互动,每个端点都在不同的端口上提供服务。关于如何配置每个端点的细节,在端点自己的部分提供。

  • gRPC服务器(默认端口:9090)。
  • REST服务器(默认端口:1317)。
  • Tendermint RPC端点(默认端口:26657)。

该节点还公开了其他一些与Cosmos SDK没有直接关系的端点,如Tendermint P2P端点,或Prometheus端点。

gRPC服务器

在Cosmos SDK中,Protobuf是主要的编码库。这带来了一系列基于Protobuf的工具,可以插入到Cosmos SDK中。其中一个工具是gRPC,这是一个现代开源的高性能RPC框架,具有多种语言的良好客户端支持。

每个模块都公开了一个定义状态查询的Protobuf查询服务。用于广播的查询服务和交易服务通过应用程序内部的以下函数与gRPC服务器相连:

		// RegisterGRPCServer registers gRPC services directly with the gRPC
		// server.
		RegisterGRPCServer(grpc.Server)

注意:无法通过gRPC公开任何Protobuf Msg服务端点。在使用gRPC进行广播之前,必须使用CLI或编程方式生成和签署交易。

grpc.Server是一个具体的gRPC服务器,它生成并提供所有gRPC查询请求和广播事务请求。这个服务器可以在~/.simapp/config/app.toml里面配置:

  • grpc.enable = true|false 字段定义了gRPC服务器是否应该被启用。默认为true。
  • grpc.address = {string}字段定义了服务器应该绑定的地址(实际上是端口,因为主机应该保持在0.0.0.0)。默认为0.0.0.0:9090。

~/.simapp是存储节点的配置和数据库的目录。默认情况下,它被设置为~/.{app_name}。

一旦gRPC服务器启动,可以使用gRPC客户端向它发送请求。

Protobuf文档概述了Cosmos SDK提供的所有可用的gRPC端点。

REST服务器

Cosmos SDK通过gRPC-gateway支持REST路由。

所有路由都配置在~/.simapp/config/app.toml中的以下字段:

  • api.enable = true|false 字段定义了REST服务器是否应该被启用。默认为false。
  • api.address = {string}字段定义了服务器应该绑定的地址(实际上是端口,因为主机应该保持在0.0.0.0)。默认为tcp://0.0.0.0:1317。
  • 在~/.simapp/config/app.toml中定义了一些额外的API配置选项。

gRPC-gateway REST路由

如果由于各种原因无法使用gRPC(例如,正在构建一个Web应用程序,浏览器不支持HTTP2,而gRPC是建立在这个基础上的),那么Cosmos SDK通过gRPC-gateway提供REST路由。

gRPC-gateway是一个将gRPC端点公开为REST端点的工具。对于每个在Protobuf查询服务中定义的gRPC端点,Cosmos SDK提供了一个等效的REST。例如,可以通过/cosmos.bank.v1beta1.QueryAllBalances gRPC端点查询余额,或者通过gRPC-gateway "/cosmos/bank/v1beta1/balances/{address}" REST端点:两者都将返回相同的结果。对于Protobuf查询服务中定义的每个RPC方法,相应的REST端点被定义为一个选项:

  // AllBalances queries the balance of all coins for a single account.
  rpc AllBalances(QueryAllBalancesRequest) returns (QueryAllBalancesResponse) {
    option (google.api.http).get = "/cosmos/bank/v1beta1/balances/{address}";

对于应用开发者来说,gRPC-gateway REST路由需要连接到REST服务器,这是通过调用ModuleManager上的RegisterGRPCGatewayRoutes函数完成的。

Swagger

一个Swagger(或OpenAPIv2)规范文件在API服务器上的/swagger路由下公开。Swagger是一个开放的规范,描述了服务器提供的API端点,包括描述、输入参数、返回类型以及关于每个端点的更多信息。

启用/swagger端点可在~/.simapp/config/app.toml内通过api.swagger字段进行配置,该字段默认设置为true。

对于应用程序开发人员,可能想根据自定义模块生成自己的Swagger定义。Cosmos SDK的Swagger生成脚本是一个好的开始。

Tendermint RPC

独立于Cosmos SDK,Tendermint公开了一个RPC服务器。这个RPC服务器可以通过调整~/.simapp/config/config.toml的rpc表下的参数来配置,默认的监听地址是tcp://0.0.0.0:26657。

一些Tendermint RPC端点与Cosmos SDK直接相关:

/abci_query:这个端点将查询应用程序的状态。作为路径参数,可以发送以下字符串:

  • 任何Protobuf完全限定的服务方法,如/cosmos.bank.v1beta1.Query/AllBalances。然后,数据字段应该包括该方法的请求参数,并使用Protobuf编码为字节。
  • /app/simulate:这将模拟一个交易,并返回一些信息,如使用的气体。
  • /app/version:这将返回应用程序的版本。
  • /store/{path}:这将直接查询商店。
  • /p2p/filter/addr/{port}:这将返回一个按地址端口过滤的节点P2P同伴列表。
  • /p2p/filter/id/{id}:这将返回节点的P2P对等体按ID的过滤列表。

/broadcast_tx_{aync,async,commit}:这3个端点将向其他对等点广播。CLI、gRPC和REST公开了一种广播交易的方式,但它们都在后台使用这3个Tendermint RPC。

对照表


Command-Line Interface(命令行界面)

命令示例

没有固定的方法来创建CLI,但Cosmos SDK模块通常使用Cobra库。使用Cobra构建CLI需要定义命令、参数和标志。命令了解用户希望采取的行动,比如tx用于创建一个交易,query用于查询应用程序。每条命令还可以有嵌套的子命令,这对命名特定的交易类型是必要的。用户还可以提供参数(Arguments),如发送硬币的帐号,以及修改命令各个方面的标志(Flags),如汽油价格或广播给哪个节点。

下面是一个用户可能输入的命令的例子,与simapp CLI simd交互以便发送一些代币:

simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --gas auto --gas-prices <gasPrices>

前四个字符串指定命令:

  • 整个应用程序simd的根命令。
  • 子命令tx,它包含所有让用户创建交易的命令。
  • 子命令bank,表示将命令传送到哪个模块(本例中为x/bank模块)。
  • 发送交易的类型。

接下来的两个字符串是参数:用户希望发送的 from_address,收件人的to_address以及他们想要发送的金额。最后,命令的最后几个字符串是可选的标志,表示用户愿意支付多少费用(用执行交易的汽油量和用户提供的汽油价格计算)。

CLI与一个节点互动,处理这个命令。接口本身是在main.go文件中定义的。

建立CLI

main.go文件需要有一个main()函数来创建一个根命令,所有的应用命令都将作为子命令添加到该命令中。根命令还可以处理:

  • 通过读取配置文件(例如Cosmos SDK的配置文件)来设置配置。
  • 向其添加任何标志,如--chain-id。
  • 通过调用应用程序的MakeCodec()函数(在simapp中称为MakeTestEncodingConfig)来实例化解码器。编解码器用于对应用程序的数据结构进行编码和解码——存储只能保留[]字节,所以开发者必须为他们的数据结构定义一种序列化格式或者使用默认的Protobuf。

为所有可能的用户交互添加子命令,包括交易命令和查询命令。

main()函数最后创建一个执行器并执行根命令。请参阅simapp应用程序中main()函数的示例:

func main() {
	rootCmd, _ := cmd.NewRootCmd()

	if err := svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome); err != nil {
		switch e := err.(type) {
		case server.ErrorCode:
			os.Exit(e.Code)

		default:
			os.Exit(1)
		}
	}
}

文件的其余部分将详细说明每个步骤需要实现的内容,并包括simapp CLI文件中较小的代码部分。

将命令添加到CLI中

每个应用程序的CLI首先构建一个根命令,然后通过使用rootCmd.AddCommand()聚合子命令(通常有进一步嵌套的子命令)来增加功能。应用程序的大部分独特功能在于其交易和查询命令,分别称为TxCmd和QueryCmd。

根命令

根命令(称为rootCmd)是用户首先在命令行中输入的内容,以表明他们希望与哪个应用程序进行交互。用于调用该命令的字符串(“使用”字段)通常是以-d为后缀的应用程序名称,例如simd或gaiad。根命令通常包括以下命令以支持应用程序的基本功能。

  • 来自Cosmos SDK rpc客户端工具的Status命令,它打印出关于连接的Node的状态信息。节点的状态包括NodeInfo、SyncInfo和ValidatorInfo。
  • 来自Cosmos SDK客户端工具的密钥命令,它包括一系列用于使用Cosmos SDK加密工具中的密钥功能的子命令,包括添加一个新的密钥并将其保存到keyring中,列出所有存储在keyring中的公钥,以及删除一个密钥。例如,用户可以键入simd keys add <name>来添加一个新的钥匙并将加密的副本保存到keyring中,使用标志--recover来从短语中恢复一个私钥,或者使用标志--multisig来将多个钥匙分组以创建一个multisig密钥。
  • Cosmos SDK服务器包中的服务器命令。这些命令负责提供启动ABCI Tendermint应用程序的必要机制,并提供完全启动应用程序所需的CLI框架(基于cobra)。该包公开了两个核心函数:StartCmd和ExportCmd,分别创建启动应用程序和导出状态的命令。
  • 交易命令。
  • 查询命令。

接下来是一个来自simapp应用程序的rootCmd函数实例。它实例化了根命令,添加了一个持久性标志和PreRun函数,以便在每次执行前运行,并添加了所有必要的子命令。

// NewRootCmd creates a new root command for simd. It is called once in the
// main function.
func NewRootCmd() (*cobra.Command, params.EncodingConfig) {
	encodingConfig := simapp.MakeTestEncodingConfig()
	initClientCtx := client.Context{}.
		WithCodec(encodingConfig.Codec).
		WithInterfaceRegistry(encodingConfig.InterfaceRegistry).
		WithTxConfig(encodingConfig.TxConfig).
		WithLegacyAmino(encodingConfig.Amino).
		WithInput(os.Stdin).
		WithAccountRetriever(types.AccountRetriever{}).
		WithHomeDir(simapp.DefaultNodeHome).
		WithViper("") // In simapp, we don't use any prefix for env variables.

	rootCmd := &cobra.Command{
		Use:   "simd",
		Short: "simulation app",
		PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
			// set the default command outputs
			cmd.SetOut(cmd.OutOrStdout())
			cmd.SetErr(cmd.ErrOrStderr())

			initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags())
			if err != nil {
				return err
			}

			initClientCtx, err = config.ReadFromClientConfig(initClientCtx)
			if err != nil {
				return err
			}

			if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil {
				return err
			}

			customAppTemplate, customAppConfig := initAppConfig()
			customTMConfig := initTendermintConfig()

			return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customTMConfig)
		},
	}

	initRootCmd(rootCmd, encodingConfig)

	return rootCmd, encodingConfig
}

rootCmd有一个名为initAppConfig()的函数,可用于设置应用程序的自定义配置。默认情况下,应用使用Cosmos SDK中的Tendermint应用配置模板,可以通过initAppConfig()覆盖该模板。这是覆盖默认app.toml模板的示例代码:

// initAppConfig helps to override default appConfig template and configs.
// return "", nil if no custom configuration is required for the application.
func initAppConfig() (string, interface{}) {
	// The following code snippet is just for reference.

	// WASMConfig defines configuration for the wasm module.
	type WASMConfig struct {
		// This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries
		QueryGasLimit uint64 `mapstructure:"query_gas_limit"`

		// Address defines the gRPC-web server to listen on
		LruSize uint64 `mapstructure:"lru_size"`
	}

	type CustomAppConfig struct {
		serverconfig.Config

		WASM WASMConfig `mapstructure:"wasm"`
	}

	// Optionally allow the chain developer to overwrite the SDK's default
	// server config.
	srvCfg := serverconfig.DefaultConfig()
	// The SDK's default minimum gas price is set to "" (empty value) inside
	// app.toml. If left empty by validators, the node will halt on startup.
	// However, the chain developer can set a default app.toml value for their
	// validators here.
	//
	// In summary:
	// - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their
	//   own app.toml config,
	// - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their
	//   own app.toml to override, or use this default value.
	//
	// In simapp, we set the min gas prices to 0.
	srvCfg.MinGasPrices = "0stake"

	customAppConfig := CustomAppConfig{
		Config: *srvCfg,
		WASM: WASMConfig{
			LruSize:       1,
			QueryGasLimit: 300000,
		},
	}

	customAppTemplate := serverconfig.DefaultConfigTemplate + `
[wasm]
# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries
query_gas_limit = 300000
# This is the number of wasm vm instances we keep cached in memory for speed-up
# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally
lru_size = 0`

	return customAppTemplate, customAppConfig
}

initAppConfig() 还允许覆盖默认Cosmos SDK的服务器配置。 一个例子是min-gas-prices配置,它定义了验证者在处理交易时愿意接受的最低气体价格。默认情况下,Cosmos SDK将此参数设置为 ""(空字符串),这会强制所有验证器调整自己的app.toml并设置非空值,否则节点将在启动时停止。这可能不是验证器的最佳UX,因此链开发人员可以在此initAppConfig()函数中为验证器设置默认的app.toml值。

// Optionally allow the chain developer to overwrite the SDK's default
	// server config.
	srvCfg := serverconfig.DefaultConfig()
	// The SDK's default minimum gas price is set to "" (empty value) inside
	// app.toml. If left empty by validators, the node will halt on startup.
	// However, the chain developer can set a default app.toml value for their
	// validators here.
	//
	// In summary:
	// - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their
	//   own app.toml config,
	// - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their
	//   own app.toml to override, or use this default value.
	//
	// In simapp, we set the min gas prices to 0.
	srvCfg.MinGasPrices = "0stake"

根级别的状态和钥匙子命令在大多数应用程序中是通用的,并且不与应用程序的状态进行交互。一个应用程序的大部分功能——用户实际上可以用它做什么——是由它的tx和query命令实现的。

交易命令

交易是包装Msgs的对象,可以触发状态变化。为了能够使用CLI接口创建事务,一般会在rootCmd中添加一个函数txCommand:

	// add keybase, auxiliary RPC, query, and tx child commands
	rootCmd.AddCommand(
		rpc.StatusCommand(),
		queryCommand(),
		txCommand(),
		keys.Commands(simapp.DefaultNodeHome),
	)

这个txCommand函数添加了所有可供终端用户使用的应用程序的交易。这通常包括:

  • 来自auth模块的Sign命令,在交易中签署消息。要启用multisig,请添加auth模块的MultiSign命令。由于每个事务都需要某种签名才能有效,所以签名命令对每个应用都是必要的。
  • 来自Cosmos SDK客户端工具的广播命令,以广播交易。
  • 所有应用程序依赖的模块交易命令,通过使用基本模块管理器的AddTxCommands()函数检索。

下面是一个txCommand的例子,聚合了来自simapp应用的这些子命令:

func txCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:                        "tx",
		Short:                      "Transactions subcommands",
		DisableFlagParsing:         false,
		SuggestionsMinimumDistance: 2,
		RunE:                       client.ValidateCmd,
	}

	cmd.AddCommand(
		authcmd.GetSignCommand(),
		authcmd.GetSignBatchCommand(),
		authcmd.GetMultiSignCommand(),
		authcmd.GetMultiSignBatchCmd(),
		authcmd.GetValidateSignaturesCommand(),
		authcmd.GetBroadcastCommand(),
		authcmd.GetEncodeCommand(),
		authcmd.GetDecodeCommand(),
		authcmd.GetAuxToFeeCommand(),
	)

	simapp.ModuleBasics.AddTxCommands(cmd)
	cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID")

	return cmd
}

查询命令

查询是允许用户检索有关应用程序状态信息的对象。要使用CLI界面创建查询,通常会在rootCmd中添加一个函数queryCommand:

	// add keybase, auxiliary RPC, query, and tx child commands
	rootCmd.AddCommand(
		rpc.StatusCommand(),
		queryCommand(),
		txCommand(),
		keys.Commands(simapp.DefaultNodeHome),
	)

这个queryCommand函数添加了所有可供终端用户查询的应用程序。这通常包括:

  • QueryTx和/或其他交易查询命令来自auth模块,允许用户通过输入交易的哈希值、标签列表或区块高度来搜索交易。这些查询允许用户查看交易是否已被包含在一个区块中。
  • 来自Auth模块的账户命令,显示给定地址的账户的状态(如账户余额)。
  • 来自Cosmos SDK rpc客户端工具的验证器命令,显示给定高度的验证器集。
  • 来自Cosmos SDK rpc客户端工具的块命令,显示一个给定高度的块数据。
  • 所有应用程序依赖的模块查询命令,通过使用基本模块管理器的AddQueryCommands()函数检索。

下面是一个查询命令的例子,它聚合了来自simapp应用程序的子命令:

func queryCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:                        "query",
		Aliases:                    []string{"q"},
		Short:                      "Querying subcommands",
		DisableFlagParsing:         false,
		SuggestionsMinimumDistance: 2,
		RunE:                       client.ValidateCmd,
	}

	cmd.AddCommand(
		authcmd.GetAccountCmd(),
		rpc.ValidatorCommand(),
		rpc.BlockCommand(),
		authcmd.QueryTxsByEventsCmd(),
		authcmd.QueryTxCmd(),
	)

	simapp.ModuleBasics.AddQueryCommands(cmd)
	cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID")

	return cmd
}

Flag

标志是用来修改命令的;开发者可以在CLI的flags.go文件中包含它们。用户可以明确地将它们包含在命令中,或在他们的app.toml中预先配置它们。常见的预配置标志包括——连接的节点和用户希望与之互动的区块链的——chain-id。

添加到命令中的持久性标志(相对于本地标志而言)超越了其所有的子命令:子命令将继承这些标志的配置值。此外,所有的标志在被添加到命令中时都有默认值;有些标志可以关闭一个选项,但有些标志是空值,用户需要覆盖它来创建有效的命令。一个标志可以明确地被标记为必需的,这样如果用户没有提供一个值,就会自动抛出一个错误,但是以不同的方式处理意外的丢失标志也是可以接受的。

标志被直接添加到命令中(一般是在定义模块命令的CLI文件中),除了rootCmd的持久性标志外,没有任何标志需要在应用层面添加。常见的做法是在根命令中添加——chain-id的持久性标志,即应用程序所涉及的区块链的唯一标识符。添加这个标志可以在main()函数中完成。添加这个标志是有意义的,因为在这个应用程序的CLI中,链ID不应该在不同的命令中改变。下面是simapp应用程序的例子:

cmd.PersistentFlags().String(flags.FlagChainID, "", "The network chain ID")

环境变量

每个标志都被绑定到它的相应命名的环境变量。然后,环境变量的名称由两部分组成——大写的basename和flag的flag名称。- 必须用_来代替。例如,带有基本名称为GAIA的应用程序的标志——home被绑定到GAIA_HOME。它允许减少常规操作中输入的标志的数量。例如,而不是:

gaia --home=./ --node=<node address> --chain-id="testchain-1" --keyring-backend=test tx ... --from=<key name>

这会更方便:

# define env variables in .env, .envrc etc
GAIA_HOME=<path to home>
GAIA_NODE=<node address>
GAIA_CHAIN_ID="testchain-1"
GAIA_KEYRING_BACKEND="test"

# and later just use
gaia tx ... --from=<key name>

配置

应用程序的根命令使用PersistentPreRun() cobra命令属性来执行命令至关重要,因此所有子命令都可以访问服务器和客户端context。这些context最初被设置为它们的默认值,并且可能在它们各自的PersistentPreRun()函数中修改,范围为命令。请注意,client.Context通常预先填充了“默认”值,这些值可能对所有命令在必要时继承和覆盖很有用。

下面是一个来自simapp 的 PersistentPreRun()函数示例:

		PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
			// set the default command outputs
			cmd.SetOut(cmd.OutOrStdout())
			cmd.SetErr(cmd.ErrOrStderr())

			initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags())
			if err != nil {
				return err
			}

			initClientCtx, err = config.ReadFromClientConfig(initClientCtx)
			if err != nil {
				return err
			}

			if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil {
				return err
			}

			customAppTemplate, customAppConfig := initAppConfig()
			customTMConfig := initTendermintConfig()

			return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customTMConfig)
		},

SetCmdClientContextHandler调用通过ReadPersistentCommandFlags读取持久标志,该标志创建一个client.Context并将其设置在根命令的context中。

InterceptConfigsPreRunHandler调用创建一个viper文字、默认server.Context和一个记录器,并将其设置在根命令的context中。server.Context将通过内部interceptConfig调用进行修改并保存到磁盘,该调用根据提供的主路径读取或创建Tendermint配置。 此外,interceptConfigs 还读取并加载应用程序配置app.toml,并将其绑定到server.Context viper文字。 这一点至关重要,因此应用程序不仅可以访问CLI标志,还可以访问此文件提供的应用程序配置值。


原文链接:https://docs.cosmos.network/main/core/grpc_rest.html


---煤油灯科技victorlamp.com翻译整理---

你可能也喜欢这些文章

科技互联网,科技,资讯,动态,洞察,量子,计算,AI,人工智能,机器人,区块链,Web3,分布式,操作系统,OS,芯片,视频,深度,论文