feat: enhance rosetta support (#11609)

* fee suggestion for construction/metadata

* rename module

* change default gas_limit for fee suggestion

* add rosetta items in template

* type fix for default-suggest-denom

* add default values

* add suggestion-related config

* force set to success for balance operations

* enable offline mode

* Revert "rename module"

This reverts commit ea433e74dacb039e01c4d4fe0dc77bbb29c91952.

* increase defaultNodeTimeout for rosetta for huge genesis

* use DefaultGasLimit as DefaultSuggestGas

* use default gas limit as default gas suggest for rosetta

* add enable-default-fee-suggest for rosetta

* update config template

* prevent bad gateway due to huge genesis

* Apply suggestions from code review

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Anil Kumar Kammari <anil@vitwit.com>

* add price flag for rosetta standalone

* fix rosetta data/block when block identifier is unset

* add checking mismatched index/hash

* bump rosetta-sdk-go to v0.7.7

* bump dependency

* add changelog

* Apply suggestions from code review

* Apply suggestions from code review

* make gas_prices and gas_limit optional

* Apply suggestions from code review

* add changelog

* revive overwritten by cherrypick

* revive overwritten by cherry-pick

* rename local variable

* rename variables and configs

* Apply suggestions from code review

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>

* break validation into two if cases

* fix config toml template

Co-authored-by: yys <sw.yunsuk@gmail.com>
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Anil Kumar Kammari <anil@vitwit.com>
This commit is contained in:
Geoff Lee 2022-05-02 17:22:50 +09:00 committed by GitHub
parent 53003e15a3
commit 38a1132024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 239 additions and 49 deletions

View File

@ -85,6 +85,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10962](https://github.com/cosmos/cosmos-sdk/pull/10962) ADR-040: Add state migration from iavl (v1Store) to smt (v2Store)
* (types) [\#10948](https://github.com/cosmos/cosmos-sdk/issues/10948) Add `app-db-backend` to the `app.toml` config to replace the compile-time `types.DBbackend` variable.
* (authz)[\#11060](https://github.com/cosmos/cosmos-sdk/pull/11060) Support grant with no expire time.
* (rosetta) [\#11590](https://github.com/cosmos/cosmos-sdk/pull/11590) Add fee suggestion for rosetta and enable offline mode. Also force set events about Fees to Success to pass reconciliation test.
### API Breaking Changes
@ -266,6 +267,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (cli) [\#11337](https://github.com/cosmos/cosmos-sdk/pull/11337) Fixes `show-adress` cli cmd
* (crypto) [\#11298](https://github.com/cosmos/cosmos-sdk/pull/11298) Fix cgo secp signature verification and update libscep256k1 library.
* (x/authz) [\#11512](https://github.com/cosmos/cosmos-sdk/pull/11512) Fix response of a panic to error, when subtracting balances.
* (rosetta) [\#11590](https://github.com/cosmos/cosmos-sdk/pull/11590) `/block` returns an error with nil pointer when a request has both of index and hash and increase timeout for huge genesis.
* (x/feegrant) [\#11813](https://github.com/cosmos/cosmos-sdk/pull/11813) Fix pagination total count in `AllowancesByGranter` query.
### State Machine Breaking

View File

@ -6,6 +6,7 @@ import (
"github.com/spf13/viper"
clientflags "github.com/cosmos/cosmos-sdk/client/flags"
pruningtypes "github.com/cosmos/cosmos-sdk/pruning/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -131,6 +132,15 @@ type RosettaConfig struct {
// Offline defines if the server must be run in offline mode
Offline bool `mapstructure:"offline"`
// EnableFeeSuggestion defines if the server should suggest fee by default
EnableFeeSuggestion bool `mapstructure:"enable-fee-suggestion"`
// GasToSuggest defines gas limit when calculating the fee
GasToSuggest int `mapstructure:"gas-to-suggest"`
// DenomToSuggest defines the defult denom for fee suggestion
DenomToSuggest string `mapstructure:"denom-to-suggest"`
}
// GRPCConfig defines configuration for the gRPC server.
@ -236,12 +246,15 @@ func DefaultConfig() *Config {
Address: DefaultGRPCAddress,
},
Rosetta: RosettaConfig{
Enable: false,
Address: ":8080",
Blockchain: "app",
Network: "network",
Retries: 3,
Offline: false,
Enable: false,
Address: ":8080",
Blockchain: "app",
Network: "network",
Retries: 3,
Offline: false,
EnableFeeSuggestion: false,
GasToSuggest: clientflags.DefaultGasLimit,
DenomToSuggest: "uatom",
},
GRPCWeb: GRPCWebConfig{
Enable: true,
@ -299,12 +312,15 @@ func GetConfig(v *viper.Viper) Config {
EnableUnsafeCORS: v.GetBool("api.enabled-unsafe-cors"),
},
Rosetta: RosettaConfig{
Enable: v.GetBool("rosetta.enable"),
Address: v.GetString("rosetta.address"),
Blockchain: v.GetString("rosetta.blockchain"),
Network: v.GetString("rosetta.network"),
Retries: v.GetInt("rosetta.retries"),
Offline: v.GetBool("rosetta.offline"),
Enable: v.GetBool("rosetta.enable"),
Address: v.GetString("rosetta.address"),
Blockchain: v.GetString("rosetta.blockchain"),
Network: v.GetString("rosetta.network"),
Retries: v.GetInt("rosetta.retries"),
Offline: v.GetBool("rosetta.offline"),
EnableFeeSuggestion: v.GetBool("rosetta.enable-fee-suggestion"),
GasToSuggest: v.GetInt("rosetta.gas-to-suggest"),
DenomToSuggest: v.GetString("rosetta.denom-to-suggest"),
},
GRPC: GRPCConfig{
Enable: v.GetBool("grpc.enable"),

View File

@ -170,6 +170,18 @@ retries = {{ .Rosetta.Retries }}
# Offline defines if Rosetta server should run in offline mode.
offline = {{ .Rosetta.Offline }}
# EnableDefaultSuggestedFee defines if the server should suggest fee by default.
# If 'construction/medata' is called without gas limit and gas price,
# suggested fee based on gas-to-suggest and denom-to-suggest will be given.
enable-fee-suggestion = {{ .Rosetta.EnableFeeSuggestion }}
# GasToSuggest defines gas limit when calculating the fee
gas-to-suggest = {{ .Rosetta.GasToSuggest }}
# DenomToSuggest defines the defult denom for fee suggestion.
# Price must be in minimum-gas-prices.
denom-to-suggest = "{{ .Rosetta.DenomToSuggest }}"
###############################################################################
### gRPC Configuration ###
###############################################################################

View File

@ -11,27 +11,32 @@ import (
"strconv"
"time"
rosettatypes "github.com/coinbase/rosetta-sdk-go/types"
"github.com/cosmos/cosmos-sdk/version"
abcitypes "github.com/tendermint/tendermint/abci/types"
tmrpc "github.com/tendermint/tendermint/rpc/client"
rosettatypes "github.com/coinbase/rosetta-sdk-go/types"
"google.golang.org/grpc/metadata"
"github.com/tendermint/tendermint/rpc/client/http"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
crgerrs "github.com/cosmos/cosmos-sdk/server/rosetta/lib/errors"
crgtypes "github.com/cosmos/cosmos-sdk/server/rosetta/lib/types"
sdk "github.com/cosmos/cosmos-sdk/types"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/cosmos/cosmos-sdk/version"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
auth "github.com/cosmos/cosmos-sdk/x/auth/types"
bank "github.com/cosmos/cosmos-sdk/x/bank/types"
tmrpc "github.com/tendermint/tendermint/rpc/client"
)
// interface assertion
var _ crgtypes.Client = (*Client)(nil)
const defaultNodeTimeout = 15 * time.Second
const defaultNodeTimeout = time.Minute
// Client implements a single network client to interact with cosmos based chains
type Client struct {
@ -121,6 +126,14 @@ func (c *Client) Ready() error {
if err != nil {
return err
}
// to prevent timeout of reading genesis block
var height int64 = -1
_, err = c.BlockByHeight(ctx, &height)
if err != nil {
return err
}
_, err = c.bank.TotalSupply(ctx, &bank.QueryTotalSupplyRequest{})
if err != nil {
return err
@ -395,6 +408,28 @@ func (c *Client) ConstructionMetadataFromOptions(ctx context.Context, options ma
return nil, err
}
// if default fees suggestion is enabled and gas limit or price is unset, use default
if c.config.EnableFeeSuggestion {
if constructionOptions.GasLimit <= 0 {
constructionOptions.GasLimit = uint64(c.config.GasToSuggest)
}
if constructionOptions.GasPrice == "" {
denom := c.config.DenomToSuggest
constructionOptions.GasPrice = c.config.SuggestPrices.AmountOf(denom).String() + denom
}
}
if constructionOptions.GasLimit > 0 && constructionOptions.GasPrice != "" {
gasPrice, err := sdk.ParseDecCoin(constructionOptions.GasPrice)
if err != nil {
return nil, err
}
// check gasPrice is in the list
if !c.config.SuggestPrices.AmountOf(gasPrice.Denom).IsPositive() {
return nil, crgerrs.ErrBadArgument
}
}
signersData := make([]*SignerData, len(constructionOptions.ExpectedSigners))
for i, signer := range constructionOptions.ExpectedSigners {

View File

@ -10,8 +10,10 @@ import (
crg "github.com/cosmos/cosmos-sdk/server/rosetta/lib/server"
clientflags "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// configuration defaults constants
@ -30,17 +32,27 @@ const (
DefaultNetwork = "network"
// DefaultOffline defines the default offline value
DefaultOffline = false
// DefaultEnableFeeSuggestion indicates to use fee suggestion if `construction/metadata` is called without gas limit and price
DefaultEnableFeeSuggestion = false
// DenomToSuggest defines the default denom for fee suggestion
DenomToSuggest = "uatom"
// DefaultPrices defines the default list of prices to suggest
DefaultPrices = "0.0uatom"
)
// configuration flags
const (
FlagBlockchain = "blockchain"
FlagNetwork = "network"
FlagTendermintEndpoint = "tendermint"
FlagGRPCEndpoint = "grpc"
FlagAddr = "addr"
FlagRetries = "retries"
FlagOffline = "offline"
FlagBlockchain = "blockchain"
FlagNetwork = "network"
FlagTendermintEndpoint = "tendermint"
FlagGRPCEndpoint = "grpc"
FlagAddr = "addr"
FlagRetries = "retries"
FlagOffline = "offline"
FlagEnableFeeSuggestion = "enable-fee-suggestion"
FlagGasToSuggest = "gas-to-suggest"
FlagDenomToSuggest = "denom-to-suggest"
FlagPricesToSuggest = "prices-to-suggest"
)
// Config defines the configuration of the rosetta server
@ -65,6 +77,14 @@ type Config struct {
Retries int
// Offline defines if the server must be run in offline mode
Offline bool
// EnableFeeSuggestion indicates to use fee suggestion when `construction/metadata` is called without gas limit and price
EnableFeeSuggestion bool
// GasToSuggest defines the gas limit for fee suggestion
GasToSuggest int
// DenomToSuggest defines the default denom for fee suggestion
DenomToSuggest string
// SuggestPrices defines the gas prices for fee suggestion
SuggestPrices sdk.DecCoins
// Codec overrides the default data and construction api client codecs
Codec *codec.ProtoCodec
// InterfaceRegistry overrides the default data and construction api interface registry
@ -99,8 +119,18 @@ func (c *Config) validate() error {
if c.Network == "" {
return fmt.Errorf("network not provided")
}
if c.Offline {
return fmt.Errorf("offline mode is not supported for stargate implementation due to how sigv2 works")
if c.GasToSuggest <= 0 {
c.GasToSuggest = clientflags.DefaultGasLimit
}
found := false
for i := 0; i < c.SuggestPrices.Len(); i++ {
if c.SuggestPrices.GetDenomByIndex(i) == c.DenomToSuggest {
found = true
break
}
}
if !found {
return fmt.Errorf("default suggest denom is not found in minimum-gas-prices")
}
// these are optional but it must be online
@ -153,14 +183,39 @@ func FromFlags(flags *pflag.FlagSet) (*Config, error) {
if err != nil {
return nil, err
}
enableDefaultFeeSuggestion, err := flags.GetBool(FlagEnableFeeSuggestion)
if err != nil {
return nil, err
}
suggestGas, err := flags.GetInt(FlagGasToSuggest)
if err != nil {
return nil, err
}
suggestDenom, err := flags.GetString(FlagDenomToSuggest)
if err != nil {
return nil, err
}
suggestPrices, err := flags.GetString(FlagPricesToSuggest)
if err != nil {
return nil, err
}
prices, err := sdk.ParseDecCoins(suggestPrices)
if err != nil {
return nil, err
}
conf := &Config{
Blockchain: blockchain,
Network: network,
TendermintRPC: tendermintRPC,
GRPCEndpoint: gRPCEndpoint,
Addr: addr,
Retries: retries,
Offline: offline,
Blockchain: blockchain,
Network: network,
TendermintRPC: tendermintRPC,
GRPCEndpoint: gRPCEndpoint,
Addr: addr,
Retries: retries,
Offline: offline,
EnableFeeSuggestion: enableDefaultFeeSuggestion,
GasToSuggest: suggestGas,
DenomToSuggest: suggestDenom,
SuggestPrices: prices,
}
err = conf.validate()
if err != nil {
@ -201,4 +256,8 @@ func SetFlags(flags *pflag.FlagSet) {
flags.String(FlagAddr, DefaultAddr, "the address rosetta will bind to")
flags.Int(FlagRetries, DefaultRetries, "the number of retries that will be done before quitting")
flags.Bool(FlagOffline, DefaultOffline, "run rosetta only with construction API")
flags.Bool(FlagEnableFeeSuggestion, DefaultEnableFeeSuggestion, "enable default fee suggestion")
flags.Int(FlagGasToSuggest, clientflags.DefaultGasLimit, "default gas for fee suggestion")
flags.String(FlagDenomToSuggest, DenomToSuggest, "default denom for fee suggestion")
flags.String(FlagPricesToSuggest, DefaultPrices, "default prices for fee suggestion")
}

View File

@ -297,7 +297,7 @@ func (c converter) Tx(rawTx tmtypes.Tx, txResult *abci.ResponseDeliverTx) (*rose
var balanceOps []*rosettatypes.Operation
// tx result might be nil, in case we're querying an unconfirmed tx from the mempool
if txResult != nil {
balanceOps = c.BalanceOps(status, txResult.Events)
balanceOps = c.BalanceOps(StatusTxSuccess, txResult.Events) // force set to success because no events for failed tx
}
// now normalize indexes

View File

@ -4,8 +4,11 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/cosmos/cosmos-sdk/server/rosetta/lib/errors"
@ -69,9 +72,43 @@ func (on OnlineNetwork) ConstructionMetadata(ctx context.Context, request *types
return nil, errors.ToRosetta(err)
}
return &types.ConstructionMetadataResponse{
response := &types.ConstructionMetadataResponse{
Metadata: metadata,
}, nil
}
if metadata["gas_price"] != nil && metadata["gas_limit"] != nil {
gasPrice, ok := metadata["gas_price"].(string)
if !ok {
return nil, errors.ToRosetta(errors.WrapError(errors.ErrBadArgument, "invalid gas_price"))
}
if gasPrice == "" { // gas_price is unset. skip fee suggestion
return response, nil
}
price, err := sdk.ParseDecCoin(gasPrice)
if err != nil {
return nil, errors.ToRosetta(err)
}
gasLimit, ok := metadata["gas_limit"].(float64)
if !ok {
return nil, errors.ToRosetta(errors.WrapError(errors.ErrBadArgument, "invalid gas_limit"))
}
if gasLimit == 0 { // gas_limit is unset. skip fee suggestion
return response, nil
}
gas := sdk.NewIntFromUint64(uint64(gasLimit))
suggestedFee := types.Amount{
Value: strconv.FormatInt(price.Amount.MulInt64(gas.Int64()).Ceil().TruncateInt64(), 10),
Currency: &(types.Currency{
Symbol: price.Denom,
Decimals: 0,
}),
}
response.SuggestedFee = []*types.Amount{&suggestedFee}
}
return response, nil
}
// ConstructionParse Parse is called on both unsigned and signed transactions to understand the

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/cosmos/cosmos-sdk/server/rosetta/lib/errors"
crgtypes "github.com/cosmos/cosmos-sdk/server/rosetta/lib/types"
)
@ -56,9 +55,14 @@ func (on OnlineNetwork) Block(ctx context.Context, request *types.BlockRequest)
blockResponse crgtypes.BlockTransactionsResponse
err error
)
// block identifier is assumed not to be nil as rosetta will do this check for us
// check if we have to query via hash or block number
// 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 {
@ -69,8 +73,23 @@ func (on OnlineNetwork) Block(ctx context.Context, request *types.BlockRequest)
if err != nil {
return nil, errors.ToRosetta(err)
}
default:
err := errors.WrapError(errors.ErrBadArgument, "at least one of hash or index needs to be specified")
// 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)
}

View File

@ -32,6 +32,7 @@ import (
"github.com/cosmos/cosmos-sdk/server/rosetta"
crgserver "github.com/cosmos/cosmos-sdk/server/rosetta/lib/server"
"github.com/cosmos/cosmos-sdk/server/types"
sdktypes "github.com/cosmos/cosmos-sdk/types"
)
const (
@ -403,16 +404,25 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App
offlineMode = true
}
minGasPrices, err := sdktypes.ParseDecCoins(config.MinGasPrices)
if err != nil {
ctx.Logger.Error("failed to parse minimum-gas-prices: ", err)
return err
}
conf := &rosetta.Config{
Blockchain: config.Rosetta.Blockchain,
Network: config.Rosetta.Network,
TendermintRPC: ctx.Config.RPC.ListenAddress,
GRPCEndpoint: config.GRPC.Address,
Addr: config.Rosetta.Address,
Retries: config.Rosetta.Retries,
Offline: offlineMode,
Codec: clientCtx.Codec.(*codec.ProtoCodec),
InterfaceRegistry: clientCtx.InterfaceRegistry,
Blockchain: config.Rosetta.Blockchain,
Network: config.Rosetta.Network,
TendermintRPC: ctx.Config.RPC.ListenAddress,
GRPCEndpoint: config.GRPC.Address,
Addr: config.Rosetta.Address,
Retries: config.Rosetta.Retries,
Offline: offlineMode,
GasToSuggest: config.Rosetta.GasToSuggest,
EnableFeeSuggestion: config.Rosetta.EnableFeeSuggestion,
SuggestPrices: minGasPrices.Sort(),
Codec: clientCtx.Codec.(*codec.ProtoCodec),
InterfaceRegistry: clientCtx.InterfaceRegistry,
}
rosettaSrv, err = rosetta.ServerFromConfig(conf)