Skip to content

Commit 7d9a706

Browse files
authored
Warn if user is not using a supported platform (#7381)
* Add prereqs package * Incorporate prereq check into the clients * gazelle * gazelle fix * linter * Add tests * minor change in test * finish up tests * gazelle * error during platform detection does not cause client to fail fast Co-authored-by: dv8silencer <15720668+dv8silencer@users.noreply.github.com>
1 parent 437bab7 commit 7d9a706

File tree

9 files changed

+275
-0
lines changed

9 files changed

+275
-0
lines changed

beacon-chain/node/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ go_library(
3434
"//shared/event:go_default_library",
3535
"//shared/featureconfig:go_default_library",
3636
"//shared/params:go_default_library",
37+
"//shared/prereq:go_default_library",
3738
"//shared/prometheus:go_default_library",
3839
"//shared/sliceutil:go_default_library",
3940
"//shared/tracing:go_default_library",

beacon-chain/node/node.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/prysmaticlabs/prysm/shared/event"
4242
"github.com/prysmaticlabs/prysm/shared/featureconfig"
4343
"github.com/prysmaticlabs/prysm/shared/params"
44+
"github.com/prysmaticlabs/prysm/shared/prereq"
4445
"github.com/prysmaticlabs/prysm/shared/prometheus"
4546
"github.com/prysmaticlabs/prysm/shared/sliceutil"
4647
"github.com/prysmaticlabs/prysm/shared/tracing"
@@ -91,6 +92,9 @@ func NewBeaconNode(cliCtx *cli.Context) (*BeaconNode, error) {
9192
return nil, err
9293
}
9394

95+
// Warn if user's platform is not supported
96+
prereq.WarnIfNotSupported(cliCtx.Context)
97+
9498
featureconfig.ConfigureBeaconChain(cliCtx)
9599
cmd.ConfigureBeaconChain(cliCtx)
96100
flags.ConfigureGlobalFlags(cliCtx)

shared/prereq/BUILD.bazel

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_test")
2+
load("@prysm//tools/go:def.bzl", "go_library")
3+
4+
go_library(
5+
name = "go_default_library",
6+
srcs = ["prereq.go"],
7+
importpath = "github.com/prysmaticlabs/prysm/shared/prereq",
8+
visibility = ["//visibility:public"],
9+
deps = [
10+
"@com_github_pkg_errors//:go_default_library",
11+
"@com_github_sirupsen_logrus//:go_default_library",
12+
],
13+
)
14+
15+
go_test(
16+
name = "go_default_test",
17+
srcs = ["prereq_test.go"],
18+
embed = [":go_default_library"],
19+
deps = [
20+
"//shared/testutil/require:go_default_library",
21+
"@com_github_pkg_errors//:go_default_library",
22+
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
23+
],
24+
)

shared/prereq/prereq.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package prereq
2+
3+
import (
4+
"context"
5+
"os/exec"
6+
"runtime"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/pkg/errors"
11+
log "github.com/sirupsen/logrus"
12+
)
13+
14+
type platform struct {
15+
os string
16+
arch string
17+
majorVersion int
18+
minorVersion int
19+
}
20+
21+
var (
22+
// execShellOutput has execShellOutputFunc as the default but can be changed for testing purposes.
23+
execShellOutput func(ctx context.Context, command string, args ...string) (string, error) = execShellOutputFunc
24+
runtimeOS = runtime.GOOS
25+
runtimeArch = runtime.GOARCH
26+
)
27+
28+
// execShellOutputFunc passes a command and args to exec.CommandContext and returns the result as a string
29+
func execShellOutputFunc(ctx context.Context, command string, args ...string) (string, error) {
30+
result, err := exec.CommandContext(ctx, command, args...).Output()
31+
if err != nil {
32+
return "", errors.Wrap(err, "error in command execution")
33+
}
34+
return string(result), nil
35+
}
36+
37+
func getSupportedPlatforms() []platform {
38+
return []platform{
39+
{os: "linux", arch: "amd64"},
40+
{os: "linux", arch: "arm64"},
41+
{os: "darwin", arch: "amd64", majorVersion: 10, minorVersion: 14},
42+
{os: "windows", arch: "amd64"},
43+
}
44+
}
45+
46+
// parseVersion takes a string and splits it using sep separator, and outputs a slice of integers
47+
// corresponding to version numbers. If it cannot find num level of versions, it returns an error
48+
func parseVersion(input string, num int, sep string) ([]int, error) {
49+
var version = make([]int, num)
50+
components := strings.Split(input, sep)
51+
for i, component := range components {
52+
components[i] = strings.TrimSpace(component)
53+
}
54+
if len(components) < num {
55+
return nil, errors.New("insufficient information about version")
56+
}
57+
for i := range version {
58+
var err error
59+
version[i], err = strconv.Atoi(components[i])
60+
if err != nil {
61+
return nil, errors.Wrap(err, "error during conversion")
62+
}
63+
}
64+
return version, nil
65+
}
66+
67+
// meetsMinPlatformReqs returns true if the runtime matches any on the list of supported platforms
68+
func meetsMinPlatformReqs(ctx context.Context) (bool, error) {
69+
okPlatforms := getSupportedPlatforms()
70+
for _, platform := range okPlatforms {
71+
if runtimeOS == platform.os && runtimeArch == platform.arch {
72+
// If MacOS we make sure it meets the minimum version cutoff
73+
if runtimeOS == "darwin" {
74+
versionStr, err := execShellOutput(ctx, "uname", "-r")
75+
if err != nil {
76+
return false, errors.Wrap(err, "error obtaining MacOS version")
77+
}
78+
version, err := parseVersion(versionStr, 2, ".")
79+
if err != nil {
80+
return false, errors.Wrap(err, "error parsing version")
81+
}
82+
if version[0] != platform.majorVersion {
83+
return version[0] > platform.majorVersion, nil
84+
}
85+
if version[1] < platform.minorVersion {
86+
return false, nil
87+
}
88+
return true, nil
89+
}
90+
// Otherwise we have a match between runtime and our list of accepted platforms
91+
return true, nil
92+
}
93+
}
94+
return false, nil
95+
}
96+
97+
// WarnIfNotSupported warns if the user's platform is not supported or if it fails to detect user's platform
98+
func WarnIfNotSupported(ctx context.Context) {
99+
supported, err := meetsMinPlatformReqs(ctx)
100+
if err != nil {
101+
log.Warnf("Failed to detect host platform: %v", err)
102+
return
103+
}
104+
if !supported {
105+
log.Warn("This platform is not supported. The following platforms are supported: Linux/AMD64," +
106+
" Linux/ARM64, Mac OS X/AMD64 (10.14+ only), and Windows/AMD64")
107+
}
108+
}

shared/prereq/prereq_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package prereq
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/pkg/errors"
8+
"github.com/prysmaticlabs/prysm/shared/testutil/require"
9+
logTest "github.com/sirupsen/logrus/hooks/test"
10+
)
11+
12+
func TestMeetsMinPlatformReqs(t *testing.T) {
13+
// Linux
14+
runtimeOS = "linux"
15+
runtimeArch = "amd64"
16+
meetsReqs, err := meetsMinPlatformReqs(context.Background())
17+
require.Equal(t, true, meetsReqs)
18+
require.NoError(t, err)
19+
runtimeArch = "arm64"
20+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
21+
require.Equal(t, true, meetsReqs)
22+
require.NoError(t, err)
23+
// mips64 is not supported
24+
runtimeArch = "mips64"
25+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
26+
require.Equal(t, false, meetsReqs)
27+
require.NoError(t, err)
28+
29+
// Mac OS X
30+
// In this function we'll set the execShellOutput package variable to another function that will 'mock' the shell
31+
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
32+
return "", errors.New("error while running command")
33+
}
34+
runtimeOS = "darwin"
35+
runtimeArch = "amd64"
36+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
37+
require.Equal(t, false, meetsReqs)
38+
require.ErrorContains(t, "error obtaining MacOS version", err)
39+
40+
// Insufficient version
41+
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
42+
return "10.4", nil
43+
}
44+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
45+
require.Equal(t, false, meetsReqs)
46+
require.NoError(t, err)
47+
48+
// Just-sufficient older version
49+
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
50+
return "10.14", nil
51+
}
52+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
53+
require.Equal(t, true, meetsReqs)
54+
require.NoError(t, err)
55+
56+
// Sufficient newer version
57+
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
58+
return "10.15.7", nil
59+
}
60+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
61+
require.Equal(t, true, meetsReqs)
62+
require.NoError(t, err)
63+
64+
// Handling abnormal response
65+
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
66+
return "tiger.lion", nil
67+
}
68+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
69+
require.Equal(t, false, meetsReqs)
70+
require.ErrorContains(t, "error parsing version", err)
71+
72+
// Windows
73+
runtimeOS = "windows"
74+
runtimeArch = "amd64"
75+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
76+
require.Equal(t, true, meetsReqs)
77+
require.NoError(t, err)
78+
runtimeArch = "arm64"
79+
meetsReqs, err = meetsMinPlatformReqs(context.Background())
80+
require.Equal(t, false, meetsReqs)
81+
require.NoError(t, err)
82+
}
83+
84+
func TestParseVersion(t *testing.T) {
85+
version, err := parseVersion("1.2.3", 3, ".")
86+
require.DeepEqual(t, version, []int{1, 2, 3})
87+
require.NoError(t, err)
88+
89+
version, err = parseVersion("6 .7 . 8 ", 3, ".")
90+
require.DeepEqual(t, version, []int{6, 7, 8})
91+
require.NoError(t, err)
92+
93+
version, err = parseVersion("10,3,5,6", 4, ",")
94+
require.DeepEqual(t, version, []int{10, 3, 5, 6})
95+
require.NoError(t, err)
96+
97+
version, err = parseVersion("4;6;8;10;11", 3, ";")
98+
require.DeepEqual(t, version, []int{4, 6, 8})
99+
require.NoError(t, err)
100+
101+
_, err = parseVersion("10.11", 3, ".")
102+
require.ErrorContains(t, "insufficient information about version", err)
103+
}
104+
105+
func TestWarnIfNotSupported(t *testing.T) {
106+
runtimeOS = "linux"
107+
runtimeArch = "amd64"
108+
hook := logTest.NewGlobal()
109+
WarnIfNotSupported(context.Background())
110+
require.LogsDoNotContain(t, hook, "Failed to detect host platform")
111+
require.LogsDoNotContain(t, hook, "platform is not supported")
112+
113+
execShellOutput = func(ctx context.Context, command string, args ...string) (string, error) {
114+
return "tiger.lion", nil
115+
}
116+
runtimeOS = "darwin"
117+
runtimeArch = "amd64"
118+
hook = logTest.NewGlobal()
119+
WarnIfNotSupported(context.Background())
120+
require.LogsContain(t, hook, "Failed to detect host platform")
121+
require.LogsContain(t, hook, "error parsing version")
122+
123+
runtimeOS = "falseOs"
124+
runtimeArch = "falseArch"
125+
hook = logTest.NewGlobal()
126+
WarnIfNotSupported(context.Background())
127+
require.LogsContain(t, hook, "platform is not supported")
128+
}

