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)