Skip to content

Commit

Permalink
support governance type upgrades from non chain providers
Browse files Browse the repository at this point in the history
  • Loading branch information
mkaczanowski committed Nov 9, 2024
1 parent 7baae0b commit 40d8768
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 6 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,10 @@ Remember that Blazar refreshes its internal state periodically. If you registere
<details>
<summary>Does Blazar work with chains with non-standard gov module (e.g., Neutron)?</summary>

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.
</details>

<details>
Expand Down
25 changes: 24 additions & 1 deletion internal/pkg/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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 ------ //
Expand All @@ -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)

Expand All @@ -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) ------ //
Expand Down Expand Up @@ -334,18 +343,32 @@ 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)

// lets see if post upgrade checks pass
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
Expand Down Expand Up @@ -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{
Expand Down
15 changes: 14 additions & 1 deletion internal/pkg/state_machine/state_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions internal/pkg/state_machine/state_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 40d8768

Please sign in to comment.