github.com/cosmos/cosmos-sdk@v0.50.10/x/auth/tx/config/config.go (about)

     1  package tx
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"google.golang.org/grpc"
     8  	"google.golang.org/grpc/codes"
     9  	grpcstatus "google.golang.org/grpc/status"
    10  	"google.golang.org/protobuf/reflect/protoreflect"
    11  
    12  	bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
    13  	txconfigv1 "cosmossdk.io/api/cosmos/tx/config/v1"
    14  	"cosmossdk.io/core/address"
    15  	"cosmossdk.io/core/appmodule"
    16  	"cosmossdk.io/depinject"
    17  	txsigning "cosmossdk.io/x/tx/signing"
    18  	"cosmossdk.io/x/tx/signing/textual"
    19  
    20  	"github.com/cosmos/cosmos-sdk/baseapp"
    21  	"github.com/cosmos/cosmos-sdk/client"
    22  	"github.com/cosmos/cosmos-sdk/codec"
    23  	"github.com/cosmos/cosmos-sdk/runtime"
    24  	sdk "github.com/cosmos/cosmos-sdk/types"
    25  	"github.com/cosmos/cosmos-sdk/types/registry"
    26  	signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
    27  	"github.com/cosmos/cosmos-sdk/x/auth/ante"
    28  	"github.com/cosmos/cosmos-sdk/x/auth/posthandler"
    29  	"github.com/cosmos/cosmos-sdk/x/auth/tx"
    30  	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
    31  	"github.com/cosmos/cosmos-sdk/x/bank/types"
    32  )
    33  
    34  func init() {
    35  	appmodule.Register(&txconfigv1.Config{},
    36  		appmodule.Provide(ProvideModule),
    37  		appmodule.Provide(ProvideProtoRegistry),
    38  	)
    39  }
    40  
    41  type ModuleInputs struct {
    42  	depinject.In
    43  
    44  	Config                *txconfigv1.Config
    45  	AddressCodec          address.Codec
    46  	ValidatorAddressCodec runtime.ValidatorAddressCodec
    47  	Codec                 codec.Codec
    48  	ProtoFileResolver     txsigning.ProtoFileResolver
    49  	// BankKeeper is the expected bank keeper to be passed to AnteHandlers
    50  	BankKeeper             authtypes.BankKeeper               `optional:"true"`
    51  	MetadataBankKeeper     BankKeeper                         `optional:"true"`
    52  	AccountKeeper          ante.AccountKeeper                 `optional:"true"`
    53  	FeeGrantKeeper         ante.FeegrantKeeper                `optional:"true"`
    54  	CustomSignModeHandlers func() []txsigning.SignModeHandler `optional:"true"`
    55  	CustomGetSigners       []txsigning.CustomGetSigner        `optional:"true"`
    56  }
    57  
    58  type ModuleOutputs struct {
    59  	depinject.Out
    60  
    61  	TxConfig        client.TxConfig
    62  	TxConfigOptions tx.ConfigOptions
    63  	BaseAppOption   runtime.BaseAppOption
    64  }
    65  
    66  func ProvideProtoRegistry() txsigning.ProtoFileResolver {
    67  	return registry.MergedProtoRegistry()
    68  }
    69  
    70  func ProvideModule(in ModuleInputs) ModuleOutputs {
    71  	var customSignModeHandlers []txsigning.SignModeHandler
    72  	if in.CustomSignModeHandlers != nil {
    73  		customSignModeHandlers = in.CustomSignModeHandlers()
    74  	}
    75  
    76  	txConfigOptions := tx.ConfigOptions{
    77  		EnabledSignModes: tx.DefaultSignModes,
    78  		SigningOptions: &txsigning.Options{
    79  			FileResolver:          in.ProtoFileResolver,
    80  			AddressCodec:          in.AddressCodec,
    81  			ValidatorAddressCodec: in.ValidatorAddressCodec,
    82  			CustomGetSigners:      make(map[protoreflect.FullName]txsigning.GetSignersFunc),
    83  		},
    84  		CustomSignModes: customSignModeHandlers,
    85  	}
    86  
    87  	for _, mode := range in.CustomGetSigners {
    88  		txConfigOptions.SigningOptions.CustomGetSigners[mode.MsgType] = mode.Fn
    89  	}
    90  
    91  	// enable SIGN_MODE_TEXTUAL only if bank keeper is available
    92  	if in.MetadataBankKeeper != nil {
    93  		txConfigOptions.EnabledSignModes = append(txConfigOptions.EnabledSignModes, signingtypes.SignMode_SIGN_MODE_TEXTUAL)
    94  		txConfigOptions.TextualCoinMetadataQueryFn = NewBankKeeperCoinMetadataQueryFn(in.MetadataBankKeeper)
    95  	}
    96  
    97  	txConfig, err := tx.NewTxConfigWithOptions(in.Codec, txConfigOptions)
    98  	if err != nil {
    99  		panic(err)
   100  	}
   101  
   102  	baseAppOption := func(app *baseapp.BaseApp) {
   103  		// AnteHandlers
   104  		if !in.Config.SkipAnteHandler {
   105  			anteHandler, err := newAnteHandler(txConfig, in)
   106  			if err != nil {
   107  				panic(err)
   108  			}
   109  			app.SetAnteHandler(anteHandler)
   110  		}
   111  
   112  		// PostHandlers
   113  		if !in.Config.SkipPostHandler {
   114  			// In v0.46, the SDK introduces _postHandlers_. PostHandlers are like
   115  			// antehandlers, but are run _after_ the `runMsgs` execution. They are also
   116  			// defined as a chain, and have the same signature as antehandlers.
   117  			//
   118  			// In baseapp, postHandlers are run in the same store branch as `runMsgs`,
   119  			// meaning that both `runMsgs` and `postHandler` state will be committed if
   120  			// both are successful, and both will be reverted if any of the two fails.
   121  			//
   122  			// The SDK exposes a default empty postHandlers chain.
   123  			//
   124  			// Please note that changing any of the anteHandler or postHandler chain is
   125  			// likely to be a state-machine breaking change, which needs a coordinated
   126  			// upgrade.
   127  			postHandler, err := posthandler.NewPostHandler(
   128  				posthandler.HandlerOptions{},
   129  			)
   130  			if err != nil {
   131  				panic(err)
   132  			}
   133  			app.SetPostHandler(postHandler)
   134  		}
   135  
   136  		// TxDecoder/TxEncoder
   137  		app.SetTxDecoder(txConfig.TxDecoder())
   138  		app.SetTxEncoder(txConfig.TxEncoder())
   139  	}
   140  
   141  	return ModuleOutputs{TxConfig: txConfig, TxConfigOptions: txConfigOptions, BaseAppOption: baseAppOption}
   142  }
   143  
   144  func newAnteHandler(txConfig client.TxConfig, in ModuleInputs) (sdk.AnteHandler, error) {
   145  	if in.BankKeeper == nil {
   146  		return nil, fmt.Errorf("both AccountKeeper and BankKeeper are required")
   147  	}
   148  
   149  	anteHandler, err := ante.NewAnteHandler(
   150  		ante.HandlerOptions{
   151  			AccountKeeper:   in.AccountKeeper,
   152  			BankKeeper:      in.BankKeeper,
   153  			SignModeHandler: txConfig.SignModeHandler(),
   154  			FeegrantKeeper:  in.FeeGrantKeeper,
   155  			SigGasConsumer:  ante.DefaultSigVerificationGasConsumer,
   156  		},
   157  	)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("failed to create ante handler: %w", err)
   160  	}
   161  
   162  	return anteHandler, nil
   163  }
   164  
   165  // NewBankKeeperCoinMetadataQueryFn creates a new Textual struct using the given
   166  // BankKeeper to retrieve coin metadata.
   167  //
   168  // This function should be used in the server (app.go) and is already injected thanks to app wiring for app_v2.
   169  func NewBankKeeperCoinMetadataQueryFn(bk BankKeeper) textual.CoinMetadataQueryFn {
   170  	return func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) {
   171  		res, err := bk.DenomMetadata(ctx, &types.QueryDenomMetadataRequest{Denom: denom})
   172  		if err != nil {
   173  			return nil, metadataExists(err)
   174  		}
   175  
   176  		m := &bankv1beta1.Metadata{
   177  			Base:    res.Metadata.Base,
   178  			Display: res.Metadata.Display,
   179  			// fields below are not strictly needed by Textual
   180  			// but added here for completeness.
   181  			Description: res.Metadata.Description,
   182  			Name:        res.Metadata.Name,
   183  			Symbol:      res.Metadata.Symbol,
   184  			Uri:         res.Metadata.URI,
   185  			UriHash:     res.Metadata.URIHash,
   186  		}
   187  		m.DenomUnits = make([]*bankv1beta1.DenomUnit, len(res.Metadata.DenomUnits))
   188  		for i, d := range res.Metadata.DenomUnits {
   189  			m.DenomUnits[i] = &bankv1beta1.DenomUnit{
   190  				Denom:    d.Denom,
   191  				Exponent: d.Exponent,
   192  				Aliases:  d.Aliases,
   193  			}
   194  		}
   195  
   196  		return m, nil
   197  	}
   198  }
   199  
   200  // NewGRPCCoinMetadataQueryFn returns a new Textual instance where the metadata
   201  // queries are done via gRPC using the provided GRPC client connection. In the
   202  // SDK, you can pass a client.Context as the GRPC connection.
   203  //
   204  // Example:
   205  //
   206  //	clientCtx := client.GetClientContextFromCmd(cmd)
   207  //	txt := tx.NewTextualWithGRPCConn(clientCtx)
   208  //
   209  // This should be used in the client (root.go) of an application.
   210  func NewGRPCCoinMetadataQueryFn(grpcConn grpc.ClientConnInterface) textual.CoinMetadataQueryFn {
   211  	return func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) {
   212  		bankQueryClient := bankv1beta1.NewQueryClient(grpcConn)
   213  		res, err := bankQueryClient.DenomMetadata(ctx, &bankv1beta1.QueryDenomMetadataRequest{
   214  			Denom: denom,
   215  		})
   216  		if err != nil {
   217  			return nil, metadataExists(err)
   218  		}
   219  
   220  		return res.Metadata, nil
   221  	}
   222  }
   223  
   224  // metadataExists parses the error, and only propagates the error if it's
   225  // different than a "not found" error.
   226  func metadataExists(err error) error {
   227  	status, ok := grpcstatus.FromError(err)
   228  	if !ok {
   229  		return err
   230  	}
   231  
   232  	// This means we didn't find any metadata for this denom. Returning
   233  	// empty metadata.
   234  	if status.Code() == codes.NotFound {
   235  		return nil
   236  	}
   237  
   238  	return err
   239  }