package main import ( "context" "errors" "fmt" "io" "path/filepath" "strings" "github.com/Kava-Labs/opendb" cometbftdb "github.com/cometbft/cometbft-db" "github.com/cometbft/cometbft/libs/log" tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/crisis" ethermintflags "github.com/evmos/ethermint/server/flags" "github.com/spf13/cast" "github.com/spf13/cobra" "github.com/0glabs/0g-chain/app" "github.com/0glabs/0g-chain/app/params" gethtypes "github.com/ethereum/go-ethereum/core/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) const ( flagMempoolEnableAuth = "mempool.enable-authentication" flagMempoolAuthAddresses = "mempool.authorized-addresses" flagSkipLoadLatest = "skip-load-latest" ) var accountNonceOp app.AccountNonceOp // appCreator holds functions used by the sdk server to control the 0g-chain app. // The methods implement types in cosmos-sdk/server/types type appCreator struct { encodingConfig params.EncodingConfig } // newApp loads config from AppOptions and returns a new app. func (ac appCreator) newApp( logger log.Logger, db cometbftdb.DB, traceStore io.Writer, appOpts servertypes.AppOptions, ) servertypes.Application { var cache sdk.MultiStorePersistentCache if cast.ToBool(appOpts.Get(server.FlagInterBlockCache)) { cache = store.NewCommitKVStoreCacheManager() } skipUpgradeHeights := make(map[int64]bool) for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { skipUpgradeHeights[int64(h)] = true } pruningOpts, err := server.GetPruningOptionsFromFlags(appOpts) if err != nil { panic(err) } homeDir := cast.ToString(appOpts.Get(flags.FlagHome)) snapshotDir := filepath.Join(homeDir, "data", "snapshots") // TODO can these directory names be imported from somewhere? snapshotDB, err := opendb.OpenDB(appOpts, snapshotDir, "metadata", server.GetAppDBBackend(appOpts)) if err != nil { panic(err) } snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) if err != nil { panic(err) } mempoolEnableAuth := cast.ToBool(appOpts.Get(flagMempoolEnableAuth)) mempoolAuthAddresses, err := accAddressesFromBech32( cast.ToStringSlice(appOpts.Get(flagMempoolAuthAddresses))..., ) if err != nil { panic(fmt.Sprintf("could not get authorized address from config: %v", err)) } iavlDisableFastNode := appOpts.Get(server.FlagDisableIAVLFastNode) if iavlDisableFastNode == nil { iavlDisableFastNode = true } snapshotOptions := snapshottypes.NewSnapshotOptions( cast.ToUint64(appOpts.Get(server.FlagStateSyncSnapshotInterval)), cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent)), ) // Setup chainId chainID := cast.ToString(appOpts.Get(flags.FlagChainID)) if len(chainID) == 0 { // fallback to genesis chain-id appGenesis, err := tmtypes.GenesisDocFromFile(filepath.Join(homeDir, "config", "genesis.json")) if err != nil { panic(err) } chainID = appGenesis.ChainID } skipLoadLatest := false if appOpts.Get(flagSkipLoadLatest) != nil { skipLoadLatest = cast.ToBool(appOpts.Get(flagSkipLoadLatest)) } bApp := app.NewBaseApp(logger, db, ac.encodingConfig, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(strings.Replace(cast.ToString(appOpts.Get(server.FlagMinGasPrices)), ";", ",", -1)), baseapp.SetHaltHeight(cast.ToUint64(appOpts.Get(server.FlagHaltHeight))), baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))), baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), // TODO what is this? baseapp.SetInterBlockCache(cache), baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))), baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))), baseapp.SetSnapshot(snapshotStore, snapshotOptions), baseapp.SetIAVLCacheSize(cast.ToInt(appOpts.Get(server.FlagIAVLCacheSize))), baseapp.SetIAVLDisableFastNode(cast.ToBool(iavlDisableFastNode)), baseapp.SetIAVLLazyLoading(cast.ToBool(appOpts.Get(server.FlagIAVLLazyLoading))), baseapp.SetChainID(chainID), baseapp.SetTxInfoExtracter(extractTxInfo), ) mempool := app.NewPriorityMempool( app.PriorityNonceWithMaxTx(fixMempoolSize(appOpts)), app.PriorityNonceWithTxReplacedCallback(func(ctx context.Context, oldTx, newTx *app.TxInfo) { if oldTx.Sender != newTx.Sender { sdkContext := sdk.UnwrapSDKContext(ctx) if accountNonceOp != nil { nonce := accountNonceOp.GetAccountNonce(sdkContext, oldTx.Sender) accountNonceOp.SetAccountNonce(sdkContext, oldTx.Sender, nonce-1) sdkContext.Logger().Debug("rewind the nonce of the account", "account", oldTx.Sender, "from", nonce, "to", nonce-1) } } else { sdkContext := sdk.UnwrapSDKContext(ctx) sdkContext.Logger().Info("tx replace", "account", oldTx.Sender, "nonce", oldTx.Nonce) } bApp.RegisterMempoolTxReplacedEvent(ctx, oldTx.Tx, newTx.Tx) }), ) bApp.SetMempool(mempool) bApp.SetTxEncoder(ac.encodingConfig.TxConfig.TxEncoder()) abciProposalHandler := app.NewDefaultProposalHandler(mempool, bApp) bApp.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler()) newApp := app.NewApp( homeDir, traceStore, ac.encodingConfig, app.Options{ SkipLoadLatest: skipLoadLatest, SkipUpgradeHeights: skipUpgradeHeights, SkipGenesisInvariants: cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)), InvariantCheckPeriod: cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)), MempoolEnableAuth: mempoolEnableAuth, MempoolAuthAddresses: mempoolAuthAddresses, EVMTrace: cast.ToString(appOpts.Get(ethermintflags.EVMTracer)), EVMMaxGasWanted: cast.ToUint64(appOpts.Get(ethermintflags.EVMMaxTxGasWanted)), }, bApp, ) accountNonceOp = app.NewAccountNonceOp(newApp) return newApp } // appExport writes out an app's state to json. func (ac appCreator) appExport( logger log.Logger, db cometbftdb.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailAllowedAddrs []string, appOpts servertypes.AppOptions, modulesToExport []string, ) (servertypes.ExportedApp, error) { homePath, ok := appOpts.Get(flags.FlagHome).(string) if !ok || homePath == "" { return servertypes.ExportedApp{}, errors.New("application home not set") } options := app.DefaultOptions options.SkipLoadLatest = true options.InvariantCheckPeriod = cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) var tempApp *app.App if height != -1 { bApp := app.NewBaseApp(logger, db, ac.encodingConfig) tempApp = app.NewApp(homePath, traceStore, ac.encodingConfig, options, bApp) if err := tempApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } } else { bApp := app.NewBaseApp(logger, db, ac.encodingConfig) tempApp = app.NewApp(homePath, traceStore, ac.encodingConfig, options, bApp) } return tempApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) } // addStartCmdFlags adds flags to the server start command. func (ac appCreator) addStartCmdFlags(startCmd *cobra.Command) { crisis.AddModuleInitFlags(startCmd) } // accAddressesFromBech32 converts a slice of bech32 encoded addresses into a slice of address types. func accAddressesFromBech32(addresses ...string) ([]sdk.AccAddress, error) { var decodedAddresses []sdk.AccAddress for _, s := range addresses { a, err := sdk.AccAddressFromBech32(s) if err != nil { return nil, err } decodedAddresses = append(decodedAddresses, a) } return decodedAddresses, nil } var ErrMustHaveSigner error = errors.New("tx must have at least one signer") func extractTxInfo(ctx sdk.Context, tx sdk.Tx) (*sdk.TxInfo, error) { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return nil, err } var sender string var nonce uint64 var gasPrice uint64 var gasLimit uint64 var txType int32 if len(sigs) == 0 { txType = 1 msgs := tx.GetMsgs() if len(msgs) != 1 { return nil, ErrMustHaveSigner } msgEthTx, ok := msgs[0].(*evmtypes.MsgEthereumTx) if !ok { return nil, ErrMustHaveSigner } ethTx := msgEthTx.AsTransaction() signer := gethtypes.NewEIP2930Signer(ethTx.ChainId()) ethSender, err := signer.Sender(ethTx) if err != nil { return nil, ErrMustHaveSigner } sender = sdk.AccAddress(ethSender.Bytes()).String() nonce = ethTx.Nonce() gasPrice = ethTx.GasPrice().Uint64() gasLimit = ethTx.Gas() } else { sig := sigs[0] sender = sdk.AccAddress(sig.PubKey.Address()).String() nonce = sig.Sequence } return &sdk.TxInfo{ SignerAddress: sender, Nonce: nonce, GasLimit: gasLimit, GasPrice: gasPrice, TxType: txType, }, nil } func fixMempoolSize(appOpts servertypes.AppOptions) int { val1 := appOpts.Get("mempool.size") val2 := appOpts.Get(server.FlagMempoolMaxTxs) if val1 != nil && val2 != nil { size1 := cast.ToInt(val1) size2 := cast.ToInt(val2) if size1 != size2 { panic("the value of mempool.size and mempool.max-txs are different") } return size1 } else if val1 == nil && val2 == nil { panic("not found mempool size in config") } else if val1 == nil { return cast.ToInt(val2) } else { //if val2 == nil { return cast.ToInt(val1) } }