Skip to content

Commit cc65f27

Browse files
committed
Extend TestSmeshing to also deploy an older version of go-spacmesh (#6403)
## Motivation To detect errors that would break compatibility with previous versions of the node I extended `TestSmeshing` to deploy 25% of the nodes in the test using an older version of go-spacemesh. So if any change changes protocol rules that would break consensus like the error fixed in #6398 or the error fixed in #5839.
1 parent 724485e commit cc65f27

23 files changed

+432
-380
lines changed

Dockerfile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# go-spacemesh needs at least ubuntu 22.04. newer versions of ubuntu might work as well, but are untested
22
FROM ubuntu:22.04 AS linux
3-
ENV DEBIAN_FRONTEND noninteractive
4-
ENV SHELL /bin/bash
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
ENV SHELL=/bin/bash
55
ARG TZ=Etc/UTC
66
ENV TZ=${TZ}
77
USER root
@@ -22,11 +22,11 @@ RUN set -ex \
2222
&& locale-gen en_US.UTF-8 \
2323
&& update-locale LANG=en_US.UTF-8 \
2424
&& echo "$TZ" > /etc/timezone
25-
ENV LANG en_US.UTF-8
26-
ENV LANGUAGE en_US.UTF-8
27-
ENV LC_ALL en_US.UTF-8
25+
ENV LANG=en_US.UTF-8
26+
ENV LANGUAGE=en_US.UTF-8
27+
ENV LC_ALL=en_US.UTF-8
2828

29-
FROM golang:1.23 as builder
29+
FROM golang:1.23 AS builder
3030
ARG VERSION=""
3131
ENV VERSION=${VERSION}
3232
RUN set -ex \

bootstrap.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.23 as builder
1+
FROM golang:1.23 AS builder
22

33
WORKDIR /src
44

common/types/block.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ type InnerBlock struct {
8989
}
9090

9191
// RatNum represents a rational number with the numerator and denominator.
92-
// note: RatNum aims to be a generic representation of a rational number and parse-able by
92+
// note: RatNum aims to be a generic representation of a rational number and parseable by
9393
// different programming languages.
9494
// For doing math around weight inside go-spacemesh codebase, use util.Weight.
9595
type RatNum struct {

config/config.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33

44
import (
55
"fmt"
6+
"io"
67
"math"
78
"os"
89
"path/filepath"
@@ -276,13 +277,13 @@ func defaultTestConfig() BaseConfig {
276277
}
277278

278279
// LoadConfig load the config file.
279-
func LoadConfig(config string, vip *viper.Viper) error {
280-
if config == "" {
280+
func LoadConfig(src io.Reader, vip *viper.Viper) error {
281+
if src == nil {
281282
return nil
282283
}
283-
vip.SetConfigFile(config)
284-
if err := vip.ReadInConfig(); err != nil {
285-
return fmt.Errorf("can't load config at %s: %w", config, err)
284+
vip.SetConfigType("json")
285+
if err := vip.ReadConfig(src); err != nil {
286+
return fmt.Errorf("can't load config: %w", err)
286287
}
287288
return nil
288289
}

config/presets/fastnet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func fastnet() config.Config {
2626
// set for systest TestEquivocation
2727
conf.BaseConfig.MinerGoodAtxsPercent = 50
2828

29-
// node will select atxs that were received atleast 4 seconds before start of the epoch
29+
// node will select atxs that were received at least 4 seconds before start of the epoch
3030
// for activeset.
3131
// if some atxs weren't received on time it will skew eligibility distribution
3232
// and will make some tests fail.

node/node.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"errors"
88
"fmt"
9+
"io"
910
"io/fs"
1011
"net"
1112
"net/http"
@@ -244,31 +245,39 @@ func GetCommand() *cobra.Command {
244245
}
245246

246247
func configure(c *cobra.Command, configPath string, conf *config.Config) error {
247-
preset := conf.Preset // might be set via CLI flag
248-
if err := loadConfig(conf, preset, configPath); err != nil {
248+
f, err := os.Open(configPath)
249+
if err != nil {
250+
return fmt.Errorf("opening config file: %w", err)
251+
}
252+
defer f.Close()
253+
if err := LoadConfig(conf, conf.Preset, f); err != nil {
249254
return fmt.Errorf("loading config: %w", err)
250255
}
256+
if err := f.Close(); err != nil {
257+
return fmt.Errorf("closing config file: %w", err)
258+
}
251259
// apply CLI args to config
252260
if err := c.ParseFlags(os.Args[1:]); err != nil {
253261
return fmt.Errorf("parsing flags: %w", err)
254262
}
255-
256263
if cmd.NoMainNet && onMainNet(conf) && !conf.NoMainOverride {
257264
return errors.New("this is a testnet-only build not intended for mainnet")
258265
}
259-
260266
return nil
261267
}
262268

263269
var grpcLog = grpc_logsettable.ReplaceGrpcLoggerV2()
264270

265-
// loadConfig loads config and preset (if provided) into the provided config.
271+
// LoadConfig loads config and preset (if provided) into the provided config.
266272
// It first loads the preset and then overrides it with values from the config file.
267-
func loadConfig(cfg *config.Config, preset, path string) error {
273+
func LoadConfig(cfg *config.Config, preset string, src io.Reader) error {
268274
v := viper.New()
269-
// read in config from file
270-
if err := config.LoadConfig(path, v); err != nil {
271-
return err
275+
// read in config from src
276+
if src != nil {
277+
v.SetConfigType("json")
278+
if err := v.ReadConfig(src); err != nil {
279+
return fmt.Errorf("can't load config: %w", err)
280+
}
272281
}
273282

274283
// override default config with preset if provided
@@ -283,7 +292,7 @@ func loadConfig(cfg *config.Config, preset, path string) error {
283292
*cfg = p
284293
}
285294

286-
// Unmarshall config file into config struct
295+
// Unmarshal config file into config struct
287296
hook := mapstructure.ComposeDecodeHookFunc(
288297
mapstructure.StringToTimeDurationHookFunc(),
289298
mapstructure.StringToSliceHookFunc(","),
@@ -294,15 +303,12 @@ func loadConfig(cfg *config.Config, preset, path string) error {
294303
mapstructureutil.AtxVersionsDecodeFunc(),
295304
mapstructure.TextUnmarshallerHookFunc(),
296305
)
297-
298306
opts := []viper.DecoderConfigOption{
299307
viper.DecodeHook(hook),
300308
WithZeroFields(),
301309
WithIgnoreUntagged(),
302310
WithErrorUnused(),
303311
}
304-
305-
// load config if it was loaded to the viper
306312
if err := v.Unmarshal(cfg, opts...); err != nil {
307313
return fmt.Errorf("unmarshal config: %w", err)
308314
}

node/node_test.go

Lines changed: 22 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ func TestSpacemeshApp_AddLogger(t *testing.T) {
139139
myLogger := "anton"
140140
subLogger := app.addLogger(myLogger, lg)
141141
subLogger.Debug("should not get printed")
142-
teststr := "should get printed"
143-
subLogger.Info(teststr)
142+
testStr := "should get printed"
143+
subLogger.Info(testStr)
144144
r.Equal(
145-
fmt.Sprintf("INFO\t%s\t%s\n", myLogger, teststr),
145+
fmt.Sprintf("INFO\t%s\t%s\n", myLogger, testStr),
146146
buf.String(),
147147
)
148148
}
@@ -339,8 +339,8 @@ func (f *noopHook) OnWrite(*zapcore.CheckedEntry, []zapcore.Field) {}
339339
// E2E app test of the stream endpoints in the NodeService.
340340
func TestSpacemeshApp_NodeService(t *testing.T) {
341341
logger := logtest.New(t)
342-
// errlog is used to simulate errors in the app
343-
errlog := log.NewFromLog(
342+
// errLog is used to simulate errors in the app
343+
errLog := log.NewFromLog(
344344
zaptest.NewLogger(t, zaptest.WrapOptions(zap.Hooks(events.EventHook()), zap.WithPanicHook(&noopHook{}))),
345345
)
346346

@@ -430,9 +430,9 @@ func TestSpacemeshApp_NodeService(t *testing.T) {
430430

431431
// Report two errors and make sure they're both received
432432
eg.Go(func() error {
433-
errlog.Error("test123")
434-
errlog.Error("test456")
435-
errlog.Panic("testPANIC")
433+
errLog.Error("test123")
434+
errLog.Error("test456")
435+
errLog.Panic("testPANIC")
436436
return nil
437437
})
438438

@@ -602,7 +602,7 @@ func TestConfig_Preset(t *testing.T) {
602602
require.NoError(t, err)
603603

604604
conf := config.Config{}
605-
require.NoError(t, loadConfig(&conf, name, ""))
605+
require.NoError(t, LoadConfig(&conf, name, nil))
606606
require.Equal(t, preset, conf)
607607
})
608608

@@ -615,7 +615,7 @@ func TestConfig_Preset(t *testing.T) {
615615
cmd.AddFlags(&flags, &conf)
616616

617617
const lowPeers = 1234
618-
require.NoError(t, loadConfig(&conf, name, ""))
618+
require.NoError(t, LoadConfig(&conf, name, nil))
619619
require.NoError(t, flags.Parse([]string{"--low-peers=" + strconv.Itoa(lowPeers)}))
620620
preset.P2P.LowPeers = lowPeers
621621
require.Equal(t, preset, conf)
@@ -628,10 +628,8 @@ func TestConfig_Preset(t *testing.T) {
628628
conf := config.Config{}
629629
const lowPeers = 1234
630630
content := fmt.Sprintf(`{"p2p": {"low-peers": %d}}`, lowPeers)
631-
path := filepath.Join(t.TempDir(), "config.json")
632-
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
633631

634-
require.NoError(t, loadConfig(&conf, name, path))
632+
require.NoError(t, LoadConfig(&conf, name, strings.NewReader(content)))
635633
preset.P2P.LowPeers = lowPeers
636634
require.Equal(t, preset, conf)
637635
})
@@ -642,10 +640,8 @@ func TestConfig_Preset(t *testing.T) {
642640

643641
conf := config.Config{}
644642
content := fmt.Sprintf(`{"preset": "%s"}`, name)
645-
path := filepath.Join(t.TempDir(), "config.json")
646-
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
647643

648-
require.NoError(t, loadConfig(&conf, name, path))
644+
require.NoError(t, LoadConfig(&conf, name, strings.NewReader(content)))
649645
require.NoError(t, err)
650646
require.Equal(t, preset, conf)
651647
})
@@ -719,7 +715,7 @@ func TestConfig_CustomTypes(t *testing.T) {
719715
var flags pflag.FlagSet
720716
cmd.AddFlags(&flags, &conf)
721717

722-
require.NoError(t, loadConfig(&conf, "", ""))
718+
require.NoError(t, LoadConfig(&conf, "", nil))
723719
require.NoError(t, flags.Parse(strings.Fields(tc.cli)))
724720
tc.updatePreset(t, &mainnet)
725721
require.Equal(t, mainnet, conf)
@@ -730,10 +726,8 @@ func TestConfig_CustomTypes(t *testing.T) {
730726
require.Nil(t, mainnet.SMESHING.Opts.ProviderID.Value())
731727

732728
conf := config.MainnetConfig()
733-
path := filepath.Join(t.TempDir(), "config.json")
734-
require.NoError(t, os.WriteFile(path, []byte(tc.config), 0o600))
735729

736-
require.NoError(t, loadConfig(&conf, "", path))
730+
require.NoError(t, LoadConfig(&conf, "", strings.NewReader(tc.config)))
737731
tc.updatePreset(t, &mainnet)
738732
require.Equal(t, mainnet, conf)
739733
})
@@ -746,7 +740,7 @@ func TestConfig_CustomTypes(t *testing.T) {
746740
var flags pflag.FlagSet
747741
cmd.AddFlags(&flags, &conf)
748742

749-
require.NoError(t, loadConfig(&conf, name, ""))
743+
require.NoError(t, LoadConfig(&conf, name, nil))
750744
require.NoError(t, flags.Parse(strings.Fields(tc.cli)))
751745
tc.updatePreset(t, &preset)
752746
require.Equal(t, preset, conf)
@@ -757,10 +751,8 @@ func TestConfig_CustomTypes(t *testing.T) {
757751
require.NoError(t, err)
758752

759753
conf := config.Config{}
760-
path := filepath.Join(t.TempDir(), "config.json")
761-
require.NoError(t, os.WriteFile(path, []byte(tc.config), 0o600))
762754

763-
require.NoError(t, loadConfig(&conf, name, path))
755+
require.NoError(t, LoadConfig(&conf, name, strings.NewReader(tc.config)))
764756
tc.updatePreset(t, &preset)
765757
require.Equal(t, preset, conf)
766758
})
@@ -803,10 +795,8 @@ func TestConfig_PostProviderID_InvalidValues(t *testing.T) {
803795
t.Run(fmt.Sprintf("%s_ConfigFile", tc.name), func(t *testing.T) {
804796
conf := config.Config{}
805797

806-
path := filepath.Join(t.TempDir(), "config.json")
807798
cfg := fmt.Sprintf(`{"smeshing": {"smeshing-opts": {"smeshing-opts-provider": %s}}}`, tc.configValue)
808-
require.NoError(t, os.WriteFile(path, []byte(cfg), 0o600))
809-
err := loadConfig(&conf, "", path)
799+
err := LoadConfig(&conf, "", strings.NewReader(cfg))
810800
require.ErrorContains(t, err, "invalid provider ID value")
811801
})
812802
}
@@ -816,18 +806,15 @@ func TestConfig_Load(t *testing.T) {
816806
t.Run("invalid fails to load", func(t *testing.T) {
817807
conf := config.Config{}
818808

819-
path := filepath.Join(t.TempDir(), "config.json")
820-
require.NoError(t, os.WriteFile(path, []byte("}"), 0o600))
821-
822-
err := loadConfig(&conf, "", path)
823-
require.ErrorContains(t, err, path)
809+
err := LoadConfig(&conf, "", strings.NewReader("}"))
810+
require.ErrorContains(t, err, "invalid character '}' looking for beginning of value")
824811
})
825812
t.Run("missing default doesn't fail", func(t *testing.T) {
826813
conf := config.Config{}
827814
var flags pflag.FlagSet
828815
cmd.AddFlags(&flags, &conf)
829816

830-
require.NoError(t, loadConfig(&conf, "", ""))
817+
require.NoError(t, LoadConfig(&conf, "", nil))
831818
require.NoError(t, flags.Parse([]string{}))
832819
})
833820
}
@@ -848,7 +835,7 @@ func TestConfig_GenesisAccounts(t *testing.T) {
848835
args = append(args, fmt.Sprintf("-a %s=%d", key, value))
849836
}
850837

851-
require.NoError(t, loadConfig(&conf, "", ""))
838+
require.NoError(t, LoadConfig(&conf, "", nil))
852839
require.NoError(t, flags.Parse(args))
853840
for _, key := range keys {
854841
require.EqualValues(t, value, conf.Genesis.Accounts[key])
@@ -858,9 +845,7 @@ func TestConfig_GenesisAccounts(t *testing.T) {
858845
func TestHRP(t *testing.T) {
859846
conf := config.Config{}
860847
data := `{"main": {"network-hrp": "TEST"}}`
861-
cfg := filepath.Join(t.TempDir(), "config.json")
862-
require.NoError(t, os.WriteFile(cfg, []byte(data), 0o600))
863-
require.NoError(t, loadConfig(&conf, "", cfg))
848+
require.NoError(t, LoadConfig(&conf, "", strings.NewReader(data)))
864849
app := New(WithConfig(&conf))
865850
require.NotNil(t, app)
866851
require.Equal(t, "TEST", types.NetworkHRP())

syncer/atxsync/syncer.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"math/rand/v2"
78
"time"
89

910
"go.uber.org/zap"
@@ -107,9 +108,10 @@ func (s *Syncer) Download(parent context.Context, publish types.EpochID, downloa
107108
return fmt.Errorf("failed to get last request time for epoch %v: %w", publish, err)
108109
}
109110
// in case of immediate we will request epoch info without waiting EpochInfoInterval
110-
immediate := len(state) == 0 || (errors.Is(err, sql.ErrNotFound) || !lastSuccess.After(downloadUntil))
111+
immediate := len(state) == 0 || errors.Is(err, sql.ErrNotFound) || !lastSuccess.After(downloadUntil)
111112
if !immediate && total == downloaded {
112-
s.logger.Debug("sync for epoch was completed before",
113+
s.logger.Debug(
114+
"sync for epoch was completed before",
113115
log.ZContext(parent),
114116
zap.Uint32("epoch_id", publish.Uint32()),
115117
)
@@ -150,22 +152,24 @@ func (s *Syncer) downloadEpochInfo(
150152
) error {
151153
interval := s.cfg.EpochInfoInterval
152154
if immediate {
153-
interval = 0
155+
interval = 1 * time.Second // not really immediate, to avoid an endless loop that doesn't wait between requests
154156
}
157+
155158
for {
156-
if interval != 0 {
157-
s.logger.Debug(
158-
"waiting between epoch info requests",
159-
zap.Uint32("epoch_id", publish.Uint32()),
160-
zap.Duration("duration", interval),
161-
)
162-
}
159+
// randomize interval to avoid sync spikes
160+
minWait := time.Duration(float64(interval) * 0.9)
161+
maxWait := time.Duration(float64(interval) * 1.1)
162+
wait := minWait + rand.N(maxWait-minWait+1)
163+
s.logger.Debug(
164+
"waiting between epoch info requests",
165+
zap.Uint32("epoch_id", publish.Uint32()),
166+
zap.Duration("duration", wait),
167+
)
168+
163169
select {
164170
case <-ctx.Done():
165171
return nil
166-
// TODO(dshulyak) this has to be randomized in a followup
167-
// when sync will be schedulled in advance, in order to smooth out request rate across the network
168-
case <-time.After(interval):
172+
case <-time.After(wait):
169173
}
170174

171175
peers := s.fetcher.SelectBestShuffled(s.cfg.EpochInfoPeers)

0 commit comments

Comments
 (0)