slasher/node/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
"//shared/event:go_default_library",
1414
"//shared/featureconfig:go_default_library",
1515
"//shared/params:go_default_library",
16+
"//shared/prereq:go_default_library",
1617
"//shared/prometheus:go_default_library",
1718
"//shared/tracing:go_default_library",
1819
"//shared/version:go_default_library",

slasher/node/node.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/prysmaticlabs/prysm/shared/event"
2020
"github.com/prysmaticlabs/prysm/shared/featureconfig"
2121
"github.com/prysmaticlabs/prysm/shared/params"
22+
"github.com/prysmaticlabs/prysm/shared/prereq"
2223
"github.com/prysmaticlabs/prysm/shared/prometheus"
2324
"github.com/prysmaticlabs/prysm/shared/tracing"
2425
"github.com/prysmaticlabs/prysm/shared/version"
@@ -64,6 +65,9 @@ func NewSlasherNode(cliCtx *cli.Context) (*SlasherNode, error) {
6465
return nil, err
6566
}
6667

68+
// Warn if user's platform is not supported
69+
prereq.WarnIfNotSupported(cliCtx.Context)
70+
6771
if cliCtx.Bool(flags.EnableHistoricalDetectionFlag.Name) {
6872
// Set the max RPC size to 4096 as configured by --historical-slasher-node for optimal historical detection.
6973
cmdConfig := cmd.Get()

validator/node/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ go_library(
2828
"//shared/featureconfig:go_default_library",
2929
"//shared/fileutil:go_default_library",
3030
"//shared/params:go_default_library",
31+
"//shared/prereq:go_default_library",
3132
"//shared/prometheus:go_default_library",
3233
"//shared/tracing:go_default_library",
3334
"//shared/version:go_default_library",

validator/node/node.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/prysmaticlabs/prysm/shared/featureconfig"
2222
"github.com/prysmaticlabs/prysm/shared/fileutil"
2323
"github.com/prysmaticlabs/prysm/shared/params"
24+
"github.com/prysmaticlabs/prysm/shared/prereq"
2425
"github.com/prysmaticlabs/prysm/shared/prometheus"
2526
"github.com/prysmaticlabs/prysm/shared/tracing"
2627
"github.com/prysmaticlabs/prysm/shared/version"
@@ -71,6 +72,9 @@ func NewValidatorClient(cliCtx *cli.Context) (*ValidatorClient, error) {
7172
}
7273
logrus.SetLevel(level)
7374

75+
// Warn if user's platform is not supported
76+
prereq.WarnIfNotSupported(cliCtx.Context)
77+
7478
registry := shared.NewServiceRegistry()
7579
ValidatorClient := &ValidatorClient{
7680
cliCtx: cliCtx,

0 commit comments

Comments
 (0)