diff --git a/.github/workflows/release-official.yml b/.github/workflows/release-official.yml index 82fd70da5..ee96bf0e5 100644 --- a/.github/workflows/release-official.yml +++ b/.github/workflows/release-official.yml @@ -39,7 +39,7 @@ jobs: with: file: scripts/halovisor/Dockerfile build-args: | - HALO_VERSION_0_GENESIS=${{ github.ref_name }} + HALO_VERSION_1_ULUWATU=${{ github.ref_name }} platforms: | linux/amd64 push: true diff --git a/.github/workflows/release-snapshot.yml b/.github/workflows/release-snapshot.yml index e5af448bd..431049e29 100644 --- a/.github/workflows/release-snapshot.yml +++ b/.github/workflows/release-snapshot.yml @@ -76,7 +76,7 @@ jobs: with: file: scripts/halovisor/Dockerfile build-args: | - HALO_VERSION_0_GENESIS=${{ steps.git_ref.outputs.short_sha }} + HALO_VERSION_1_ULUWATU=${{ steps.git_ref.outputs.short_sha }} platforms: | linux/amd64 push: true diff --git a/e2e/app/run.go b/e2e/app/run.go index d31194e52..cf9e1580c 100644 --- a/e2e/app/run.go +++ b/e2e/app/run.go @@ -5,9 +5,11 @@ import ( "time" "github.com/omni-network/omni/contracts/bindings" + "github.com/omni-network/omni/e2e/app/eoa" "github.com/omni-network/omni/e2e/netman" "github.com/omni-network/omni/e2e/netman/pingpong" "github.com/omni-network/omni/e2e/types" + "github.com/omni-network/omni/halo/genutil/evm/predeploys" "github.com/omni-network/omni/lib/contracts" "github.com/omni-network/omni/lib/errors" "github.com/omni-network/omni/lib/k1util" @@ -16,6 +18,7 @@ import ( e2e "github.com/cometbft/cometbft/test/e2e/pkg" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" ) const ( @@ -139,6 +142,10 @@ func Deploy(ctx context.Context, def Definition, cfg DeployConfig) (*pingpong.XD return nil, errors.Wrap(err, "start all edges") } + if err := maybeSubmitNetworkUpgrade(ctx, def); err != nil { + return nil, err + } + if err := FundValidatorsForTesting(ctx, def); err != nil { return nil, err } @@ -382,3 +389,58 @@ func checkSupportedChains(ctx context.Context, n netman.Manager) (bool, error) { return true, nil } + +// maybeSubmitNetworkUpgrade submits a network upgrade if required. +func maybeSubmitNetworkUpgrade(ctx context.Context, def Definition) error { + if def.Manifest.NetworkUpgradeHeight <= 0 { + log.Debug(ctx, "Not submitting network upgrade admin tx") + + return nil // No explicit network upgrade required. + } + + network := def.Testnet.Network + + backend, err := def.Backends().Backend(network.Static().OmniExecutionChainID) + if err != nil { + return err + } + + contract, err := bindings.NewUpgrade(common.HexToAddress(predeploys.Upgrade), backend) + if err != nil { + return err + } + + txOpts, err := backend.BindOpts(ctx, eoa.MustAddress(network, eoa.RoleAdmin)) + if err != nil { + return err + } + + height, err := backend.BlockNumber(ctx) + if err != nil { + return err + } + + const minDelay = 5 // Upgrades fail if processed too late (mempool is non-deterministic, so we need a buffer). + height += minDelay + + // If requested height is later, use that as is. + if uint64(def.Manifest.NetworkUpgradeHeight) > height { + height = uint64(def.Manifest.NetworkUpgradeHeight) + } + + log.Info(ctx, "Planning upgrade", "height", height, "name", latestUpgrade) + + tx, err := contract.PlanUpgrade(txOpts, bindings.UpgradePlan{ + Name: latestUpgrade, + Height: height, + Info: "e2e triggered upgrade", + }) + if err != nil { + return errors.Wrap(err, "plan upgrade") + } + if _, err := backend.WaitMined(ctx, tx); err != nil { + return errors.Wrap(err, "wait mined") + } + + return nil +} diff --git a/e2e/app/setup.go b/e2e/app/setup.go index e82da771f..f7460c99a 100644 --- a/e2e/app/setup.go +++ b/e2e/app/setup.go @@ -18,6 +18,7 @@ import ( "github.com/omni-network/omni/e2e/app/static" "github.com/omni-network/omni/e2e/types" "github.com/omni-network/omni/e2e/vmcompose" + uluwatu1 "github.com/omni-network/omni/halo/app/upgrades/uluwatu" halocmd "github.com/omni-network/omni/halo/cmd" halocfg "github.com/omni-network/omni/halo/config" "github.com/omni-network/omni/halo/genutil" @@ -48,6 +49,8 @@ const ( PrivvalKeyFile = "config/priv_validator_key.json" PrivvalStateFile = "data/priv_validator_state.json" + + latestUpgrade = uluwatu1.UpgradeName ) // Setup sets up the testnet configuration. @@ -87,10 +90,16 @@ func Setup(ctx context.Context, def Definition, depCfg DeployConfig) error { valPrivKeys = append(valPrivKeys, val.PrivvalKey) } + var upgrade string + if def.Manifest.NetworkUpgradeHeight == 0 { + upgrade = latestUpgrade // Genesis upgrade. + } + cosmosGenesis, err := genutil.MakeGenesis( def.Manifest.Network, time.Now(), gethGenesis.ToBlock().Hash(), + upgrade, vals...) if err != nil { return errors.Wrap(err, "make genesis") diff --git a/e2e/manifests/backwards.toml b/e2e/manifests/backwards.toml index 6aeebe7f1..790ed9d7f 100644 --- a/e2e/manifests/backwards.toml +++ b/e2e/manifests/backwards.toml @@ -3,6 +3,7 @@ network = "devnet" anvil_chains = ["mock_l1", "mock_l2"] multi_omni_evms = true +network_upgrade_height = -1 # Disable network upgrade [node.validator01] [node.validator02] diff --git a/e2e/manifests/ci.toml b/e2e/manifests/ci.toml index b2f5805ba..16436264a 100644 --- a/e2e/manifests/ci.toml +++ b/e2e/manifests/ci.toml @@ -2,7 +2,7 @@ network = "devnet" anvil_chains = ["mock_l2", "mock_l1"] multi_omni_evms = true - +network_upgrade_height = 15 pingpong_n = 5 # Increased ping pong to span validator updates [node.validator01] diff --git a/e2e/types/manifest.go b/e2e/types/manifest.go index 45f3a4578..2d437c957 100644 --- a/e2e/types/manifest.go +++ b/e2e/types/manifest.go @@ -114,6 +114,10 @@ type Manifest struct { // This allows source code defined versions for protected networks. // This overrides the --omni-image-tag if non-empty. PinnedRelayerTag string `toml:"pinned_relayer_tag"` + + // NetworkUpgradeHeight defines the network upgrade height, default is genesis, negative is disabled. + // Note that it might be scheduled at a later height. + NetworkUpgradeHeight int64 `toml:"network_upgrade_height"` } // Seeds returns a map of seed nodes by name. diff --git a/halo/app/app.go b/halo/app/app.go index 661223808..520d49e7d 100644 --- a/halo/app/app.go +++ b/halo/app/app.go @@ -101,7 +101,7 @@ func newApp( baseAppOpts ...func(*baseapp.BaseApp), ) (*App, error) { depCfg := depinject.Configs( - appConfig, + appConfig(), depinject.Provide(diProviders...), depinject.Supply( logger, @@ -183,6 +183,12 @@ func newApp( }) } + // setUpgradeHandlers should be called before `Load()` + // because StoreLoad is sealed after that + if err := app.setUpgradeHandlers(); err != nil { + return nil, errors.Wrap(err, "set upgrade handlers") + } + if err := app.Load(true); err != nil { return nil, errors.Wrap(err, "load app") } diff --git a/halo/app/app_config.go b/halo/app/app_config.go index 705ec58ae..04708b7d9 100644 --- a/halo/app/app_config.go +++ b/halo/app/app_config.go @@ -29,6 +29,7 @@ import ( txconfigv1 "cosmossdk.io/api/cosmos/tx/config/v1" upgrademodulev1 "cosmossdk.io/api/cosmos/upgrade/module/v1" "cosmossdk.io/core/appconfig" + "cosmossdk.io/depinject" evidencetypes "cosmossdk.io/x/evidence/types" upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/cosmos/cosmos-sdk/runtime" @@ -43,10 +44,11 @@ import ( const ( // TODO(corver): Maybe move these to genesis itself. - genesisVoteWindow = 64 - genesisVoteExtLimit = 256 - genesisTrimLag = 1 // Delete attestations state after each epoch, only storing the very latest attestations. - genesisCTrimLag = 72_000 // Delete consensus attestations state after +-1 day (given a period of 1.2s). + genesisVoteWindowUp uint64 = 64 // Allow early votes for + genesisVoteWindowDown uint64 = 2 // Only allow late votes for + genesisVoteExtLimit uint64 = 256 + genesisTrimLag uint64 = 1 // Allow deleting attestations in block after approval. + genesisCTrimLag uint64 = 72_000 // Delete consensus attestations state after +-1 day (given a period of 1.2s). ) //nolint:gochecknoglobals // Cosmos-style @@ -96,101 +98,104 @@ var ( } // appConfig application configuration (used by depinject). - appConfig = appconfig.Compose(&appv1alpha1.Config{ - Modules: []*appv1alpha1.ModuleConfig{ - { - Name: runtime.ModuleName, - Config: appconfig.WrapAny(&runtimev1alpha1.Module{ - AppName: Name, - BeginBlockers: beginBlockers, - PreBlockers: []string{upgradetypes.ModuleName}, - // Setting endblockers in newApp since valsync replaces staking endblocker. - InitGenesis: genesisModuleOrder, - OverrideStoreKeys: []*runtimev1alpha1.StoreKeyConfig{ - { - ModuleName: authtypes.ModuleName, - KvStoreKey: "acc", + appConfig = func() depinject.Config { + return appconfig.Compose(&appv1alpha1.Config{ + Modules: []*appv1alpha1.ModuleConfig{ + { + Name: runtime.ModuleName, + Config: appconfig.WrapAny(&runtimev1alpha1.Module{ + AppName: Name, + BeginBlockers: beginBlockers, + PreBlockers: []string{upgradetypes.ModuleName}, + // Setting endblockers in newApp since valsync replaces staking endblocker. + InitGenesis: genesisModuleOrder, + OverrideStoreKeys: []*runtimev1alpha1.StoreKeyConfig{ + { + ModuleName: authtypes.ModuleName, + KvStoreKey: "acc", + }, }, - }, - }), - }, - { - Name: authtypes.ModuleName, - Config: appconfig.WrapAny(&authmodulev1.Module{ - ModuleAccountPermissions: moduleAccPerms, - Bech32Prefix: sdk.Bech32HRP, - }), - }, - { - Name: "tx", - Config: appconfig.WrapAny(&txconfigv1.Config{ - SkipAnteHandler: true, // Disable ante handler (since we don't have proper txs). - SkipPostHandler: true, - }), - }, - { - Name: banktypes.ModuleName, - Config: appconfig.WrapAny(&bankmodulev1.Module{ - BlockedModuleAccountsOverride: blockAccAddrs, - }), - }, - { - Name: consensustypes.ModuleName, - Config: appconfig.WrapAny(&consensusmodulev1.Module{}), - }, - { - Name: distrtypes.ModuleName, - Config: appconfig.WrapAny(&distrmodulev1.Module{}), - }, - { - Name: genutiltypes.ModuleName, - Config: appconfig.WrapAny(&genutilmodulev1.Module{}), - }, - { - Name: stakingtypes.ModuleName, - Config: appconfig.WrapAny(&stakingmodulev1.Module{}), - }, - { - Name: slashingtypes.ModuleName, - Config: appconfig.WrapAny(&slashingmodulev1.Module{}), - }, - { - Name: evidencetypes.ModuleName, - Config: appconfig.WrapAny(&evidencemodulev1.Module{}), - }, - { - Name: upgradetypes.ModuleName, - Config: appconfig.WrapAny(&upgrademodulev1.Module{ - Authority: evmupgrade.ModuleName, - }), - }, - { - Name: engevmtypes.ModuleName, - Config: appconfig.WrapAny(&engevmmodule.Module{}), - }, - { - Name: attesttypes.ModuleName, - Config: appconfig.WrapAny(&attestmodule.Module{ - VoteWindow: genesisVoteWindow, - VoteExtensionLimit: genesisVoteExtLimit, - TrimLag: genesisTrimLag, - ConsensusTrimLag: genesisCTrimLag, - }), - }, - { - Name: valsynctypes.ModuleName, - Config: appconfig.WrapAny(&valsyncmodule.Module{}), - }, - { - Name: portaltypes.ModuleName, - Config: appconfig.WrapAny(&portalmodule.Module{}), - }, - { - Name: registrytypes.ModuleName, - Config: appconfig.WrapAny(®istrymodule.Module{}), - }, - }, - }) + }), + }, + { + Name: authtypes.ModuleName, + Config: appconfig.WrapAny(&authmodulev1.Module{ + ModuleAccountPermissions: moduleAccPerms, + Bech32Prefix: sdk.Bech32HRP, + }), + }, + { + Name: "tx", + Config: appconfig.WrapAny(&txconfigv1.Config{ + SkipAnteHandler: true, // Disable ante handler (since we don't have proper txs). + SkipPostHandler: true, + }), + }, + { + Name: banktypes.ModuleName, + Config: appconfig.WrapAny(&bankmodulev1.Module{ + BlockedModuleAccountsOverride: blockAccAddrs, + }), + }, + { + Name: consensustypes.ModuleName, + Config: appconfig.WrapAny(&consensusmodulev1.Module{}), + }, + { + Name: distrtypes.ModuleName, + Config: appconfig.WrapAny(&distrmodulev1.Module{}), + }, + { + Name: genutiltypes.ModuleName, + Config: appconfig.WrapAny(&genutilmodulev1.Module{}), + }, + { + Name: stakingtypes.ModuleName, + Config: appconfig.WrapAny(&stakingmodulev1.Module{}), + }, + { + Name: slashingtypes.ModuleName, + Config: appconfig.WrapAny(&slashingmodulev1.Module{}), + }, + { + Name: evidencetypes.ModuleName, + Config: appconfig.WrapAny(&evidencemodulev1.Module{}), + }, + { + Name: upgradetypes.ModuleName, + Config: appconfig.WrapAny(&upgrademodulev1.Module{ + Authority: evmupgrade.ModuleName, + }), + }, + { + Name: engevmtypes.ModuleName, + Config: appconfig.WrapAny(&engevmmodule.Module{}), + }, + { + Name: attesttypes.ModuleName, + Config: appconfig.WrapAny(&attestmodule.Module{ + VoteWindowUp: genesisVoteWindowUp, + VoteWindowDown: genesisVoteWindowDown, + VoteExtensionLimit: genesisVoteExtLimit, + TrimLag: genesisTrimLag, + ConsensusTrimLag: genesisCTrimLag, + }), + }, + { + Name: valsynctypes.ModuleName, + Config: appconfig.WrapAny(&valsyncmodule.Module{}), + }, + { + Name: portaltypes.ModuleName, + Config: appconfig.WrapAny(&portalmodule.Module{}), + }, + { + Name: registrytypes.ModuleName, + Config: appconfig.WrapAny(®istrymodule.Module{}), + }, + }, + }) + } // diProviders defines a list of depinject provider functions. // These are non-cosmos module constructors used in halo's app wiring. diff --git a/halo/app/start_test.go b/halo/app/start_test.go index 5a3945768..7a89c3e46 100644 --- a/halo/app/start_test.go +++ b/halo/app/start_test.go @@ -11,6 +11,7 @@ import ( "time" haloapp "github.com/omni-network/omni/halo/app" + uluwatu1 "github.com/omni-network/omni/halo/app/upgrades/uluwatu" atypes "github.com/omni-network/omni/halo/attest/types" halocmd "github.com/omni-network/omni/halo/cmd" halocfg "github.com/omni-network/omni/halo/config" @@ -223,9 +224,10 @@ func setupSimnet(t *testing.T) haloapp.Config { tutil.RequireNoError(t, err) err = halocmd.InitFiles(log.WithNoopLogger(context.Background()), halocmd.InitConfig{ - HomeDir: homeDir, - Network: netconf.Simnet, - ExecutionHash: executionGenesis.Hash(), + HomeDir: homeDir, + Network: netconf.Simnet, + ExecutionHash: executionGenesis.Hash(), + GenesisUpgrade: uluwatu1.UpgradeName, }) tutil.RequireNoError(t, err) diff --git a/halo/app/upgrades.go b/halo/app/upgrades.go new file mode 100644 index 000000000..74629d779 --- /dev/null +++ b/halo/app/upgrades.go @@ -0,0 +1,46 @@ +package app + +import ( + uluwatu1 "github.com/omni-network/omni/halo/app/upgrades/uluwatu" + "github.com/omni-network/omni/lib/errors" + + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" +) + +func (a App) setUpgradeHandlers() error { + upgrades := []struct { + Name string + Handler upgradetypes.UpgradeHandler + Store storetypes.StoreUpgrades + }{ + { + Name: uluwatu1.UpgradeName, + Handler: uluwatu1.CreateUpgradeHandler(a.ModuleManager, a.Configurator()), + Store: uluwatu1.StoreUpgrades, + }, + } + + for _, u := range upgrades { + a.UpgradeKeeper.SetUpgradeHandler(u.Name, u.Handler) + } + + upgradeInfo, err := a.UpgradeKeeper.ReadUpgradeInfoFromDisk() + if err != nil { + return errors.Wrap(err, "read upgrade info from disk") + } else if upgradeInfo.Name == "" { + return nil // No upgrade info found + } + + for _, u := range upgrades { + if u.Name != upgradeInfo.Name { + continue + } + + a.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &u.Store)) + + return nil + } + + return errors.New("unknown upgrade info [BUG]", "name", upgradeInfo.Name) +} diff --git a/halo/app/upgrades/uluwatu/upgrade.go b/halo/app/upgrades/uluwatu/upgrade.go new file mode 100644 index 000000000..f143aedc0 --- /dev/null +++ b/halo/app/upgrades/uluwatu/upgrade.go @@ -0,0 +1,25 @@ +// Package uluwatu defines the first omni consensus chain upgrade named after the iconic surf spot in Bali. +// It only includes attest module migration including constructor and logic changes. +// It doesn't include any store migrations. +package uluwatu + +import ( + "context" + + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +const UpgradeName = "1_uluwatu" + +var StoreUpgrades storetypes.StoreUpgrades // Zero store upgrades + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + return mm.RunMigrations(ctx, configurator, fromVM) + } +} diff --git a/halo/attest/keeper/common_test.go b/halo/attest/keeper/common_test.go index 54baf2a32..4278ae279 100644 --- a/halo/attest/keeper/common_test.go +++ b/halo/attest/keeper/common_test.go @@ -108,9 +108,10 @@ func setupKeeper(t *testing.T, expectations ...expectation) (*keeper.Keeper, sdk } } - const voteWindow = 1 + const voteWindowUp = 1 + const voteWindowDown = 0 const voteLimit = 4 - k, err := keeper.New(codec, storeSvc, m.skeeper, m.namer.ChainName, m.voter, voteWindow, voteLimit, trimLag, cTrimLag) + k, err := keeper.New(codec, storeSvc, m.skeeper, m.namer.ChainName, m.voter, voteWindowUp, voteWindowDown, voteLimit, trimLag, cTrimLag) require.NoError(t, err, "new keeper") k.SetValidatorProvider(m.valProvider) @@ -123,7 +124,7 @@ func setupKeeper(t *testing.T, expectations ...expectation) (*keeper.Keeper, sdk func dumpTables(t *testing.T, ctx sdk.Context, k *keeper.Keeper) ([]*keeper.Attestation, []*keeper.Signature) { t.Helper() var atts []*keeper.Attestation - aitr, err := k.AttestTable().List(ctx, keeper.AttestationIdIndexKey{}) + aitr, err := k.AttestTableForT().List(ctx, keeper.AttestationIdIndexKey{}) require.NoError(t, err, "list attestations") defer aitr.Close() @@ -134,7 +135,7 @@ func dumpTables(t *testing.T, ctx sdk.Context, k *keeper.Keeper) ([]*keeper.Atte } var sigs []*keeper.Signature - sitr, err := k.SignatureTable().List(ctx, keeper.SignatureIdIndexKey{}) + sitr, err := k.SignatureTableForT().List(ctx, keeper.SignatureIdIndexKey{}) require.NoError(t, err, "list signatures") defer sitr.Close() diff --git a/halo/attest/keeper/keeper.go b/halo/attest/keeper/keeper.go index d5e29634e..33240e281 100644 --- a/halo/attest/keeper/keeper.go +++ b/halo/attest/keeper/keeper.go @@ -51,10 +51,11 @@ type Keeper struct { namer types.ChainVerNameFunc voter types.Voter - voteWindow uint64 - voteExtLimit uint64 - trimLag uint64 // Non-consensus chain trim lag - cTrimLag uint64 // Consensus chain trim lag + voteWindowUp uint64 // Vote window upper bound delta + voteWindowDown uint64 // Vote window lower bound delta + voteExtLimit uint64 + trimLag uint64 // Non-consensus chain trim lag + cTrimLag uint64 // Consensus chain trim lag valAddrCache *valAddrCache } @@ -66,7 +67,8 @@ func New( skeeper baseapp.ValidatorStore, namer types.ChainVerNameFunc, voter types.Voter, - voteWindow uint64, + voteWindowUp uint64, + voteWindowDown uint64, voteExtLimit uint64, trimLag uint64, cTrimLag uint64, @@ -97,7 +99,8 @@ func New( skeeper: skeeper, namer: namer, voter: voter, - voteWindow: voteWindow, + voteWindowUp: voteWindowUp, + voteWindowDown: voteWindowDown, voteExtLimit: voteExtLimit, trimLag: trimLag, cTrimLag: cTrimLag, @@ -345,7 +348,7 @@ func (k *Keeper) Approve(ctx context.Context, valset ValSet) error { // Trim votes behind minimum vote-window minVoteWindows := make(map[xchain.ChainVersion]uint64) for chainVer, head := range approvedByChain { - minVoteWindows[chainVer] = uintSub(head, k.voteWindow) + minVoteWindows[chainVer] = umath.SubtractOrZero(head, k.voteWindowDown) } count := k.voter.TrimBehind(minVoteWindows) @@ -852,7 +855,7 @@ func (k *Keeper) windowCompare(ctx context.Context, chainVer xchain.ChainVersion latestOffset = latest.GetAttestOffset() } - return windowCompare(k.voteWindow, latestOffset, offset), nil + return windowCompare(k.voteWindowDown, k.voteWindowUp, latestOffset, offset), nil } // verifyAggVotes verifies the given aggregates votes: @@ -951,14 +954,12 @@ func (k *Keeper) deleteBefore(ctx context.Context, height uint64, consensusID ui continue } - // Never delete anything after the last approved attestation offset per chain, - // even if it is very old. Otherwise, we could introduce a gap - // once we start catching up. - // Also, don't delete anything if we don't have an approved attestation yet (leave all pending attestations). + // Wait for the attestation to exit the vote window before deleting it. + // This includes pending attestations. if latest, ok, err := latestOffset(ctx, att.XChainVersion()); err != nil { return err - } else if !ok || att.GetAttestOffset() >= latest { - continue // Skip deleting pending attestations after latest finalized (or self if latest). + } else if !ok || windowCompare(k.voteWindowDown, k.voteWindowUp, latest, att.GetAttestOffset()) >= 0 { + continue } var fuzzyDepsFound bool @@ -1123,27 +1124,17 @@ func verifyHeaderChains(ctx context.Context, cChainID uint64, registry rtypes.Po return nil } -// windowCompare returns -1 if x < mid-voteWindow, 1 if x > mid+voteWindow, else 0. -func windowCompare(voteWindow uint64, mid uint64, x uint64) int { - if x < uintSub(mid, voteWindow) { +// windowCompare returns -1 if x < mid-voteWindowDown, 1 if x > mid+voteWindowUp, else 0. +func windowCompare(voteWindowDown uint64, voteWindowUp, mid uint64, x uint64) int { + if x < umath.SubtractOrZero(mid, voteWindowDown) { return -1 - } else if x > mid+voteWindow { + } else if x > mid+voteWindowUp { return 1 } return 0 } -// uintSub returns a - b if a > b, else 0. -// Subtracting uints can result in underflow, so we need to check for that. -func uintSub(a, b uint64) uint64 { - if a <= b { - return 0 - } - - return a - b -} - // isApprovedByDifferentSet returns true if the attestation is approved by a different validator set. func isApprovedByDifferentSet(att *Attestation, valSetID uint64) bool { if att.GetStatus() != uint32(Status_Approved) { diff --git a/halo/attest/keeper/keeper_internal_test.go b/halo/attest/keeper/keeper_internal_test.go index fca323146..86557267a 100644 --- a/halo/attest/keeper/keeper_internal_test.go +++ b/halo/attest/keeper/keeper_internal_test.go @@ -19,19 +19,25 @@ import ( "github.com/stretchr/testify/require" ) -// AttestTable returns the attestations ORM table. -func (k *Keeper) AttestTable() AttestationTable { +// AttestTable returns the attestations ORM table for testing purposes. +func (k *Keeper) AttestTableForT() AttestationTable { return k.attTable } -// SignatureTable returns the attestations ORM table. -func (k *Keeper) SignatureTable() SignatureTable { +// SignatureTable returns the attestations ORM table for testing purposes. +func (k *Keeper) SignatureTableForT() SignatureTable { return k.sigTable } +// SetVoteWindowDownForT sets vote window down for testing purposes. +func (k *Keeper) SetVoteWindowDownForT(voteWindowDown uint64) { + k.voteWindowDown = voteWindowDown +} + func TestWindowCompose(t *testing.T) { t.Parallel() - const window = 64 + const windowUp = 64 + const windowDown = 8 const mid = 32 const bigMid = 256 @@ -47,18 +53,18 @@ func TestWindowCompose(t *testing.T) { {mid, mid, in}, {mid, mid + 1, in}, {mid, mid - 1, in}, - {mid, mid + window, in}, - {mid, 0, in}, - {mid, mid + window + 1, above}, - {mid, mid + window + window, above}, - {bigMid, bigMid - window - 1, below}, - {bigMid, bigMid - window - window, below}, + {mid, mid + windowUp, in}, + {mid, 0, below}, + {mid, mid + windowUp + 1, above}, + {mid, mid + windowUp + windowUp, above}, + {bigMid, bigMid - windowDown - 1, below}, + {bigMid, bigMid - windowDown - windowDown, below}, } for i, tt := range tests { t.Run(fmt.Sprintf("mid_%d_target_%d", tt.Mid, tt.Target), func(t *testing.T) { t.Parallel() - got := windowCompare(window, tt.Mid, tt.Target) + got := windowCompare(windowDown, windowUp, tt.Mid, tt.Target) if got != tt.Expected { t.Errorf("Test %d: Expected %d, got %d", i, tt.Expected, got) } @@ -169,7 +175,8 @@ func TestVerifyAggVotes(t *testing.T) { portalRegistry: portalReg, namer: netconf.SimnetNetwork().ChainVersionName, voter: nil, - voteWindow: 0, + voteWindowUp: 0, + voteWindowDown: 0, voteExtLimit: voteExtLimit, } diff --git a/halo/attest/keeper/keeper_test.go b/halo/attest/keeper/keeper_test.go index f3e348eea..c6316a1b8 100644 --- a/halo/attest/keeper/keeper_test.go +++ b/halo/attest/keeper/keeper_test.go @@ -398,7 +398,7 @@ func TestKeeper_Approve(t *testing.T) { // add sig from val3 sig := expectValSig(0, 1, val3, defaultOffset) - err = k.SignatureTable().Insert(ctx, sig) + err = k.SignatureTableForT().Insert(ctx, sig) require.NoError(t, err) }, }, @@ -529,7 +529,7 @@ func TestKeeper_Approve(t *testing.T) { err = k.Approve(ctx, toValSet(valset1_2)) require.NoError(t, err) - // Begin the block at height 20, which should cause the first 2 attestations to be deleted, but not the third and fourth + // Begin the block at height 20, which should cause the first 3 attestations to be deleted, but not the fourth err = k.BeginBlock(ctx.WithBlockHeight(initHeight + 10)) require.NoError(t, err) }, @@ -547,6 +547,73 @@ func TestKeeper_Approve(t *testing.T) { }, }, }, + { + name: "dont_delete_vote_window", + expectations: []expectation{ + namerCalled(1), + defaultExpectations, + activeSetQueried(9), + activeSetQueried(10), + activeSetQueried(17), + activeSetQueried(18), + trimBehindCalled(), + valsetCalled(), + noFuzzyDeps(), + }, + prerequisites: []prerequisite{ + func(t *testing.T, k *keeper.Keeper, ctx sdk.Context) { + t.Helper() + k.SetVoteWindowDownForT(2) // Change voteWindowDown from 0 to 2, so less atts are deleted. + + initHeight := int64(10) + vote1 := defaultAggVote().WithAttestOfset(defaultOffset).Vote() + msg1 := defaultMsg().Default().WithVotes(vote1).Msg() + err := k.Add(ctx.WithBlockHeight(initHeight), msg1) + require.NoError(t, err) + + vote2 := defaultAggVote().WithAttestOfset(defaultOffset + 1).Vote() + msg2 := defaultMsg().Default().WithVotes(vote2).Msg() + err = k.Add(ctx.WithBlockHeight(initHeight+1), msg2) + require.NoError(t, err) + + vote3 := defaultAggVote().WithAttestOfset(defaultOffset + 2).Vote() + msg3 := defaultMsg().Default().WithVotes(vote3).Msg() + err = k.Add(ctx.WithBlockHeight(initHeight+8), msg3) + require.NoError(t, err) + + vote4 := defaultAggVote().WithAttestOfset(defaultOffset + 3).Vote() + msg4 := defaultMsg().Default().WithVotes(vote4).Msg() + err = k.Add(ctx.WithBlockHeight(initHeight+9), msg4) + require.NoError(t, err) + + // Approve all four attestations so they're no longer pending + err = k.Approve(ctx, toValSet(valset1_2)) + require.NoError(t, err) + + // Begin the block at height 20, which should cause the only first attestations to be deleted, but not the others (due to increased vote window) + err = k.BeginBlock(ctx.WithBlockHeight(initHeight + 10)) + require.NoError(t, err) + }, + }, + args: args{ + valset: valset1_2, + }, + want: want{ + atts: []*keeper.Attestation{ + expectApprovedAtt(2, defaultOffset+1, valset1_2, 11), + expectApprovedAtt(3, defaultOffset+2, valset1_2, 18), + expectApprovedAtt(4, defaultOffset+3, valset1_2, 19), + }, + sigs: []*keeper.Signature{ + expectValSig(3, 2, val1, defaultOffset+1), + expectValSig(4, 2, val2, defaultOffset+1), + expectValSig(5, 3, val1, defaultOffset+2), + expectValSig(6, 3, val2, defaultOffset+2), + expectValSig(7, 4, val1, defaultOffset+3), + expectValSig(8, 4, val2, defaultOffset+3), + }, + }, + }, { name: "dont_delete_latest", expectations: []expectation{ diff --git a/halo/attest/module/migrations.go b/halo/attest/module/migrations.go new file mode 100644 index 000000000..037310a8e --- /dev/null +++ b/halo/attest/module/migrations.go @@ -0,0 +1,32 @@ +package module + +import ( + "github.com/omni-network/omni/halo/attest/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +// noopMigration doesn't perform any store migrations. +var noopMigration = func(_ sdk.Context) error { return nil } + +func registerMigrations(cfg module.Configurator) { + migrations := []struct { + FromVersion uint64 + Handler module.MigrationHandler + }{ + { + // 1_uluwatu doesn't include any store migrations. + // It only includes constructor and logic changes. + FromVersion: 1, + Handler: noopMigration, + }, + } + + for _, m := range migrations { + err := cfg.RegisterMigration(types.ModuleName, m.FromVersion, m.Handler) + if err != nil { + panic(err) + } + } +} diff --git a/halo/attest/module/module.go b/halo/attest/module/module.go index d07aab53d..ad4077eb2 100644 --- a/halo/attest/module/module.go +++ b/halo/attest/module/module.go @@ -18,7 +18,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/runtime" ) -const ConsensusVersion = 1 +const ConsensusVersion = 2 var ( _ module.AppModuleBasic = (*AppModule)(nil) @@ -95,6 +95,7 @@ func (m AppModule) EndBlock(ctx context.Context) error { func (m AppModule) RegisterServices(cfg module.Configurator) { types.RegisterMsgServiceServer(cfg.MsgServer(), keeper.NewMsgServerImpl(m.keeper)) types.RegisterQueryServer(cfg.QueryServer(), m.keeper) + registerMigrations(cfg) } // IsOnePerModuleType implements the depinject.OnePerModuleType interface. @@ -144,7 +145,8 @@ func ProvideModule(in ModuleInputs) (ModuleOutputs, error) { in.SKeeper, in.Namer, in.Voter, - in.Config.GetVoteWindow(), + in.Config.GetVoteWindowUp(), + in.Config.GetVoteWindowDown(), in.Config.GetVoteExtensionLimit(), in.Config.GetTrimLag(), in.Config.GetConsensusTrimLag(), diff --git a/halo/attest/module/module.proto b/halo/attest/module/module.proto index 5d731d7b6..e9c4f3e12 100644 --- a/halo/attest/module/module.proto +++ b/halo/attest/module/module.proto @@ -15,16 +15,20 @@ message Module { // authority defines the custom module authority. If not set, defaults to the governance module. string authority = 1; - // vote_window defines the number of blocks before and after the latest approved attestation + // vote_window_up defines the number of blocks before (higher than) the latest approved attestation // that votes are allowed for. - uint64 vote_window = 2; + uint64 vote_window_up = 2; + + // vote_window_down defines the number of blocks after (lower than) the latest approved attestation + // that votes are allowed for. + uint64 vote_window_down = 3; // vote_extension_limit defines the maximum number of votes that a validator may include in a single vote extension. - uint64 vote_extension_limit = 3; + uint64 vote_extension_limit = 4; // trim_lag defines the number of blocks after which non-consensus-chain attestations are deleted from the module state. - uint64 trim_lag = 4; + uint64 trim_lag = 5; // consensus_trim_lag defines the number of blocks after which consensus-chain attestations are deleted from the module state. - uint64 consensus_trim_lag = 5; + uint64 consensus_trim_lag = 6; } \ No newline at end of file diff --git a/halo/attest/module/module.pulsar.go b/halo/attest/module/module.pulsar.go index 814dbff32..7941e484f 100644 --- a/halo/attest/module/module.pulsar.go +++ b/halo/attest/module/module.pulsar.go @@ -16,7 +16,8 @@ import ( var ( md_Module protoreflect.MessageDescriptor fd_Module_authority protoreflect.FieldDescriptor - fd_Module_vote_window protoreflect.FieldDescriptor + fd_Module_vote_window_up protoreflect.FieldDescriptor + fd_Module_vote_window_down protoreflect.FieldDescriptor fd_Module_vote_extension_limit protoreflect.FieldDescriptor fd_Module_trim_lag protoreflect.FieldDescriptor fd_Module_consensus_trim_lag protoreflect.FieldDescriptor @@ -26,7 +27,8 @@ func init() { file_halo_attest_module_module_proto_init() md_Module = File_halo_attest_module_module_proto.Messages().ByName("Module") fd_Module_authority = md_Module.Fields().ByName("authority") - fd_Module_vote_window = md_Module.Fields().ByName("vote_window") + fd_Module_vote_window_up = md_Module.Fields().ByName("vote_window_up") + fd_Module_vote_window_down = md_Module.Fields().ByName("vote_window_down") fd_Module_vote_extension_limit = md_Module.Fields().ByName("vote_extension_limit") fd_Module_trim_lag = md_Module.Fields().ByName("trim_lag") fd_Module_consensus_trim_lag = md_Module.Fields().ByName("consensus_trim_lag") @@ -103,9 +105,15 @@ func (x *fastReflection_Module) Range(f func(protoreflect.FieldDescriptor, proto return } } - if x.VoteWindow != uint64(0) { - value := protoreflect.ValueOfUint64(x.VoteWindow) - if !f(fd_Module_vote_window, value) { + if x.VoteWindowUp != uint64(0) { + value := protoreflect.ValueOfUint64(x.VoteWindowUp) + if !f(fd_Module_vote_window_up, value) { + return + } + } + if x.VoteWindowDown != uint64(0) { + value := protoreflect.ValueOfUint64(x.VoteWindowDown) + if !f(fd_Module_vote_window_down, value) { return } } @@ -144,8 +152,10 @@ func (x *fastReflection_Module) Has(fd protoreflect.FieldDescriptor) bool { switch fd.FullName() { case "halo.attest.module.Module.authority": return x.Authority != "" - case "halo.attest.module.Module.vote_window": - return x.VoteWindow != uint64(0) + case "halo.attest.module.Module.vote_window_up": + return x.VoteWindowUp != uint64(0) + case "halo.attest.module.Module.vote_window_down": + return x.VoteWindowDown != uint64(0) case "halo.attest.module.Module.vote_extension_limit": return x.VoteExtensionLimit != uint64(0) case "halo.attest.module.Module.trim_lag": @@ -170,8 +180,10 @@ func (x *fastReflection_Module) Clear(fd protoreflect.FieldDescriptor) { switch fd.FullName() { case "halo.attest.module.Module.authority": x.Authority = "" - case "halo.attest.module.Module.vote_window": - x.VoteWindow = uint64(0) + case "halo.attest.module.Module.vote_window_up": + x.VoteWindowUp = uint64(0) + case "halo.attest.module.Module.vote_window_down": + x.VoteWindowDown = uint64(0) case "halo.attest.module.Module.vote_extension_limit": x.VoteExtensionLimit = uint64(0) case "halo.attest.module.Module.trim_lag": @@ -197,8 +209,11 @@ func (x *fastReflection_Module) Get(descriptor protoreflect.FieldDescriptor) pro case "halo.attest.module.Module.authority": value := x.Authority return protoreflect.ValueOfString(value) - case "halo.attest.module.Module.vote_window": - value := x.VoteWindow + case "halo.attest.module.Module.vote_window_up": + value := x.VoteWindowUp + return protoreflect.ValueOfUint64(value) + case "halo.attest.module.Module.vote_window_down": + value := x.VoteWindowDown return protoreflect.ValueOfUint64(value) case "halo.attest.module.Module.vote_extension_limit": value := x.VoteExtensionLimit @@ -231,8 +246,10 @@ func (x *fastReflection_Module) Set(fd protoreflect.FieldDescriptor, value proto switch fd.FullName() { case "halo.attest.module.Module.authority": x.Authority = value.Interface().(string) - case "halo.attest.module.Module.vote_window": - x.VoteWindow = value.Uint() + case "halo.attest.module.Module.vote_window_up": + x.VoteWindowUp = value.Uint() + case "halo.attest.module.Module.vote_window_down": + x.VoteWindowDown = value.Uint() case "halo.attest.module.Module.vote_extension_limit": x.VoteExtensionLimit = value.Uint() case "halo.attest.module.Module.trim_lag": @@ -261,8 +278,10 @@ func (x *fastReflection_Module) Mutable(fd protoreflect.FieldDescriptor) protore switch fd.FullName() { case "halo.attest.module.Module.authority": panic(fmt.Errorf("field authority of message halo.attest.module.Module is not mutable")) - case "halo.attest.module.Module.vote_window": - panic(fmt.Errorf("field vote_window of message halo.attest.module.Module is not mutable")) + case "halo.attest.module.Module.vote_window_up": + panic(fmt.Errorf("field vote_window_up of message halo.attest.module.Module is not mutable")) + case "halo.attest.module.Module.vote_window_down": + panic(fmt.Errorf("field vote_window_down of message halo.attest.module.Module is not mutable")) case "halo.attest.module.Module.vote_extension_limit": panic(fmt.Errorf("field vote_extension_limit of message halo.attest.module.Module is not mutable")) case "halo.attest.module.Module.trim_lag": @@ -284,7 +303,9 @@ func (x *fastReflection_Module) NewField(fd protoreflect.FieldDescriptor) protor switch fd.FullName() { case "halo.attest.module.Module.authority": return protoreflect.ValueOfString("") - case "halo.attest.module.Module.vote_window": + case "halo.attest.module.Module.vote_window_up": + return protoreflect.ValueOfUint64(uint64(0)) + case "halo.attest.module.Module.vote_window_down": return protoreflect.ValueOfUint64(uint64(0)) case "halo.attest.module.Module.vote_extension_limit": return protoreflect.ValueOfUint64(uint64(0)) @@ -365,8 +386,11 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { if l > 0 { n += 1 + l + runtime.Sov(uint64(l)) } - if x.VoteWindow != 0 { - n += 1 + runtime.Sov(uint64(x.VoteWindow)) + if x.VoteWindowUp != 0 { + n += 1 + runtime.Sov(uint64(x.VoteWindowUp)) + } + if x.VoteWindowDown != 0 { + n += 1 + runtime.Sov(uint64(x.VoteWindowDown)) } if x.VoteExtensionLimit != 0 { n += 1 + runtime.Sov(uint64(x.VoteExtensionLimit)) @@ -409,20 +433,25 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { if x.ConsensusTrimLag != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.ConsensusTrimLag)) i-- - dAtA[i] = 0x28 + dAtA[i] = 0x30 } if x.TrimLag != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.TrimLag)) i-- - dAtA[i] = 0x20 + dAtA[i] = 0x28 } if x.VoteExtensionLimit != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.VoteExtensionLimit)) i-- + dAtA[i] = 0x20 + } + if x.VoteWindowDown != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.VoteWindowDown)) + i-- dAtA[i] = 0x18 } - if x.VoteWindow != 0 { - i = runtime.EncodeVarint(dAtA, i, uint64(x.VoteWindow)) + if x.VoteWindowUp != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.VoteWindowUp)) i-- dAtA[i] = 0x10 } @@ -516,9 +545,9 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { iNdEx = postIndex case 2: if wireType != 0 { - return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field VoteWindow", wireType) + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field VoteWindowUp", wireType) } - x.VoteWindow = 0 + x.VoteWindowUp = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow @@ -528,12 +557,31 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { } b := dAtA[iNdEx] iNdEx++ - x.VoteWindow |= uint64(b&0x7F) << shift + x.VoteWindowUp |= uint64(b&0x7F) << shift if b < 0x80 { break } } case 3: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field VoteWindowDown", wireType) + } + x.VoteWindowDown = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.VoteWindowDown |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: if wireType != 0 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field VoteExtensionLimit", wireType) } @@ -552,7 +600,7 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { break } } - case 4: + case 5: if wireType != 0 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field TrimLag", wireType) } @@ -571,7 +619,7 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { break } } - case 5: + case 6: if wireType != 0 { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ConsensusTrimLag", wireType) } @@ -646,15 +694,18 @@ type Module struct { // authority defines the custom module authority. If not set, defaults to the governance module. Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` - // vote_window defines the number of blocks before and after the latest approved attestation + // vote_window_up defines the number of blocks before (higher than) the latest approved attestation // that votes are allowed for. - VoteWindow uint64 `protobuf:"varint,2,opt,name=vote_window,json=voteWindow,proto3" json:"vote_window,omitempty"` + VoteWindowUp uint64 `protobuf:"varint,2,opt,name=vote_window_up,json=voteWindowUp,proto3" json:"vote_window_up,omitempty"` + // vote_window_down defines the number of blocks after (lower than) the latest approved attestation + // that votes are allowed for. + VoteWindowDown uint64 `protobuf:"varint,3,opt,name=vote_window_down,json=voteWindowDown,proto3" json:"vote_window_down,omitempty"` // vote_extension_limit defines the maximum number of votes that a validator may include in a single vote extension. - VoteExtensionLimit uint64 `protobuf:"varint,3,opt,name=vote_extension_limit,json=voteExtensionLimit,proto3" json:"vote_extension_limit,omitempty"` + VoteExtensionLimit uint64 `protobuf:"varint,4,opt,name=vote_extension_limit,json=voteExtensionLimit,proto3" json:"vote_extension_limit,omitempty"` // trim_lag defines the number of blocks after which non-consensus-chain attestations are deleted from the module state. - TrimLag uint64 `protobuf:"varint,4,opt,name=trim_lag,json=trimLag,proto3" json:"trim_lag,omitempty"` + TrimLag uint64 `protobuf:"varint,5,opt,name=trim_lag,json=trimLag,proto3" json:"trim_lag,omitempty"` // consensus_trim_lag defines the number of blocks after which consensus-chain attestations are deleted from the module state. - ConsensusTrimLag uint64 `protobuf:"varint,5,opt,name=consensus_trim_lag,json=consensusTrimLag,proto3" json:"consensus_trim_lag,omitempty"` + ConsensusTrimLag uint64 `protobuf:"varint,6,opt,name=consensus_trim_lag,json=consensusTrimLag,proto3" json:"consensus_trim_lag,omitempty"` } func (x *Module) Reset() { @@ -684,9 +735,16 @@ func (x *Module) GetAuthority() string { return "" } -func (x *Module) GetVoteWindow() uint64 { +func (x *Module) GetVoteWindowUp() uint64 { + if x != nil { + return x.VoteWindowUp + } + return 0 +} + +func (x *Module) GetVoteWindowDown() uint64 { if x != nil { - return x.VoteWindow + return x.VoteWindowDown } return 0 } @@ -720,34 +778,37 @@ var file_halo_attest_module_module_proto_rawDesc = []byte{ 0x6f, 0x12, 0x12, 0x68, 0x61, 0x6c, 0x6f, 0x2e, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x1a, 0x20, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf4, 0x01, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa3, 0x02, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x6f, 0x74, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x76, 0x6f, 0x74, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, - 0x77, 0x12, 0x30, 0x0a, 0x14, 0x76, 0x6f, 0x74, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, - 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x12, 0x76, 0x6f, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x69, 0x6d, 0x5f, 0x6c, 0x61, 0x67, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x72, 0x69, 0x6d, 0x4c, 0x61, 0x67, 0x12, 0x2c, - 0x0a, 0x12, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x5f, 0x74, 0x72, 0x69, 0x6d, - 0x5f, 0x6c, 0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, - 0x65, 0x6e, 0x73, 0x75, 0x73, 0x54, 0x72, 0x69, 0x6d, 0x4c, 0x61, 0x67, 0x3a, 0x30, 0xba, 0xc0, - 0x96, 0xda, 0x01, 0x2a, 0x0a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6f, 0x6d, - 0x6e, 0x69, 0x2f, 0x68, 0x61, 0x6c, 0x6f, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x42, 0xb4, - 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x6c, 0x6f, 0x2e, 0x61, 0x74, 0x74, 0x65, - 0x73, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, - 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x23, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, - 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x61, 0x6c, 0x6f, 0x2f, - 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xa2, 0x02, 0x03, - 0x48, 0x41, 0x4d, 0xaa, 0x02, 0x12, 0x48, 0x61, 0x6c, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, - 0x74, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xca, 0x02, 0x12, 0x48, 0x61, 0x6c, 0x6f, 0x5c, - 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xe2, 0x02, 0x1e, - 0x48, 0x61, 0x6c, 0x6f, 0x5c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x5c, 0x4d, 0x6f, 0x64, 0x75, - 0x6c, 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x14, 0x48, 0x61, 0x6c, 0x6f, 0x3a, 0x3a, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x3a, 0x3a, 0x4d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x24, 0x0a, 0x0e, 0x76, 0x6f, 0x74, 0x65, 0x5f, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, + 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x76, 0x6f, 0x74, 0x65, 0x57, 0x69, + 0x6e, 0x64, 0x6f, 0x77, 0x55, 0x70, 0x12, 0x28, 0x0a, 0x10, 0x76, 0x6f, 0x74, 0x65, 0x5f, 0x77, + 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0e, 0x76, 0x6f, 0x74, 0x65, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x44, 0x6f, 0x77, 0x6e, + 0x12, 0x30, 0x0a, 0x14, 0x76, 0x6f, 0x74, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, + 0x76, 0x6f, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x69, 0x6d, 0x5f, 0x6c, 0x61, 0x67, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x72, 0x69, 0x6d, 0x4c, 0x61, 0x67, 0x12, 0x2c, 0x0a, + 0x12, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x5f, 0x74, 0x72, 0x69, 0x6d, 0x5f, + 0x6c, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x65, + 0x6e, 0x73, 0x75, 0x73, 0x54, 0x72, 0x69, 0x6d, 0x4c, 0x61, 0x67, 0x3a, 0x30, 0xba, 0xc0, 0x96, + 0xda, 0x01, 0x2a, 0x0a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x6f, 0x6d, 0x6e, 0x69, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6f, 0x6d, 0x6e, + 0x69, 0x2f, 0x68, 0x61, 0x6c, 0x6f, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x42, 0xb4, 0x01, + 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x6c, 0x6f, 0x2e, 0x61, 0x74, 0x74, 0x65, 0x73, + 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x23, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, + 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x61, 0x6c, 0x6f, 0x2f, 0x61, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xa2, 0x02, 0x03, 0x48, + 0x41, 0x4d, 0xaa, 0x02, 0x12, 0x48, 0x61, 0x6c, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, + 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xca, 0x02, 0x12, 0x48, 0x61, 0x6c, 0x6f, 0x5c, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0xe2, 0x02, 0x1e, 0x48, + 0x61, 0x6c, 0x6f, 0x5c, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x14, + 0x48, 0x61, 0x6c, 0x6f, 0x3a, 0x3a, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x3a, 0x3a, 0x4d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/halo/cmd/init.go b/halo/cmd/init.go index 321fd3c3d..4514ca1e3 100644 --- a/halo/cmd/init.go +++ b/halo/cmd/init.go @@ -33,17 +33,18 @@ const maxPeers = 5 // Limit the amount of peers to add to the address book. // InitConfig is the config for the init command. type InitConfig struct { - HomeDir string - Moniker string - Network netconf.ID - TrustedSync bool - AddrBook bool - LogCfgFunc func(*log.Config) - HaloCfgFunc func(*halocfg.Config) - CometCfgFunc func(*cmtconfig.Config) - Force bool - Clean bool - ExecutionHash common.Hash + HomeDir string + Moniker string + Network netconf.ID + TrustedSync bool + AddrBook bool + LogCfgFunc func(*log.Config) + HaloCfgFunc func(*halocfg.Config) + CometCfgFunc func(*cmtconfig.Config) + Force bool + Clean bool + ExecutionHash common.Hash + GenesisUpgrade string // Zero value omits network upgrade genesis tx } func (c InitConfig) Verify() error { @@ -294,7 +295,7 @@ func InitFiles(ctx context.Context, initCfg InitConfig) error { return errors.Wrap(err, "get public key") } - cosmosGen, err := genutil.MakeGenesis(network, time.Now(), initCfg.ExecutionHash, pubKey) + cosmosGen, err := genutil.MakeGenesis(network, time.Now(), initCfg.ExecutionHash, initCfg.GenesisUpgrade, pubKey) if err != nil { return err } diff --git a/halo/genutil/genutil.go b/halo/genutil/genutil.go index 5f50474ad..6f7f04352 100644 --- a/halo/genutil/genutil.go +++ b/halo/genutil/genutil.go @@ -7,6 +7,7 @@ import ( "time" attesttypes "github.com/omni-network/omni/halo/attest/types" + "github.com/omni-network/omni/halo/evmupgrade" vtypes "github.com/omni-network/omni/halo/valsync/types" "github.com/omni-network/omni/lib/buildinfo" "github.com/omni-network/omni/lib/errors" @@ -27,6 +28,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" cosmosstd "github.com/cosmos/cosmos-sdk/std" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" atypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -52,6 +54,7 @@ func MakeGenesis( network netconf.ID, genesisTime time.Time, executionBlockHash common.Hash, + upgradeName string, valPubkeys ...crypto.PubKey, ) (*gtypes.AppGenesis, error) { cdc := getCodec() @@ -85,17 +88,25 @@ func MakeGenesis( } // Step 3: Create the genesis validators; genesis account and a MsgCreateValidator. - valTxs := make([]sdk.Tx, 0, len(valPubkeys)) + var genTxs []sdk.Tx for _, pubkey := range sortByAddress(valPubkeys) { tx, err := addValidator(txConfig, pubkey, cdc, tempFile.Name()) if err != nil { return nil, errors.Wrap(err, "add validator") } - valTxs = append(valTxs, tx) + genTxs = append(genTxs, tx) + } + + if upgradeName != "" { + tx, err := addUpgrade(txConfig, upgradeName) + if err != nil { + return nil, errors.Wrap(err, "add upgrade") + } + genTxs = append(genTxs, tx) } // Step 4: Collect the MsgCreateValidator txs and update the app state (again). - appState2, err := collectGenTxs(cdc, txConfig, tempFile.Name(), valTxs) + appState2, err := collectGenTxs(cdc, txConfig, tempFile.Name(), genTxs) if err != nil { return nil, errors.Wrap(err, "collect genesis transactions") } @@ -226,6 +237,25 @@ func addValidator(txConfig client.TxConfig, pubkey crypto.PubKey, cdc codec.Code return builder.GetTx(), nil } +func addUpgrade(txConfig client.TxConfig, name string) (sdk.Tx, error) { + msg := &utypes.MsgSoftwareUpgrade{ + Authority: sdk.AccAddress(address.Module(evmupgrade.ModuleName)).String(), + Plan: utypes.Plan{ + Name: name, + Height: 1, + Info: "genesis upgrade", + }, + } + + builder := txConfig.NewTxBuilder() + + if err := builder.SetMsgs(msg); err != nil { + return nil, errors.Wrap(err, "set message") + } + + return builder.GetTx(), nil +} + // defaultAppState returns the default genesis application state. func defaultAppState( maxVals uint32, @@ -277,6 +307,7 @@ func getCodec() *codec.ProtoCodec { etypes.RegisterInterfaces(reg) evmtypes.RegisterInterfaces(reg) attesttypes.RegisterInterfaces(reg) + utypes.RegisterInterfaces(reg) return codec.NewProtoCodec(reg) } diff --git a/halo/genutil/genutil_test.go b/halo/genutil/genutil_test.go index aa477d872..e0e9bb35b 100644 --- a/halo/genutil/genutil_test.go +++ b/halo/genutil/genutil_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + uluwatu1 "github.com/omni-network/omni/halo/app/upgrades/uluwatu" "github.com/omni-network/omni/halo/genutil" "github.com/omni-network/omni/lib/netconf" "github.com/omni-network/omni/lib/tutil" @@ -26,7 +27,7 @@ func TestMakeGenesis(t *testing.T) { executionBlockHash := common.BytesToHash([]byte("blockhash")) - resp, err := genutil.MakeGenesis(netconf.Simnet, timestamp, executionBlockHash, val1, val2) + resp, err := genutil.MakeGenesis(netconf.Simnet, timestamp, executionBlockHash, uluwatu1.UpgradeName, val1, val2) tutil.RequireNoError(t, err) tutil.RequireGoldenJSON(t, resp) diff --git a/halo/genutil/testdata/TestMakeGenesis.golden b/halo/genutil/testdata/TestMakeGenesis.golden index 58ea7eac2..bb5102221 100644 --- a/halo/genutil/testdata/TestMakeGenesis.golden +++ b/halo/genutil/testdata/TestMakeGenesis.golden @@ -185,6 +185,38 @@ "tip": null }, "signatures": [] + }, + { + "body": { + "messages": [ + { + "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", + "authority": "omni1h0hvs2xgpfxg304npntxzpuddttp5n8lcmpz6u", + "plan": { + "name": "1_uluwatu", + "time": "0001-01-01T00:00:00Z", + "height": "1", + "info": "genesis upgrade", + "upgraded_client_state": null + } + } + ], + "memo": "", + "timeout_height": "0", + "extension_options": [], + "non_critical_extension_options": [] + }, + "auth_info": { + "signer_infos": [], + "fee": { + "amount": [], + "gas_limit": "0", + "payer": "", + "granter": "" + }, + "tip": null + }, + "signatures": [] } ] }, diff --git a/scripts/halovisor/Dockerfile b/scripts/halovisor/Dockerfile index 984f818a8..9b0acffac 100644 --- a/scripts/halovisor/Dockerfile +++ b/scripts/halovisor/Dockerfile @@ -1,10 +1,12 @@ # Docker build args ARG OMNI_COSMOVISOR_VERSION=v0.1.0 ARG HALO_VERSION_0_GENESIS=v0.8.0 +ARG HALO_VERSION_1_ULUWATU=main # Build stages FROM omniops/cosmovisor:${OMNI_COSMOVISOR_VERSION} AS build-cosmovisor FROM omniops/halo:${HALO_VERSION_0_GENESIS} AS build-0-genesis +FROM omniops/halo:${HALO_VERSION_1_ULUWATU} AS build-1-uluwatu # Runtime stage FROM scratch AS runtime @@ -30,6 +32,7 @@ VOLUME /halo # Copy binaries from build stages. COPY --from=build-cosmovisor /ko-app/cosmovisor /usr/local/bin/cosmovisor COPY --from=build-0-genesis /app /halovisor/genesis/bin/halo +COPY --from=build-1-uluwatu /app /halovisor/upgrades/1_uluwatu/bin/halo # Cosmovisor is the entrypoint ENTRYPOINT [ "cosmovisor" ] diff --git a/scripts/halovisor/build.sh b/scripts/halovisor/build.sh index 4f6751f7e..2a045d4b5 100755 --- a/scripts/halovisor/build.sh +++ b/scripts/halovisor/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# ./build.sh +# ./build.sh # This scripts builds the halovisor docker image # Halovisor wraps cosmovisor and multiple halo versions into a single docker image. # It allows for docker based deployments that support halo network upgrades. @@ -9,24 +9,26 @@ set -e SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) HALO_VERSION_0_GENESIS="${1}" -if [ -z "${HALO_VERSION_0_GENESIS}" ]; then - HALO_VERSION_0_GENESIS=$(git rev-parse --short=7 HEAD) - echo "Using head as HALO_VERSION_GENESIS: ${HALO_VERSION_0_GENESIS}" +if [ -z "$HALO_VERSION_0_GENESIS" ]; then + HALO_VERSION_0_GENESIS=v0.8.0 + echo "Using HALO_VERSION_GENESIS: ${HALO_VERSION_0_GENESIS}" fi -IMAGEREF="omniops/halovisor:${HALO_VERSION_0_GENESIS}" +HALO_VERSION_1_ULUWATU="${2}" +if [ -z "$HALO_VERSION_1_ULUWATU" ]; then + HALO_VERSION_1_ULUWATU=$(git rev-parse --short=7 HEAD) + echo "Using head as HALO_VERSION_ULUWATU: ${HALO_VERSION_1_ULUWATU}" +fi + +IMAGEREF="omniops/halovisor:${HALO_VERSION_1_ULUWATU}" IMAGEMAIN="omniops/halovisor:main" docker build \ --build-arg HALO_VERSION_0_GENESIS="${HALO_VERSION_0_GENESIS}" \ + --build-arg HALO_VERSION_1_ULUWATU="${HALO_VERSION_1_ULUWATU}" \ -t "${IMAGEREF}" \ -t "${IMAGEMAIN}" \ "${SCRIPT_DIR}" echo "Built docker image: ${IMAGEREF}" echo "Built docker image: ${IMAGEMAIN}" - -# TODOs: -# - Add support for multiple halo versions/upgrades -# - Add support for official releases -# - Support multiple architectures