From 65e36e99f29c73977255cb9f86ef93e25d5d54d7 Mon Sep 17 00:00:00 2001 From: Ivan Shvedunov Date: Wed, 20 Dec 2023 21:16:46 +0000 Subject: [PATCH 1/4] Add no-main-override config option (#5367) This makes it possible to force "nomain" builds to run on mainnet. ## Motivation Sometimes it is necessary to try a "nomain" build with mainnet. ## Changes This adds `no-main-override` toplevel config option (bool) and also `--no-main-override` command line flag. --- CHANGELOG.md | 3 +++ cmd/root.go | 3 +++ config/config.go | 3 +++ node/node.go | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd9116398..25aef13914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,9 @@ for more information on how to configure the node to work with the PoST service. section. The non-conditional changes include values/provides support on all of the nodes, which will enable DHT to function efficiently for routing discovery. +* [#5367](https://github.com/spacemeshos/go-spacemesh/pull/5367) Add `no-main-override` toplevel config option and + `--no-main-override` CLI option that makes it possible to run "nomain" builds on mainnet. + ## Release v1.2.9 ### Improvements diff --git a/cmd/root.go b/cmd/root.go index 02a43752ee..7e0c8c8313 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -82,6 +82,9 @@ func AddCommands(cmd *cobra.Command) { cmd.PersistentFlags().DurationVar(&cfg.DatabasePruneInterval, "db-prune-interval", cfg.DatabasePruneInterval, "configure interval for database pruning") + cmd.PersistentFlags().BoolVar(&cfg.NoMainOverride, "no-main-override", + cfg.NoMainOverride, "force 'nomain' builds to run on the mainnet") + /** ======================== P2P Flags ========================== **/ cmd.PersistentFlags().Var(flags.NewAddressListValue(cfg.P2P.Listen, &cfg.P2P.Listen), diff --git a/config/config.go b/config/config.go index 94efb384ed..15e8045af1 100644 --- a/config/config.go +++ b/config/config.go @@ -128,6 +128,9 @@ type BaseConfig struct { // ATXGradeDelay is used to grade ATXs for selection in tortoise active set. // See grading fuction in miner/proposals_builder.go ATXGradeDelay time.Duration `mapstructure:"atx-grade-delay"` + + // NoMainOverride forces the "nomain" builds to run on the mainnet + NoMainOverride bool `mapstructure:"no-main-override"` } type PublicMetrics struct { diff --git a/node/node.go b/node/node.go index 0f6f7fa489..cf7ac874ae 100644 --- a/node/node.go +++ b/node/node.go @@ -133,7 +133,7 @@ func GetCommand() *cobra.Command { log.JSONLog(true) } - if cmd.NoMainNet && onMainNet(conf) { + if cmd.NoMainNet && onMainNet(conf) && !conf.NoMainOverride { log.With().Fatal("this is a testnet-only build not intended for mainnet") } From 3c26d0b8236936bccbeefc798e7db42254bdfcda Mon Sep 17 00:00:00 2001 From: Ivan Shvedunov Date: Wed, 20 Dec 2023 22:10:24 +0000 Subject: [PATCH 2/4] Make disable-connection-manager option really work (#5370) Need to provide `NullConnMgr` as the connection manager for no peer trimming to happen. This is useful for debugging problems that may be related to peer trimming by the libp2p `ConnectionManager`. --- p2p/host.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/p2p/host.go b/p2p/host.go index 8ea70e3f20..0fbaccb12f 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -205,14 +205,6 @@ func New( } lp2plog.SetPrimaryCore(logger.Core()) lp2plog.SetAllLoggers(lp2plog.LogLevel(cfg.LogLevel)) - cm, err := connmgr.NewConnManager( - cfg.LowPeers, - cfg.HighPeers, - connmgr.WithGracePeriod(cfg.GracePeersShutdown), - ) - if err != nil { - return nil, fmt.Errorf("p2p create conn mgr: %w", err) - } streamer := *yamux.DefaultTransport streamer.Config().ConnectionWriteTimeout = 25 * time.Second // should be NOT exposed in the config ps, err := pstoremem.NewPeerstore() @@ -299,7 +291,17 @@ func New( ) } if !cfg.DisableConnectionManager { + cm, err := connmgr.NewConnManager( + cfg.LowPeers, + cfg.HighPeers, + connmgr.WithGracePeriod(cfg.GracePeersShutdown), + ) + if err != nil { + return nil, fmt.Errorf("p2p create conn mgr: %w", err) + } lopts = append(lopts, libp2p.ConnectionManager(cm)) + } else { + lopts = append(lopts, libp2p.ConnectionManager(&ccmgr.NullConnMgr{})) } if len(cfg.AdvertiseAddress) > 0 { lopts = append( From 1f28eaec23f361d98b7a71ad91a0a47e44ee1ae9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:03:59 +0000 Subject: [PATCH 3/4] build(deps): Bump go.uber.org/mock from 0.3.0 to 0.4.0 (#5386) Bumps [go.uber.org/mock](https://github.com/uber/mock) from 0.3.0 to 0.4.0. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 890793bab1..cc5fd5e4c3 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/zeebo/blake3 v0.2.3 - go.uber.org/mock v0.3.0 + go.uber.org/mock v0.4.0 go.uber.org/zap v1.26.0 golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb golang.org/x/sync v0.5.0 diff --git a/go.sum b/go.sum index 439c94e316..bd0f441873 100644 --- a/go.sum +++ b/go.sum @@ -720,8 +720,8 @@ go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpK go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= -go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= From c580fbac09987f86aa1f2d9ab8320e27d6188d04 Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Thu, 21 Dec 2023 10:37:25 +0000 Subject: [PATCH 4/4] spawn a process to download atxs hinted by a server (#5377) related: https://github.com/spacemeshos/go-spacemesh/issues/5366 this change disables sync reconciliation at the end and start of epoch, normal atx downloading works the way it was working. instead it will ask peers for atxs that are recorded in the set, shared by the server. --- config/mainnet.go | 1 + node/node.go | 15 ++++ syncer/atxsync/atxsync.go | 75 ++++++++++++++++++++ syncer/atxsync/atxsync_test.go | 126 +++++++++++++++++++++++++++++++++ syncer/atxsync/mocks/mocks.go | 78 ++++++++++++++++++++ syncer/syncer.go | 6 +- 6 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 syncer/atxsync/atxsync.go create mode 100644 syncer/atxsync/atxsync_test.go create mode 100644 syncer/atxsync/mocks/mocks.go diff --git a/config/mainnet.go b/config/mainnet.go index 6c42334c60..bc21d26410 100644 --- a/config/mainnet.go +++ b/config/mainnet.go @@ -172,6 +172,7 @@ func MainnetConfig() Config { Standalone: false, GossipDuration: 50 * time.Second, OutOfSyncThresholdLayers: 36, // 3h + DisableAtxReconciliation: true, }, Recovery: checkpoint.DefaultConfig(), Cache: datastore.DefaultConfig(), diff --git a/node/node.go b/node/node.go index cf7ac874ae..86ead1b394 100644 --- a/node/node.go +++ b/node/node.go @@ -69,6 +69,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql/localsql" dbmetrics "github.com/spacemeshos/go-spacemesh/sql/metrics" "github.com/spacemeshos/go-spacemesh/syncer" + "github.com/spacemeshos/go-spacemesh/syncer/atxsync" "github.com/spacemeshos/go-spacemesh/syncer/blockssync" "github.com/spacemeshos/go-spacemesh/system" "github.com/spacemeshos/go-spacemesh/timesync" @@ -1154,6 +1155,20 @@ func (app *App) listenToUpdates(ctx context.Context) { } if len(update.Data.ActiveSet) > 0 { app.hOracle.UpdateActiveSet(update.Data.Epoch, update.Data.ActiveSet) + set := update.Data.ActiveSet + app.eg.Go(func() error { + if err := atxsync.Download( + ctx, + 10*time.Second, + app.addLogger(SyncLogger, app.log).Zap(), + app.db, + app.fetcher, + set, + ); err != nil { + app.errCh <- err + } + return nil + }) } } } diff --git a/syncer/atxsync/atxsync.go b/syncer/atxsync/atxsync.go new file mode 100644 index 0000000000..c889157e6c --- /dev/null +++ b/syncer/atxsync/atxsync.go @@ -0,0 +1,75 @@ +package atxsync + +import ( + "context" + "math/rand" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/atxs" +) + +//go:generate mockgen -typed -package=mocks -destination=./mocks/mocks.go -source=./atxsync.go +type atxFetcher interface { + GetAtxs(context.Context, []types.ATXID) error +} + +func getMissing(db *sql.Database, set []types.ATXID) ([]types.ATXID, error) { + missing := []types.ATXID{} + for _, atx := range set { + exist, err := atxs.Has(db, atx) + if err != nil { + return nil, err + } + if !exist { + missing = append(missing, atx) + } + } + return missing, nil +} + +// Download specified set of atxs from peers in the network. +// +// actual retry interval will be between [retryInterval, 2*retryInterval]. +func Download( + ctx context.Context, + retryInterval time.Duration, + logger *zap.Logger, + db *sql.Database, + fetcher atxFetcher, + set []types.ATXID, +) error { + total := len(set) + for { + missing, err := getMissing(db, set) + if err != nil { + return err + } + set = missing + downloaded := total - len(missing) + logger.Info("downloaded atxs", + zap.Int("total", total), + zap.Int("downloaded", downloaded), + zap.Array("missing", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, atx := range missing { + enc.AppendString(atx.ShortString()) + } + return nil + }))) + if len(missing) == 0 { + return nil + } + if err := fetcher.GetAtxs(ctx, missing); err != nil { + logger.Debug("failed to fetch atxs", zap.Error(err)) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(retryInterval + time.Duration(rand.Int63n(int64(retryInterval)))): + } + } + } +} diff --git a/syncer/atxsync/atxsync_test.go b/syncer/atxsync/atxsync_test.go new file mode 100644 index 0000000000..14c11ef0d6 --- /dev/null +++ b/syncer/atxsync/atxsync_test.go @@ -0,0 +1,126 @@ +package atxsync + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/log/logtest" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/atxs" + "github.com/spacemeshos/go-spacemesh/syncer/atxsync/mocks" +) + +func atx(id types.ATXID) *types.VerifiedActivationTx { + atx := &types.ActivationTx{InnerActivationTx: types.InnerActivationTx{ + NIPostChallenge: types.NIPostChallenge{ + PublishEpoch: 1, + }, + NumUnits: 1, + }} + atx.SetID(id) + atx.SetEffectiveNumUnits(1) + atx.SetReceived(time.Now()) + copy(atx.SmesherID[:], id[:]) + vatx, err := atx.Verify(0, 1) + if err != nil { + panic(err) + } + return vatx +} + +func id(id ...byte) types.ATXID { + return types.BytesToATXID(id) +} + +type fetchRequest struct { + request []types.ATXID + result []*types.VerifiedActivationTx + error error +} + +func TestDownload(t *testing.T) { + canceled, cancel := context.WithCancel(context.Background()) + cancel() + for _, tc := range []struct { + desc string + ctx context.Context + retry time.Duration + existing []*types.VerifiedActivationTx + set []types.ATXID + fetched []fetchRequest + rst error + }{ + { + desc: "all existing", + ctx: context.Background(), + existing: []*types.VerifiedActivationTx{atx(id(1)), atx(id(2)), atx(id(3))}, + set: []types.ATXID{id(1), id(2), id(3)}, + }, + { + desc: "with multiple requests", + ctx: context.Background(), + existing: []*types.VerifiedActivationTx{atx(id(1))}, + retry: 1, + fetched: []fetchRequest{ + { + request: []types.ATXID{id(2), id(3)}, + error: errors.New("test"), + result: []*types.VerifiedActivationTx{atx(id(2))}, + }, + {request: []types.ATXID{id(3)}, result: []*types.VerifiedActivationTx{atx(id(3))}}, + }, + set: []types.ATXID{id(1), id(2), id(3)}, + }, + { + desc: "continue on error", + ctx: context.Background(), + retry: 1, + existing: []*types.VerifiedActivationTx{atx(id(1))}, + fetched: []fetchRequest{ + {request: []types.ATXID{id(2)}, error: errors.New("test")}, + {request: []types.ATXID{id(2)}, result: []*types.VerifiedActivationTx{atx(id(2))}}, + }, + set: []types.ATXID{id(1), id(2)}, + }, + { + desc: "exit on context", + ctx: canceled, + retry: 1, + existing: []*types.VerifiedActivationTx{atx(id(1))}, + fetched: []fetchRequest{ + {request: []types.ATXID{id(2)}, error: errors.New("test")}, + }, + set: []types.ATXID{id(1), id(2)}, + rst: context.Canceled, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + logger := logtest.New(t) + db := sql.InMemory() + ctrl := gomock.NewController(t) + fetcher := mocks.NewMockatxFetcher(ctrl) + for _, atx := range tc.existing { + require.NoError(t, atxs.Add(db, atx)) + } + for i := range tc.fetched { + req := tc.fetched[i] + fetcher.EXPECT(). + GetAtxs(tc.ctx, req.request). + Times(1). + DoAndReturn(func(_ context.Context, _ []types.ATXID) error { + for _, atx := range req.result { + require.NoError(t, atxs.Add(db, atx)) + } + return req.error + }) + } + require.Equal(t, tc.rst, Download(tc.ctx, tc.retry, logger.Zap(), db, fetcher, tc.set)) + }) + } +} diff --git a/syncer/atxsync/mocks/mocks.go b/syncer/atxsync/mocks/mocks.go new file mode 100644 index 0000000000..a5e86d876c --- /dev/null +++ b/syncer/atxsync/mocks/mocks.go @@ -0,0 +1,78 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./atxsync.go +// +// Generated by this command: +// +// mockgen -typed -package=mocks -destination=./mocks/mocks.go -source=./atxsync.go +// +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + types "github.com/spacemeshos/go-spacemesh/common/types" + gomock "go.uber.org/mock/gomock" +) + +// MockatxFetcher is a mock of atxFetcher interface. +type MockatxFetcher struct { + ctrl *gomock.Controller + recorder *MockatxFetcherMockRecorder +} + +// MockatxFetcherMockRecorder is the mock recorder for MockatxFetcher. +type MockatxFetcherMockRecorder struct { + mock *MockatxFetcher +} + +// NewMockatxFetcher creates a new mock instance. +func NewMockatxFetcher(ctrl *gomock.Controller) *MockatxFetcher { + mock := &MockatxFetcher{ctrl: ctrl} + mock.recorder = &MockatxFetcherMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockatxFetcher) EXPECT() *MockatxFetcherMockRecorder { + return m.recorder +} + +// GetAtxs mocks base method. +func (m *MockatxFetcher) GetAtxs(arg0 context.Context, arg1 []types.ATXID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAtxs", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetAtxs indicates an expected call of GetAtxs. +func (mr *MockatxFetcherMockRecorder) GetAtxs(arg0, arg1 any) *atxFetcherGetAtxsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAtxs", reflect.TypeOf((*MockatxFetcher)(nil).GetAtxs), arg0, arg1) + return &atxFetcherGetAtxsCall{Call: call} +} + +// atxFetcherGetAtxsCall wrap *gomock.Call +type atxFetcherGetAtxsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *atxFetcherGetAtxsCall) Return(arg0 error) *atxFetcherGetAtxsCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *atxFetcherGetAtxsCall) Do(f func(context.Context, []types.ATXID) error) *atxFetcherGetAtxsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *atxFetcherGetAtxsCall) DoAndReturn(f func(context.Context, []types.ATXID) error) *atxFetcherGetAtxsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/syncer/syncer.go b/syncer/syncer.go index 629665838a..840bdab45a 100644 --- a/syncer/syncer.go +++ b/syncer/syncer.go @@ -28,6 +28,7 @@ type Config struct { MaxStaleDuration time.Duration Standalone bool GossipDuration time.Duration + DisableAtxReconciliation bool `mapstructure:"disable-atx-reconciliation"` OutOfSyncThresholdLayers uint32 `mapstructure:"out-of-sync-threshold"` } @@ -440,7 +441,10 @@ func (s *Syncer) syncAtx(ctx context.Context) error { return err } } - + if s.cfg.DisableAtxReconciliation { + s.logger.Debug("atx sync disabled") + return nil + } // steady state atx syncing curr := s.ticker.CurrentLayer() if float64(