diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6703d96518..79de516fba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -207,6 +207,11 @@ jobs: **/go.sum **/Makefile Makefile + - name: install Compose + uses: ndeloof/install-compose-action@v0.0.1 + with: + version: latest + legacy: true - name: start localnet if: env.GIT_DIFF run: | diff --git a/client/tx/factory.go b/client/tx/factory.go index 0aa92bc478..fe467008d9 100644 --- a/client/tx/factory.go +++ b/client/tx/factory.go @@ -8,10 +8,11 @@ import ( "os" "strings" - "cosmossdk.io/math" - "github.com/spf13/pflag" - + sdkmath "cosmossdk.io/math" "github.com/cosmos/go-bip39" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/spf13/pflag" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -24,7 +25,6 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" - "github.com/ethereum/go-ethereum/signer/core/apitypes" ) // Factory defines a client transaction factory that facilitates generating and @@ -333,7 +333,7 @@ func (f Factory) BuildUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) { return nil, errors.New("cannot provide both fees and gas prices") } - glDec := math.LegacyNewDec(int64(f.gas)) + glDec := sdkmath.LegacyNewDec(int64(f.gas)) // Derive the fees based on the provided gas prices, where // fee = ceil(gasPrice * gasLimit). @@ -434,7 +434,15 @@ func (f Factory) PrintEIP712MsgType(clientCtx client.Context, msgs ...sdk.Msg) e if err != nil { return fmt.Errorf("failed to get msg types: %s", err) } - typedData, err := authtx.WrapTxToTypedData(chainID.Uint64(), signDoc, msgTypes) + + typedDataDomain := apitypes.TypedDataDomain{ + Name: "Greenfield Tx", + Version: "1.0.0", + ChainId: math.NewHexOrDecimal256(chainID.Int64()), + VerifyingContract: "0x71e835aff094655dEF897fbc85534186DbeaB75d", + Salt: "0", + } + typedData, err := authtx.WrapTxToTypedData(signDoc, msgTypes, typedDataDomain) if err != nil { return fmt.Errorf("failed to wrap tx to typedData: %s", err) } diff --git a/types/upgrade.go b/types/upgrade.go index afe848d59e..5119b5c64b 100644 --- a/types/upgrade.go +++ b/types/upgrade.go @@ -35,4 +35,7 @@ const ( // Mongolian is the upgrade name for Mongolian upgrade Mongolian = "Mongolian" + + // Altai is the upgrade name for Altai upgrade + Altai = "Altai" ) diff --git a/x/auth/ante/sigverify.go b/x/auth/ante/sigverify.go index ceb9f82604..698e931a57 100644 --- a/x/auth/ante/sigverify.go +++ b/x/auth/ante/sigverify.go @@ -303,7 +303,7 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul // no need to verify signatures on recheck tx if !simulate && !ctx.IsReCheckTx() { - err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx, ctx.SigCache(), ctx.TxBytes()) + err := authsigning.VerifySignature(ctx, pubKey, signerData, sig.Data, svd.signModeHandler, tx) if err != nil { errMsg := fmt.Sprintf("signature verification failed; please verify account (%s); err: %s", pubKey.Address(), err) return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) diff --git a/x/auth/client/cli/tx_multisign.go b/x/auth/client/cli/tx_multisign.go index 4cbc5051cd..d086732c7b 100644 --- a/x/auth/client/cli/tx_multisign.go +++ b/x/auth/client/cli/tx_multisign.go @@ -139,7 +139,7 @@ func makeMultiSignCmd() func(cmd *cobra.Command, args []string) (err error) { PubKey: sig.PubKey, } - err = signing.VerifySignature(sig.PubKey, signingData, sig.Data, txCfg.SignModeHandler(), txBuilder.GetTx(), nil, nil) + err = signing.VerifySignature(sdk.Context{}, sig.PubKey, signingData, sig.Data, txCfg.SignModeHandler(), txBuilder.GetTx()) if err != nil { addr, _ := sdk.AccAddressFromHexUnsafe(sig.PubKey.Address().String()) return fmt.Errorf("couldn't verify signature for address %s", addr) @@ -329,7 +329,7 @@ func makeBatchMultisignCmd() func(cmd *cobra.Command, args []string) error { } for _, sig := range signatureBatch { - err = signing.VerifySignature(sig[i].PubKey, signingData, sig[i].Data, txCfg.SignModeHandler(), txBldr.GetTx(), nil, nil) + err = signing.VerifySignature(sdk.Context{}, sig[i].PubKey, signingData, sig[i].Data, txCfg.SignModeHandler(), txBldr.GetTx()) if err != nil { return fmt.Errorf("couldn't verify signature: %w %v", err, sig) } diff --git a/x/auth/client/cli/validate_sigs.go b/x/auth/client/cli/validate_sigs.go index 61dc033a7c..b41aa54f2b 100644 --- a/x/auth/client/cli/validate_sigs.go +++ b/x/auth/client/cli/validate_sigs.go @@ -111,7 +111,7 @@ func printAndValidateSigs( Sequence: accSeq, PubKey: pubKey, } - err = authsigning.VerifySignature(pubKey, signingData, sig.Data, signModeHandler, sigTx, nil, nil) + err = authsigning.VerifySignature(sdk.Context{}, pubKey, signingData, sig.Data, signModeHandler, sigTx) if err != nil { return false } diff --git a/x/auth/migrations/legacytx/amino_signing.go b/x/auth/migrations/legacytx/amino_signing.go index 4115efa704..502d26c960 100644 --- a/x/auth/migrations/legacytx/amino_signing.go +++ b/x/auth/migrations/legacytx/amino_signing.go @@ -47,6 +47,11 @@ func (stdTxSignModeHandler) GetSignBytes(mode signingtypes.SignMode, data signin ), nil } +// GetSignBytesRuntime implements SignModeHandler.GetSignBytesRuntime +func (h stdTxSignModeHandler) GetSignBytesRuntime(ctx sdk.Context, mode signingtypes.SignMode, data signing.SignerData, tx sdk.Tx) ([]byte, error) { + return h.GetSignBytes(mode, data, tx) +} + // SignatureDataToAminoSignature converts a SignatureData to amino-encoded signature bytes. // Only SIGN_MODE_LEGACY_AMINO_JSON is supported. func SignatureDataToAminoSignature(cdc *codec.LegacyAmino, data signingtypes.SignatureData) ([]byte, error) { diff --git a/x/auth/signing/handler_map.go b/x/auth/signing/handler_map.go index 936de47da5..e93cbb8216 100644 --- a/x/auth/signing/handler_map.go +++ b/x/auth/signing/handler_map.go @@ -3,9 +3,8 @@ package signing import ( "fmt" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" ) // SignModeHandlerMap is SignModeHandler that aggregates multiple SignModeHandler's into @@ -50,7 +49,7 @@ func (h SignModeHandlerMap) Modes() []signing.SignMode { return h.modes } -// DefaultMode implements SignModeHandler.GetSignBytes +// GetSignBytes implements SignModeHandler.GetSignBytes func (h SignModeHandlerMap) GetSignBytes(mode signing.SignMode, data SignerData, tx sdk.Tx) ([]byte, error) { handler, found := h.signModeHandlers[mode] if !found { @@ -58,3 +57,12 @@ func (h SignModeHandlerMap) GetSignBytes(mode signing.SignMode, data SignerData, } return handler.GetSignBytes(mode, data, tx) } + +// GetSignBytesRuntime implements SignModeHandler.GetSignBytesRuntime +func (h SignModeHandlerMap) GetSignBytesRuntime(ctx sdk.Context, mode signing.SignMode, data SignerData, tx sdk.Tx) ([]byte, error) { + handler, found := h.signModeHandlers[mode] + if !found { + return nil, fmt.Errorf("can't verify sign mode %s", mode.String()) + } + return handler.GetSignBytesRuntime(ctx, mode, data, tx) +} diff --git a/x/auth/signing/sign_mode_handler.go b/x/auth/signing/sign_mode_handler.go index d4faff3d05..c6e467a893 100644 --- a/x/auth/signing/sign_mode_handler.go +++ b/x/auth/signing/sign_mode_handler.go @@ -19,6 +19,10 @@ type SignModeHandler interface { // GetSignBytes returns the sign bytes for the provided SignMode, SignerData and Tx, // or an error GetSignBytes(mode signing.SignMode, data SignerData, tx sdk.Tx) ([]byte, error) + + // GetSignBytesRuntime returns the sign bytes for the provided SignMode, SignerData and Tx, + // or an error based on the context information + GetSignBytesRuntime(ctx sdk.Context, mode signing.SignMode, data SignerData, tx sdk.Tx) ([]byte, error) } // SignerData is the specific information needed to sign a transaction that generally diff --git a/x/auth/signing/verify.go b/x/auth/signing/verify.go index 89e5b1b9da..9891c6a82e 100644 --- a/x/auth/signing/verify.go +++ b/x/auth/signing/verify.go @@ -6,7 +6,6 @@ import ( errorsmod "cosmossdk.io/errors" ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" - lru "github.com/hashicorp/golang-lru" "github.com/cosmos/cosmos-sdk/crypto/keys/eth/ethsecp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -17,60 +16,30 @@ import ( // VerifySignature verifies a transaction signature contained in SignatureData abstracting over different signing modes // and single vs multi-signatures. -func VerifySignature(pubKey cryptotypes.PubKey, signerData SignerData, sigData signing.SignatureData, handler SignModeHandler, tx sdk.Tx, sigCache *lru.ARCCache, txBytes []byte) error { +func VerifySignature(ctx sdk.Context, pubKey cryptotypes.PubKey, signerData SignerData, sigData signing.SignatureData, handler SignModeHandler, tx sdk.Tx) error { switch data := sigData.(type) { case *signing.SingleSignatureData: // EIP712 signatures are verified in a different way // In greenfield, we adapt another antehandler to reject non-EIP712 signatures if data.SignMode == signing.SignMode_SIGN_MODE_EIP_712 { - // skip signature verification if we have a cache and the tx is already in it - if sigCache != nil && txBytes != nil { - if _, known := sigCache.Get(string(txBytes)); known { - return nil - } - } - - sig := data.Signature - // check signature length - if len(sig) != ethcrypto.SignatureLength { + if len(data.Signature) != ethcrypto.SignatureLength { return errorsmod.Wrap(sdkerrors.ErrorInvalidSigner, "signature length doesn't match typical [R||S||V] signature 65 bytes") } - sigHash, err := handler.GetSignBytes(data.SignMode, signerData, tx) - if err != nil { - return err - } - - // remove the recovery offset if needed (ie. Metamask eip712 signature) - if sig[ethcrypto.RecoveryIDOffset] == 27 || sig[ethcrypto.RecoveryIDOffset] == 28 { - sig[ethcrypto.RecoveryIDOffset] -= 27 - } - - // recover the pubkey from the signature - feePayerPubkey, err := secp256k1.RecoverPubkey(sigHash, sig) - if err != nil { - return errorsmod.Wrap(err, "failed to recover fee payer from sig") - } - - ecPubKey, err := ethcrypto.UnmarshalPubkey(feePayerPubkey) - if err != nil { - return errorsmod.Wrap(err, "failed to unmarshal recovered fee payer pubkey") - } - - // check that the recovered pubkey matches the one in the signerData data - pk := ðsecp256k1.PubKey{ - Key: ethcrypto.CompressPubkey(ecPubKey), - } - if !pubKey.Equals(pk) { - return errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "feePayer's pubkey %s is different from signature's pubkey %s", pubKey, pk) + // skip signature verification if we have a cache and the tx is already in it + if ctx.SigCache() != nil && ctx.TxBytes() != nil { + if _, known := ctx.SigCache().Get(string(ctx.TxBytes())); known { + return nil + } } - // add the tx to the cache if needed - if sigCache != nil && txBytes != nil { - sigCache.Add(string(txBytes), tx) + // verify signature + err := verifyEip712SignatureWithFallback(ctx, pubKey, data.Signature, handler, signerData, tx) + if err == nil && ctx.SigCache() != nil && ctx.TxBytes() != nil { + ctx.SigCache().Add(string(ctx.TxBytes()), tx) } - return nil + return err } else { // original cosmos-sdk signature verification signBytes, err := handler.GetSignBytes(data.SignMode, signerData, tx) @@ -89,3 +58,48 @@ func VerifySignature(pubKey cryptotypes.PubKey, signerData SignerData, sigData s return fmt.Errorf("unexpected SignatureData %T", sigData) } } + +func verifyEip712SignatureWithFallback(ctx sdk.Context, pubKey cryptotypes.PubKey, sig []byte, handler SignModeHandler, signerData SignerData, tx sdk.Tx) error { + // try with the old sign scheme first (for backward compatibility) + sigHash, err := handler.GetSignBytes(signing.SignMode_SIGN_MODE_EIP_712, signerData, tx) + if err == nil { + if err := verifyEip712Signature(pubKey, sig, sigHash); err == nil { + return nil + } + } + + // try with the new sign scheme + sigHash, err = handler.GetSignBytesRuntime(ctx, signing.SignMode_SIGN_MODE_EIP_712, signerData, tx) + if err != nil { + return err + } + return verifyEip712Signature(pubKey, sig, sigHash) +} + +func verifyEip712Signature(pubKey cryptotypes.PubKey, sig []byte, msg []byte) error { + // remove the recovery offset if needed (ie. Metamask eip712 signature) + if sig[ethcrypto.RecoveryIDOffset] == 27 || sig[ethcrypto.RecoveryIDOffset] == 28 { + sig[ethcrypto.RecoveryIDOffset] -= 27 + } + + // recover the pubkey from the signature + feePayerPubkey, err := secp256k1.RecoverPubkey(msg, sig) + if err != nil { + return errorsmod.Wrap(err, "failed to recover fee payer from sig") + } + + ecPubKey, err := ethcrypto.UnmarshalPubkey(feePayerPubkey) + if err != nil { + return errorsmod.Wrap(err, "failed to unmarshal recovered fee payer pubkey") + } + + // check that the recovered pubkey matches the one in the signerData data + pk := ðsecp256k1.PubKey{ + Key: ethcrypto.CompressPubkey(ecPubKey), + } + if !pubKey.Equals(pk) { + return errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "feePayer's pubkey %s is different from signature's pubkey %s", pubKey, pk) + } + + return nil +} diff --git a/x/auth/signing/verify_test.go b/x/auth/signing/verify_test.go index 61080d32f1..83c73bb2a7 100644 --- a/x/auth/signing/verify_test.go +++ b/x/auth/signing/verify_test.go @@ -76,6 +76,6 @@ func TestVerifySignature(t *testing.T) { handler := MakeTestHandlerMap() stdTx := legacytx.NewStdTx(msgs, fee, []legacytx.StdSignature{stdSig}, memo) stdTx.TimeoutHeight = 10 - err = signing.VerifySignature(pubKey, signerData, sigV2.Data, handler, stdTx, nil, nil) + err = signing.VerifySignature(sdk.Context{}, pubKey, signerData, sigV2.Data, handler, stdTx) require.NoError(t, err) } diff --git a/x/auth/tx/direct.go b/x/auth/tx/direct.go index 4acc52a08d..7ced6094d9 100644 --- a/x/auth/tx/direct.go +++ b/x/auth/tx/direct.go @@ -3,10 +3,9 @@ package tx import ( "fmt" - signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" - sdk "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/types/tx" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" ) @@ -42,6 +41,11 @@ func (signModeDirectHandler) GetSignBytes(mode signingtypes.SignMode, data signi return DirectSignBytes(bodyBz, authInfoBz, data.ChainID, data.AccountNumber) } +// GetSignBytesRuntime implements SignModeHandler.GetSignBytesRuntime +func (h signModeDirectHandler) GetSignBytesRuntime(ctx sdk.Context, mode signingtypes.SignMode, data signing.SignerData, tx sdk.Tx) ([]byte, error) { + return h.GetSignBytes(mode, data, tx) +} + // DirectSignBytes returns the SIGN_MODE_DIRECT sign bytes for the provided TxBody bytes, AuthInfo bytes, chain ID, // account number and sequence. func DirectSignBytes(bodyBytes, authInfoBytes []byte, chainID string, accnum uint64) ([]byte, error) { diff --git a/x/auth/tx/direct_aux.go b/x/auth/tx/direct_aux.go index 6361309813..483a534323 100644 --- a/x/auth/tx/direct_aux.go +++ b/x/auth/tx/direct_aux.go @@ -68,3 +68,8 @@ func (signModeDirectAuxHandler) GetSignBytes( return signDocDirectAux.Marshal() } + +// GetSignBytesRuntime implements SignModeHandler.GetSignBytesRuntime +func (h signModeDirectAuxHandler) GetSignBytesRuntime(ctx sdk.Context, mode signingtypes.SignMode, data signing.SignerData, tx sdk.Tx) ([]byte, error) { + return h.GetSignBytes(mode, data, tx) +} diff --git a/x/auth/tx/eip712.go b/x/auth/tx/eip712.go index f84fc38f29..9941e842c3 100644 --- a/x/auth/tx/eip712.go +++ b/x/auth/tx/eip712.go @@ -13,6 +13,7 @@ import ( errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" + "github.com/cosmos/gogoproto/jsonpb" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" @@ -29,15 +30,19 @@ import ( signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/gogoproto/jsonpb" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" ) -var domain = &apitypes.TypedDataDomain{ - Name: "Greenfield Tx", - Version: "1.0.0", - VerifyingContract: "greenfield", - Salt: "0", -} +var ( + domain = &apitypes.TypedDataDomain{ + Name: "Greenfield Tx", + Version: "1.0.0", + VerifyingContract: "greenfield", + Salt: "0", + } + + gnfdVerifyingContract = "0x71e835aff094655dEF897fbc85534186DbeaB75d" // keccak256("greenfield")[12:] +) // signModeEip712Handler defines the SIGN_MODE_DIRECT SignModeHandler type signModeEip712Handler struct{} @@ -55,36 +60,45 @@ func (signModeEip712Handler) Modes() []signingtypes.SignMode { } // GetSignBytes implements SignModeHandler.GetSignBytes -func (signModeEip712Handler) GetSignBytes(mode signingtypes.SignMode, signerData signing.SignerData, tx sdk.Tx) ([]byte, error) { +func (h signModeEip712Handler) GetSignBytes(mode signingtypes.SignMode, signerData signing.SignerData, tx sdk.Tx) ([]byte, error) { + return getSignBytes(mode, signerData, tx, false) +} + +// GetSignBytesRuntime implements SignModeHandler.GetSignBytesRuntime +func (h signModeEip712Handler) GetSignBytesRuntime(ctx sdk.Context, mode signingtypes.SignMode, signerData signing.SignerData, tx sdk.Tx) ([]byte, error) { + if !ctx.IsUpgraded(upgradetypes.Altai) { + return h.GetSignBytes(mode, signerData, tx) + } + return getSignBytes(mode, signerData, tx, true) +} + +func getSignBytes(mode signingtypes.SignMode, signerData signing.SignerData, tx sdk.Tx, isAltai bool) ([]byte, error) { if mode != signingtypes.SignMode_SIGN_MODE_EIP_712 { return nil, fmt.Errorf("expected %s, got %s", signingtypes.SignMode_SIGN_MODE_EIP_712, mode) } - // get the EIP155 chainID from the signerData chainID, err := sdk.ParseChainID(signerData.ChainID) if err != nil { return nil, fmt.Errorf("failed to parse chainID: %s", signerData.ChainID) } - // get the EIP712 types and signDoc from the tx msgTypes, signDoc, err := GetMsgTypes(signerData, tx, chainID) if err != nil { - return nil, errorsmod.Wrapf(err, "failed to get msg types") + return nil, errorsmod.Wrap(err, "failed to get msg types") } - // pack the tx data in EIP712 object - typedData, err := WrapTxToTypedData(chainID.Uint64(), signDoc, msgTypes) - if err != nil { - return nil, errorsmod.Wrapf(err, "failed to pack tx data in EIP712 object") + typedDataDomain := *domain + typedDataDomain.ChainId = math.NewHexOrDecimal256(chainID.Int64()) + if isAltai { + typedDataDomain.VerifyingContract = gnfdVerifyingContract } - // compute the hash - sigHash, err := ComputeTypedDataHash(typedData) + typedData, err := WrapTxToTypedData(signDoc, msgTypes, typedDataDomain) if err != nil { - return nil, err + return nil, errorsmod.Wrap(err, "failed to pack tx data in EIP712 object") } - return sigHash, nil + return ComputeTypedDataHash(typedData) } func GetMsgTypes(signerData signing.SignerData, tx sdk.Tx, typedChainID *big.Int) (apitypes.Types, *types.SignDocEip712, error) { @@ -205,9 +219,9 @@ func ComputeTypedDataHash(typedData apitypes.TypedData) ([]byte, error) { } func WrapTxToTypedData( - chainID uint64, signDoc *types.SignDocEip712, msgTypes apitypes.Types, + typedDataDomain apitypes.TypedDataDomain, ) (apitypes.TypedData, error) { msgCodec := jsonpb.Marshaler{ EmitDefaults: true, @@ -242,12 +256,10 @@ func WrapTxToTypedData( }) } - tempDomain := *domain - tempDomain.ChainId = math.NewHexOrDecimal256(int64(chainID)) typedData := apitypes.TypedData{ Types: msgTypes, PrimaryType: "Tx", - Domain: tempDomain, + Domain: typedDataDomain, Message: txData, } diff --git a/x/auth/tx/legacy_amino_json.go b/x/auth/tx/legacy_amino_json.go index 01274f9f6e..b9d0d2f34b 100644 --- a/x/auth/tx/legacy_amino_json.go +++ b/x/auth/tx/legacy_amino_json.go @@ -76,3 +76,8 @@ func (s signModeLegacyAminoJSONHandler) GetSignBytes(mode signingtypes.SignMode, tx.GetMsgs(), protoTx.GetMemo(), tip, ), nil } + +// GetSignBytesRuntime implements SignModeHandler.GetSignBytesRuntime +func (s signModeLegacyAminoJSONHandler) GetSignBytesRuntime(ctx sdk.Context, mode signingtypes.SignMode, data signing.SignerData, tx sdk.Tx) ([]byte, error) { + return s.GetSignBytes(mode, data, tx) +} diff --git a/x/upgrade/types/upgrade_config.go b/x/upgrade/types/upgrade_config.go index b75d8abaf0..22ca1b205e 100644 --- a/x/upgrade/types/upgrade_config.go +++ b/x/upgrade/types/upgrade_config.go @@ -41,6 +41,9 @@ const ( // Mongolian is the upgrade name for Mongolian upgrade Mongolian = types.Mongolian + + // Altai is the upgrade name for Altai upgrade + Altai = types.Altai ) // The default upgrade config for networks