diff --git a/cli/cmd/compose.yaml.tpl b/cli/cmd/compose.yaml.tpl index 71dc6215d..a0657d007 100644 --- a/cli/cmd/compose.yaml.tpl +++ b/cli/cmd/compose.yaml.tpl @@ -4,6 +4,7 @@ services: container_name: halo image: omniops/halovisor:{{.HaloTag}} restart: unless-stopped + environment: [{{ if .GenesisBinary}}COSMOVISOR_CUSTOM_GENESIS={{.GenesisBinary}}{{ end }}] ports: - 26656:26656 # CometBFT Consensus P2P - 26657:26657 # CometBFT Consensus RPC diff --git a/cli/cmd/initnodes.go b/cli/cmd/initnodes.go index 5408132f7..6d1a9944b 100644 --- a/cli/cmd/initnodes.go +++ b/cli/cmd/initnodes.go @@ -11,11 +11,13 @@ import ( "text/template" "github.com/omni-network/omni/e2e/app/geth" + haloapp "github.com/omni-network/omni/halo/app" halocmd "github.com/omni-network/omni/halo/cmd" halocfg "github.com/omni-network/omni/halo/config" "github.com/omni-network/omni/lib/buildinfo" cprovider "github.com/omni-network/omni/lib/cchain/provider" "github.com/omni-network/omni/lib/errors" + "github.com/omni-network/omni/lib/feature" "github.com/omni-network/omni/lib/log" "github.com/omni-network/omni/lib/netconf" @@ -37,13 +39,14 @@ const ( ) type InitConfig struct { - Network netconf.ID - Home string - Moniker string - Clean bool - Archive bool - Debug bool - HaloTag string + Network netconf.ID + Home string + Moniker string + Clean bool + Archive bool + Debug bool + HaloTag string + HaloFeatureFlags feature.Flags } func (c InitConfig) Verify() error { @@ -131,6 +134,7 @@ func InitNodes(ctx context.Context, cfg InitConfig) error { TrustedSync: !cfg.Archive, // Don't state sync if archive AddrBook: true, HaloCfgFunc: func(haloCfg *halocfg.Config) { + haloCfg.FeatureFlags = cfg.HaloFeatureFlags haloCfg.EngineEndpoint = "http://omni_evm:8551" haloCfg.EngineJWTFile = "/geth/jwtsecret" if cfg.Archive { @@ -158,7 +162,18 @@ func InitNodes(ctx context.Context, cfg InitConfig) error { return errors.Wrap(err, "init halo") } - err = writeComposeFile(ctx, cfg) + var upgrade string + // For non-archive nodes, we want to detect the latest upgrade and start + // the local node with this binary, so that the consensus snapshot pulled + // from the network is compatible with the local binary. + if !cfg.Archive { + upgrade, err = detectCurrentUpgrade(ctx, cfg.Network) + if err != nil { + return errors.Wrap(err, "detect upgrade") + } + } + + err = writeComposeFile(ctx, cfg, upgrade) if err != nil { return errors.Wrap(err, "write compose file") } @@ -166,6 +181,43 @@ func InitNodes(ctx context.Context, cfg InitConfig) error { return nil } +// detectCurrentUpgrade detects the current upgrade applied on the network. +// Note it only detects known upgrades. It return empty string if no known upgrade is applied. +func detectCurrentUpgrade(ctx context.Context, network netconf.ID) (string, error) { + rpcServer := network.Static().ConsensusRPC() + rpcCl, err := rpchttp.New(rpcServer, "/websocket") + if err != nil { + return "", errors.Wrap(err, "create rpc client") + } + cprov := cprovider.NewABCI(rpcCl, network) + + // Cosmos doesn't provide an API to simply query current applied upgrade. + // So we iterate through knowns and check which is active. + for _, upgrade := range haloapp.AllUpgrades() { + plan, ok, err := cprov.AppliedPlan(ctx, upgrade) + if err != nil { + return "", errors.Wrap(err, "fetching applied plan") + } else if !ok { + continue + } else if upgrade != plan.Name { + return "", errors.New("unexpected upgrade plan name", "expected", upgrade, "actual", plan.Name) + } + + log.Info(ctx, "Detected activated network upgrade", "upgrade_name", plan.Name, "upgrade_height", plan.Height) + + return upgrade, nil + } + + // No known upgrade detected. + if network != netconf.Devnet { // This is only expected for devnet + return "", errors.New("failed to detect known network upgrade (use latest cli version)") + } + + log.Info(ctx, "No known network upgrade detected") + + return "", nil +} + // maybeDownloadGenesis downloads the genesis files via cprovider the network if they are not already set. func maybeDownloadGenesis(ctx context.Context, network netconf.ID) error { if network.IsProtected() { @@ -191,7 +243,7 @@ func maybeDownloadGenesis(ctx context.Context, network netconf.ID) error { return netconf.SetEphemeralGenesis(network, execution, consensus) } -func writeComposeFile(ctx context.Context, cfg InitConfig) error { +func writeComposeFile(ctx context.Context, cfg InitConfig, upgrade string) error { composeFile := filepath.Join(cfg.Home, "compose.yaml") if cmtos.FileExists(composeFile) { @@ -223,11 +275,13 @@ func writeComposeFile(ctx context.Context, cfg InitConfig) error { GethTag string GethVerbosity int GethArchive bool + GenesisBinary string }{ HaloTag: cfg.HaloTag, GethTag: geth.Version, GethVerbosity: verbosity, GethArchive: cfg.Archive, + GenesisBinary: upgrade, }) if err != nil { return errors.Wrap(err, "execute template") diff --git a/halo/app/upgrades.go b/halo/app/upgrades.go index 49e3964a5..f80bb6246 100644 --- a/halo/app/upgrades.go +++ b/halo/app/upgrades.go @@ -25,6 +25,16 @@ var upgrades = []Upgrade{ }, } +// AllUpgrades returns the names of all known upgrades. +func AllUpgrades() []string { + var resp []string + for _, u := range upgrades { + resp = append(resp, u.Name) + } + + return resp +} + // NextUpgrade returns the next upgrade name after the provided previous upgrade.. func NextUpgrade(prev string) (string, error) { if prev == "" { // Return the first upgrade diff --git a/scripts/join/join_test.go b/scripts/join/join_test.go index bb3108c51..5f3876b17 100644 --- a/scripts/join/join_test.go +++ b/scripts/join/join_test.go @@ -16,8 +16,11 @@ import ( "time" clicmd "github.com/omni-network/omni/cli/cmd" + "github.com/omni-network/omni/e2e/manifests" + "github.com/omni-network/omni/e2e/types" "github.com/omni-network/omni/lib/errors" "github.com/omni-network/omni/lib/ethclient" + "github.com/omni-network/omni/lib/feature" "github.com/omni-network/omni/lib/log" "github.com/omni-network/omni/lib/netconf" "github.com/omni-network/omni/lib/tutil" @@ -25,6 +28,7 @@ import ( rpchttp "github.com/cometbft/cometbft/rpc/client/http" + "github.com/BurntSushi/toml" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" ) @@ -61,10 +65,11 @@ func TestJoinNetwork(t *testing.T) { networkID := netconf.ID(*network) haloTag := haloTag(t) cfg := clicmd.InitConfig{ - Network: networkID, - Home: home, - Moniker: t.Name(), - HaloTag: haloTag, + Network: networkID, + Home: home, + Moniker: t.Name(), + HaloTag: haloTag, + HaloFeatureFlags: maybeGetFeatureFlags(ctx, networkID), } tutil.RequireNoError(t, ensureHaloImage(cfg.HaloTag)) @@ -348,3 +353,20 @@ func getContainerStats(ctx context.Context) (stats, error) { return resp, nil } + +func maybeGetFeatureFlags(ctx context.Context, network netconf.ID) feature.Flags { + if network.IsProtected() { + return make([]string, 0) // Protected networks never have feature flags + } else if network == netconf.Devnet { + panic("cannot join devnet") + } + + var manifest types.Manifest + _, err := toml.Decode(string(manifests.Staging()), &manifest) + if err != nil { + log.Error(ctx, "failed to parse the manifest", err) + return make([]string, 0) + } + + return manifest.FeatureFlags +}