From 40d87684c5f7ecd605394e215379ec8ce747cbe9 Mon Sep 17 00:00:00 2001 From: Mateusz Kaczanowski Date: Sat, 9 Nov 2024 12:00:28 +0100 Subject: [PATCH] support governance type upgrades from non chain providers --- README.md | 7 ++--- internal/pkg/daemon/daemon_test.go | 25 +++++++++++++++- internal/pkg/state_machine/state_machine.go | 15 +++++++++- .../pkg/state_machine/state_machine_test.go | 29 +++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3853047..5dfbfad 100644 --- a/README.md +++ b/README.md @@ -221,11 +221,10 @@ Remember that Blazar refreshes its internal state periodically. If you registere
Does Blazar work with chains with non-standard gov module (e.g., Neutron)? -Yes, but with limitations. Neutron is a smart contract chain that implements its own governance (DAO DAO) via an on-chain contract. Blazar currently doesn't understand the custom smart contract logic, therefore the operator cannot use the `CHAIN` provider. However, upgrades can still be executed via: -1. `NON_GOVERNANCE_COORDINATED` - a network operator registers the upgrade at a certain height. -2. `upgrade-info.json` - the Neutron node will put the `upgrade-info.json` on disk prior to the upgrade. A network operator must register a docker version tag for the expected upgrade height. +Yes, but you'll need to register manually a `GOVERNANCE` type upgrade in `LOCAL` or `DATABASE` provider. -The downside of option 2 is the lack of pre-upgrade checks. +Neutron is a smart contract chain that implements its own governance (DAO DAO) via an on-chain contract. Blazar currently doesn't understand the custom smart contract logic, therefore the operator cannot use the `CHAIN` provider. +However, the Neutron governance is integrated with Cosmos SDK upgrades module and will output the `upgrade-info.json` at the upgrade height. Therefore from Blazar perspective, the `GOVERNANCE` type is valid, but the source provide must be different.
diff --git a/internal/pkg/daemon/daemon_test.go b/internal/pkg/daemon/daemon_test.go index 040a1c6..fd34d0c 100644 --- a/internal/pkg/daemon/daemon_test.go +++ b/internal/pkg/daemon/daemon_test.go @@ -24,6 +24,7 @@ import ( "blazar/internal/pkg/log/logger" "blazar/internal/pkg/log/notification" "blazar/internal/pkg/metrics" + checksproto "blazar/internal/pkg/proto/daemon" urproto "blazar/internal/pkg/proto/upgrades_registry" vrproto "blazar/internal/pkg/proto/version_resolver" "blazar/internal/pkg/provider" @@ -253,6 +254,8 @@ func run(t *testing.T, metrics *metrics.Metrics, prvdr provider.UpgradeProvider, // chain process must have logged upgrade height being hit require.Contains(t, stdout.String(), "UPGRADE \"test1\" NEEDED at height: 10") + requirePreCheckStatus(t, sm, 10) + // perform the upgrade err = daemon.performUpgrade(ctx, &cfg.Compose, cfg.ComposeService, height) require.NoError(t, err) @@ -272,6 +275,8 @@ func run(t *testing.T, metrics *metrics.Metrics, prvdr provider.UpgradeProvider, err = daemon.postUpgradeChecks(ctx, sm, &cfg.Checks.PostUpgrade, height) require.NoError(t, err) + requirePreCheckStatus(t, sm, 10) + outBuffer.Reset() // ------ TEST NON_GOVERNANCE_UNCOORDINATED UPGRADE ------ // @@ -291,6 +296,8 @@ func run(t *testing.T, metrics *metrics.Metrics, prvdr provider.UpgradeProvider, require.Contains(t, outBuffer.String(), fmt.Sprintf("Monitoring %s for new upgrades", cfg.UpgradeInfoFilePath())) require.Contains(t, outBuffer.String(), "Received upgrade height from the chain rpc") + requirePreCheckStatus(t, sm, 13) + err = daemon.performUpgrade(ctx, &cfg.Compose, cfg.ComposeService, height) require.NoError(t, err) @@ -301,6 +308,8 @@ func run(t *testing.T, metrics *metrics.Metrics, prvdr provider.UpgradeProvider, err = daemon.postUpgradeChecks(ctx, sm, &cfg.Checks.PostUpgrade, height) require.NoError(t, err) + requirePostCheckStatus(t, sm, 13) + outBuffer.Reset() // ------ TEST NON_GOVERNANCE_COORDINATED UPGRADE (with HALT_HEIGHT) ------ // @@ -334,6 +343,8 @@ func run(t *testing.T, metrics *metrics.Metrics, prvdr provider.UpgradeProvider, // we want to be sure the pre-check worked require.Contains(t, outBuffer.String(), "HALT_HEIGHT likely worked but didn't shut down the node") + requirePreCheckStatus(t, sm, 19) + err = daemon.performUpgrade(ctx, &cfg.Compose, cfg.ComposeService, height) require.NoError(t, err) @@ -341,11 +352,23 @@ func run(t *testing.T, metrics *metrics.Metrics, prvdr provider.UpgradeProvider, err = daemon.postUpgradeChecks(ctx, sm, &cfg.Checks.PostUpgrade, height) require.NoError(t, err) + requirePostCheckStatus(t, sm, 19) + // cleanup err = dcc.Down(ctx, cfg.ComposeService, cfg.Compose.DownTimeout) require.NoError(t, err) } +func requirePreCheckStatus(t *testing.T, sm *state_machine.StateMachine, height int64) { + require.Equal(t, checksproto.CheckStatus_FINISHED, sm.GetPreCheckStatus(height, checksproto.PreCheck_PULL_DOCKER_IMAGE)) + require.Equal(t, checksproto.CheckStatus_FINISHED, sm.GetPreCheckStatus(height, checksproto.PreCheck_SET_HALT_HEIGHT)) +} + +func requirePostCheckStatus(t *testing.T, sm *state_machine.StateMachine, height int64) { + require.Equal(t, checksproto.CheckStatus_FINISHED, sm.GetPostCheckStatus(height, checksproto.PostCheck_CHAIN_HEIGHT_INCREASED)) + require.Equal(t, checksproto.CheckStatus_FINISHED, sm.GetPostCheckStatus(height, checksproto.PostCheck_GRPC_RESPONSIVE)) +} + type threadSafeBuffer struct { buf bytes.Buffer mu sync.Mutex @@ -403,7 +426,7 @@ func generateConfig(t *testing.T, tempDir, serviceName string, grpcPort, cometbf }, Checks: config.Checks{ PreUpgrade: config.PreUpgrade{ - Enabled: []string{"SET_HALT_HEIGHT"}, + Enabled: []string{"PULL_DOCKER_IMAGE", "SET_HALT_HEIGHT"}, // as soon as possible Blocks: 100, SetHaltHeight: &config.SetHaltHeight{ diff --git a/internal/pkg/state_machine/state_machine.go b/internal/pkg/state_machine/state_machine.go index 006c23f..0c3bc53 100644 --- a/internal/pkg/state_machine/state_machine.go +++ b/internal/pkg/state_machine/state_machine.go @@ -120,7 +120,20 @@ func (sm *StateMachine) UpdateStatus(currentHeight int64, upgrades map[int64]*ur // if the new status is coming from governance proposal then we update // otherwise it must have been set by a blazar instance while processing upgrade if !slices.Contains(statusManagedByStateMachine, sm.state.UpgradeStatus[upgrade.Height]) { - sm.state.UpgradeStatus[upgrade.Height] = upgrade.Status + // Some chains implement their own governance system that may be compatible with the cosmos sdk. + // Take "Neutron", it uses smart contract for governance and it is also compatible with the cosmos sdk, such that + // it produces the upgrade-info.json at the upgrade height. + // + // We want to handle the case where the GOVERNANCE upgrade is not coming from the chain itself but from anoter provider. + // In this case, we want to mark the upgrade as ACTIVE as there is no onchain component (blazar is aware of) that manages the upgrade status. + if upgrade.Source != urproto.ProviderType_CHAIN { + if upgrade.Height > currentHeight { + sm.state.UpgradeStatus[upgrade.Height] = urproto.UpgradeStatus_ACTIVE + } + } else { + // the onchain status is the source of truth + sm.state.UpgradeStatus[upgrade.Height] = upgrade.Status + } } case urproto.UpgradeType_NON_GOVERNANCE_COORDINATED, urproto.UpgradeType_NON_GOVERNANCE_UNCOORDINATED: // mark the upgrade as 'ready for exection' (active) diff --git a/internal/pkg/state_machine/state_machine_test.go b/internal/pkg/state_machine/state_machine_test.go index 374a469..8139888 100644 --- a/internal/pkg/state_machine/state_machine_test.go +++ b/internal/pkg/state_machine/state_machine_test.go @@ -53,6 +53,35 @@ func TestStateMachineInitialUpgradeStates(t *testing.T) { } } +// Asserts that GOVERNANCE proposals from various chain sources are set to correct initial state +func TestStateMachineInitialNonChainGov(t *testing.T) { + for provider, expectedStatus := range map[urproto.ProviderType]urproto.UpgradeStatus{ + urproto.ProviderType_CHAIN: urproto.UpgradeStatus_UNKNOWN, + urproto.ProviderType_LOCAL: urproto.UpgradeStatus_ACTIVE, + urproto.ProviderType_DATABASE: urproto.UpgradeStatus_ACTIVE, + } { + upgrades := []*urproto.Upgrade{ + { + Height: 200, + Tag: "v1.0.0", + Name: "test upgrade", + Type: urproto.UpgradeType_GOVERNANCE, + Status: urproto.UpgradeStatus_UNKNOWN, + Source: provider, + }, + } + + upgradesMap := make(map[int64]*urproto.Upgrade) + for _, upgrade := range upgrades { + upgradesMap[upgrade.Height] = upgrade + } + + stateMachine := NewStateMachine(nil) + stateMachine.UpdateStatus(100, upgradesMap) + assert.Equal(t, expectedStatus.String(), stateMachine.GetStatus(200).String()) + } +} + // Asserts that the state machine panics when it receives an upgrade with an initial status that is not managed by the state machine func TestStateMachineUpgradesAreDeleted(t *testing.T) { currentHeight := int64(100)