Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add zetaclient minimum version check #3320

Draft
wants to merge 1 commit into
base: zetaclient-signer-latency-correction
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions cmd/zetaclientd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ func Start(_ *cobra.Command, _ []string) error {
Telemetry: telemetry,
}

tss, err := zetatss.Setup(ctx, tssSetupProps, logger.Std)
if err != nil {
return errors.Wrap(err, "unable to setup TSS service")
}

isObserver, err := isObserverNode(ctx, zetacoreClient)
switch {
case err != nil:
Expand All @@ -115,11 +110,23 @@ func Start(_ *cobra.Command, _ []string) error {
graceful.ShutdownNow()
})

maintenance.NewShutdownListener(zetacoreClient, logger.Std).Listen(ctx, func() {
shutdownListener := maintenance.NewShutdownListener(zetacoreClient, logger.Std)
err = shutdownListener.RunPreStartCheck(ctx)
if err != nil {
return errors.Wrap(err, "pre start check failed")
}
shutdownListener.Listen(ctx, func() {
logger.Std.Info().Msg("Shutdown listener received an action to shutdown zetaclientd.")
graceful.ShutdownNow()
})

// This will start p2p communication so it should only happen after
// preflight checks have completed
tss, err := zetatss.Setup(ctx, tssSetupProps, logger.Std)
if err != nil {
return errors.Wrap(err, "unable to setup TSS service")
}

// CreateSignerMap: This creates a map of all signers for each chain.
// Each signer is responsible for signing transactions for a particular chain
signerMap, err := orchestrator.CreateSignerMap(ctx, tss, logger)
Expand Down
4 changes: 4 additions & 0 deletions proto/zetachain/zetacore/observer/operational.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ message OperationalFlags {
// Should be calculated and set based on max(zetaclient_core_block_latency).
google.protobuf.Duration signer_block_time_offset = 2
[ (gogoproto.stdduration) = true ];

// Minimum version of zetaclient that is allowed to run. This must be either
// a valid semver string (v23.0.1) or empty.
string minimum_version = 3;
}
5 changes: 5 additions & 0 deletions x/observer/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ var (
1140,
"signer block time offset exceeds limit",
)
ErrOperationalFlagsInvalidMinimumVersion = errorsmod.Register(
ModuleName,
1141,
"minimum version is not a valid semver string",
)
)
4 changes: 4 additions & 0 deletions x/observer/types/operational.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"time"

cosmoserrors "cosmossdk.io/errors"
"golang.org/x/mod/semver"
)

const (
Expand All @@ -23,5 +24,8 @@
return cosmoserrors.Wrapf(ErrOperationalFlagsSignerBlockTimeOffsetLimit, "(%s)", signerBlockTimeOffset)
}
}
if f.MinimumVersion != "" && !semver.IsValid(f.MinimumVersion) {
return ErrOperationalFlagsInvalidMinimumVersion
}

Check warning on line 29 in x/observer/types/operational.go

View check run for this annotation

Codecov / codecov/patch

x/observer/types/operational.go#L28-L29

