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 }