package tx import ( "fmt" "strings" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // MaxGasWanted defines the max gas allowed. const MaxGasWanted = uint64((1 << 63) - 1) // Interface implementation checks. var _, _, _, _ codectypes.UnpackInterfacesMessage = &Tx{}, &TxBody{}, &AuthInfo{}, &SignerInfo{} var _ sdk.Tx = &Tx{} // GetMsgs implements the GetMsgs method on sdk.Tx. func (t *Tx) GetMsgs() []sdk.Msg { if t == nil || t.Body == nil { return nil } anys := t.Body.Messages res := make([]sdk.Msg, len(anys)) for i, any := range anys { var msg sdk.Msg if isServiceMsg(any.TypeUrl) { msg = sdk.ServiceMsg{ MethodName: any.TypeUrl, Request: any.GetCachedValue().(sdk.MsgRequest), } } else { msg = any.GetCachedValue().(sdk.Msg) } res[i] = msg } return res } // ValidateBasic implements the ValidateBasic method on sdk.Tx. func (t *Tx) ValidateBasic() error { if t == nil { return fmt.Errorf("bad Tx") } body := t.Body if body == nil { return fmt.Errorf("missing TxBody") } authInfo := t.AuthInfo if authInfo == nil { return fmt.Errorf("missing AuthInfo") } fee := authInfo.Fee if fee == nil { return fmt.Errorf("missing fee") } if fee.GasLimit > MaxGasWanted { return sdkerrors.Wrapf( sdkerrors.ErrInvalidRequest, "invalid gas supplied; %d > %d", fee.GasLimit, MaxGasWanted, ) } if fee.Amount.IsAnyNegative() { return sdkerrors.Wrapf( sdkerrors.ErrInsufficientFee, "invalid fee provided: %s", fee.Amount, ) } if fee.Payer != "" { _, err := sdk.AccAddressFromBech32(fee.Payer) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid fee payer address (%s)", err) } } sigs := t.Signatures if len(sigs) == 0 { return sdkerrors.ErrNoSignatures } if len(sigs) != len(t.GetSigners()) { return sdkerrors.Wrapf( sdkerrors.ErrUnauthorized, "wrong number of signers; expected %d, got %d", len(t.GetSigners()), len(sigs), ) } return nil } // GetSigners retrieves all the signers of a tx. // This includes all unique signers of the messages (in order), // as well as the FeePayer (if specified and not already included). func (t *Tx) GetSigners() []sdk.AccAddress { var signers []sdk.AccAddress seen := map[string]bool{} for _, msg := range t.GetMsgs() { for _, addr := range msg.GetSigners() { if !seen[addr.String()] { signers = append(signers, addr) seen[addr.String()] = true } } } // ensure any specified fee payer is included in the required signers (at the end) feePayer := t.AuthInfo.Fee.Payer if feePayer != "" && !seen[feePayer] { payerAddr, err := sdk.AccAddressFromBech32(feePayer) if err != nil { panic(err) } signers = append(signers, payerAddr) seen[feePayer] = true } return signers } // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (t *Tx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { if t.Body != nil { if err := t.Body.UnpackInterfaces(unpacker); err != nil { return err } } if t.AuthInfo != nil { return t.AuthInfo.UnpackInterfaces(unpacker) } return nil } // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *TxBody) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { for _, any := range m.Messages { // If the any's typeUrl contains 2 slashes, then we unpack the any into // a ServiceMsg struct as per ADR-031. if isServiceMsg(any.TypeUrl) { var req sdk.MsgRequest err := unpacker.UnpackAny(any, &req) if err != nil { return err } } else { var msg sdk.Msg err := unpacker.UnpackAny(any, &msg) if err != nil { return err } } } return nil } // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *AuthInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { for _, signerInfo := range m.SignerInfos { err := signerInfo.UnpackInterfaces(unpacker) if err != nil { return err } } return nil } // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *SignerInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { return unpacker.UnpackAny(m.PublicKey, new(cryptotypes.PubKey)) } // RegisterInterfaces registers the sdk.Tx interface. func RegisterInterfaces(registry codectypes.InterfaceRegistry) { registry.RegisterInterface("cosmos.tx.v1beta1.Tx", (*sdk.Tx)(nil)) registry.RegisterImplementations((*sdk.Tx)(nil), &Tx{}) } // isServiceMsg checks if a type URL corresponds to a service method name, // i.e. /cosmos.bank.Msg/Send vs /cosmos.bank.MsgSend func isServiceMsg(typeURL string) bool { return strings.Count(typeURL, "/") >= 2 }