Added lines #L28 - L29 were not covered by tests
return nil
}
93 changes: 74 additions & 19 deletions x/observer/types/operational.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions x/observer/types/operational_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ func TestOperationalFlags_Validate(t *testing.T) {
of types.OperationalFlags
errContains string
}{
{
name: "empty is valid",
of: types.OperationalFlags{},
},
{
name: "invalid restart height",
of: types.OperationalFlags{
Expand Down Expand Up @@ -48,11 +52,18 @@ func TestOperationalFlags_Validate(t *testing.T) {
},
errContains: types.ErrOperationalFlagsSignerBlockTimeOffsetLimit.Error(),
},
{
name: "minimum version valid",
of: types.OperationalFlags{
MinimumVersion: "v1.1.1",
},
},
{
name: "all flags valid",
of: types.OperationalFlags{
RestartHeight: 1,
SignerBlockTimeOffset: ptr.Ptr(time.Second),
MinimumVersion: "v1.1.1",
},
},
}
Expand Down
63 changes: 56 additions & 7 deletions zetaclient/maintenance/shutdown_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import (
"context"
"fmt"
"strings"
"time"

"cosmossdk.io/errors"
"github.com/rs/zerolog"
"golang.org/x/mod/semver"

"github.com/zeta-chain/node/pkg/bg"
"github.com/zeta-chain/node/pkg/constant"
"github.com/zeta-chain/node/pkg/retry"
observertypes "github.com/zeta-chain/node/x/observer/types"
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
Expand All @@ -22,17 +26,32 @@
logger zerolog.Logger

lastRestartHeightMissed int64
getVersion func() string
}

// NewShutdownListener creates a new ShutdownListener.
func NewShutdownListener(client interfaces.ZetacoreClient, logger zerolog.Logger) *ShutdownListener {
log := logger.With().Str("module", "shutdown_listener").Logger()
return &ShutdownListener{
client: client,
logger: log,
client: client,
logger: log,
getVersion: getVersionDefault,
}
}

// RunPreStartCheck runs any checks that must run before fully starting zetaclient.
// Specifically this should be run before any TSS P2P is started.
func (o *ShutdownListener) RunPreStartCheck(ctx context.Context) error {
operationalFlags, err := o.getOperationalFlagsWithRetry(ctx)
if err != nil {
return errors.Wrap(err, "unable to get initial operational flags")
}

Check warning on line 48 in zetaclient/maintenance/shutdown_listener.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/maintenance/shutdown_listener.go#L47-L48

Added lines #L47 - L48 were not covered by tests
if err := o.checkMinimumVersion(operationalFlags); err != nil {
return err
}
return nil
}

func (o *ShutdownListener) Listen(ctx context.Context, action func()) {
var (
withLogger = bg.WithLogger(o.logger)
Expand All @@ -43,12 +62,9 @@
}

func (o *ShutdownListener) waitForUpdate(ctx context.Context) error {
operationalFlags, err := retry.DoTypedWithBackoffAndRetry(
func() (observertypes.OperationalFlags, error) { return o.client.GetOperationalFlags(ctx) },
retry.DefaultConstantBackoff(),
)
operationalFlags, err := o.getOperationalFlagsWithRetry(ctx)
if err != nil {
return errors.Wrap(err, "unable to get initial operational flags")
return errors.Wrap(err, "get initial operational flags")

Check warning on line 67 in zetaclient/maintenance/shutdown_listener.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/maintenance/shutdown_listener.go#L67

Added line #L67 was not covered by tests
}
if o.handleNewFlags(ctx, operationalFlags) {
return nil
Expand All @@ -74,8 +90,19 @@
}
}

func (o *ShutdownListener) getOperationalFlagsWithRetry(ctx context.Context) (observertypes.OperationalFlags, error) {
return retry.DoTypedWithBackoffAndRetry(
func() (observertypes.OperationalFlags, error) { return o.client.GetOperationalFlags(ctx) },
retry.DefaultConstantBackoff(),
)
}

// handleNewFlags processes the flags and returns true if a shutdown should be signaled
func (o *ShutdownListener) handleNewFlags(ctx context.Context, f observertypes.OperationalFlags) bool {
if err := o.checkMinimumVersion(f); err != nil {
o.logger.Error().Err(err).Msg("minimum version check")
return true
}
if f.RestartHeight < 1 {
return false
}
Expand Down Expand Up @@ -123,3 +150,25 @@
}
}
}

func (o *ShutdownListener) checkMinimumVersion(f observertypes.OperationalFlags) error {
if f.MinimumVersion != "" {
// we typically store the version without the required v prefix
currentVersion := ensurePrefix(o.getVersion(), "v")
if semver.Compare(currentVersion, f.MinimumVersion) == -1 {
return fmt.Errorf("current version (%s) is less than minimum version (%s)", currentVersion, f.MinimumVersion)
}
}
return nil
}

func getVersionDefault() string {
return constant.Version

Check warning on line 166 in zetaclient/maintenance/shutdown_listener.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/maintenance/shutdown_listener.go#L165-L166

Added lines #L165 - L166 were not covered by tests
}

func ensurePrefix(s, prefix string) string {
if !strings.HasPrefix(s, prefix) {
return prefix + s
}
return s

Check warning on line 173 in zetaclient/maintenance/shutdown_listener.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/maintenance/shutdown_listener.go#L173

Added line #L173 was not covered by tests
}
Loading
Loading