diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae522121c..4fe35b361f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ See [RELEASE](./RELEASE.md) for workflow instructions. ## Release v1.3.8 +### Features + +* [#5517](https://github.com/spacemeshos/go-spacemesh/pull/5517) + Added a flag `--smeshing-opts-verifying-disable` and a config parameter `smeshing-opts-verifying-disable` + meant for disabling verifying POST in incoming ATXs on private nodes. + The verification should be performed by the public node instead. + ### Improvements * [#5441](https://github.com/spacemeshos/go-spacemesh/pull/5441) diff --git a/activation/handler.go b/activation/handler.go index 4c7b1f2b7b..54edf52819 100644 --- a/activation/handler.go +++ b/activation/handler.go @@ -49,7 +49,6 @@ type Handler struct { log log.Log mu sync.Mutex fetcher system.Fetcher - poetCfg PoetConfig signerMtx sync.Mutex signers map[types.NodeID]*signing.EdSigner @@ -75,7 +74,6 @@ func NewHandler( beacon AtxReceiver, tortoise system.Tortoise, log log.Log, - poetCfg PoetConfig, ) *Handler { return &Handler{ local: local, @@ -91,7 +89,6 @@ func NewHandler( fetcher: fetcher, beacon: beacon, tortoise: tortoise, - poetCfg: poetCfg, signers: make(map[types.NodeID]*signing.EdSigner), inProgress: make(map[types.ATXID][]chan error), diff --git a/activation/handler_test.go b/activation/handler_test.go index a8a4a68ca1..f8b640eba4 100644 --- a/activation/handler_test.go +++ b/activation/handler_test.go @@ -117,7 +117,6 @@ func newTestHandler(tb testing.TB, goldenATXID types.ATXID) *testHandler { mbeacon, mtortoise, lg, - PoetConfig{}, ) return &testHandler{ Handler: atxHdlr, diff --git a/activation/post.go b/activation/post.go index c3a29446c4..ddf71c0ecb 100644 --- a/activation/post.go +++ b/activation/post.go @@ -79,6 +79,11 @@ func DefaultPostProvingOpts() PostProvingOpts { // PostProofVerifyingOpts are the options controlling POST proving process. type PostProofVerifyingOpts struct { + // Disable verifying POST proofs. Experimental. + // Use with caution, only on private nodes with a trusted public peer that + // validates the proofs. + Disabled bool `mapstructure:"smeshing-opts-verifying-disable"` + // Number of workers spawned to verify proofs. Workers int `mapstructure:"smeshing-opts-verifying-workers"` // The minimum number of verifying workers to keep diff --git a/activation/post_verifier.go b/activation/post_verifier.go index fbe8e7361b..4572a94d1f 100644 --- a/activation/post_verifier.go +++ b/activation/post_verifier.go @@ -60,7 +60,7 @@ func (a autoscaler) run(stop chan struct{}, s scaler, min, target int) { } } -type OffloadingPostVerifier struct { +type offloadingPostVerifier struct { eg errgroup.Group log *zap.Logger verifier PostVerifier @@ -97,40 +97,79 @@ func (v *postVerifier) Verify( return v.ProofVerifier.Verify(p, m, v.cfg, v.logger, opts...) } -// NewPostVerifier creates a new post verifier. -func NewPostVerifier(cfg PostConfig, logger *zap.Logger, opts ...verifying.OptionFunc) (PostVerifier, error) { - verifier, err := verifying.NewProofVerifier(opts...) - if err != nil { - return nil, err +type postVerifierOpts struct { + opts PostProofVerifyingOpts + prioritizedIds []types.NodeID + autoscaling bool +} + +type PostVerifierOpt func(v *postVerifierOpts) + +func WithVerifyingOpts(opts PostProofVerifyingOpts) PostVerifierOpt { + return func(v *postVerifierOpts) { + v.opts = opts } +} - return &postVerifier{logger: logger, ProofVerifier: verifier, cfg: cfg.ToConfig()}, nil +func PrioritizedIDs(ids ...types.NodeID) PostVerifierOpt { + return func(v *postVerifierOpts) { + v.prioritizedIds = ids + } } -type OffloadingPostVerifierOpt func(v *OffloadingPostVerifier) +func WithAutoscaling() PostVerifierOpt { + return func(v *postVerifierOpts) { + v.autoscaling = true + } +} -func PrioritizedIDs(ids ...types.NodeID) OffloadingPostVerifierOpt { - return func(v *OffloadingPostVerifier) { - for _, id := range ids { - v.prioritizedIds[id] = struct{}{} - } +// NewPostVerifier creates a new post verifier. +func NewPostVerifier(cfg PostConfig, logger *zap.Logger, opts ...PostVerifierOpt) (PostVerifier, error) { + options := &postVerifierOpts{ + opts: DefaultPostVerifyingOpts(), } + for _, o := range opts { + o(options) + } + if options.opts.Disabled { + logger.Warn("verifying post proofs is disabled") + return &noopPostVerifier{}, nil + } + + logger.Debug("creating post verifier") + verifier, err := verifying.NewProofVerifier(verifying.WithPowFlags(options.opts.Flags.Value())) + logger.Debug("created post verifier", zap.Error(err)) + if err != nil { + return nil, err + } + workers := options.opts.Workers + minWorkers := min(options.opts.MinWorkers, workers) + offloadingVerifier := newOffloadingPostVerifier( + &postVerifier{logger: logger, ProofVerifier: verifier, cfg: cfg.ToConfig()}, + workers, + logger, + options.prioritizedIds..., + ) + if options.autoscaling && minWorkers != workers { + offloadingVerifier.autoscale(minWorkers, workers) + } + return offloadingVerifier, nil } -// NewOffloadingPostVerifier creates a new post proof verifier with the given number of workers. +// newOffloadingPostVerifier creates a new post proof verifier with the given number of workers. // The verifier will distribute incoming proofs between the workers. // It will block if all workers are busy. // // SAFETY: The `verifier` must be safe to use concurrently. // // The verifier must be closed after use with Close(). -func NewOffloadingPostVerifier( +func newOffloadingPostVerifier( verifier PostVerifier, numWorkers int, logger *zap.Logger, - opts ...OffloadingPostVerifierOpt, -) *OffloadingPostVerifier { - v := &OffloadingPostVerifier{ + prioritizedIds ...types.NodeID, +) *offloadingPostVerifier { + v := &offloadingPostVerifier{ log: logger, workers: make([]*postVerifierWorker, 0, numWorkers), prioritized: make(chan *verifyPostJob, numWorkers), @@ -139,9 +178,8 @@ func NewOffloadingPostVerifier( verifier: verifier, prioritizedIds: make(map[types.NodeID]struct{}), } - - for _, o := range opts { - o(v) + for _, id := range prioritizedIds { + v.prioritizedIds[id] = struct{}{} } v.log.Info("starting post verifier") @@ -152,7 +190,7 @@ func NewOffloadingPostVerifier( // Turn on automatic scaling of the number of workers. // The number of workers will be scaled between `min` and `target` (inclusive). -func (v *OffloadingPostVerifier) Autoscale(min, target int) { +func (v *offloadingPostVerifier) autoscale(min, target int) { a, err := newAutoscaler() if err != nil { v.log.Panic("failed to create autoscaler", zap.Error(err)) @@ -165,7 +203,7 @@ func (v *OffloadingPostVerifier) Autoscale(min, target int) { // SAFETY: Must not be called concurrently. // This is satisfied by the fact that the only caller is the autoscaler, // which executes scale() serially. -func (v *OffloadingPostVerifier) scale(target int) { +func (v *offloadingPostVerifier) scale(target int) { v.log.Info("scaling post verifier", zap.Int("current", len(v.workers)), zap.Int("new", target)) if target > len(v.workers) { @@ -192,7 +230,7 @@ func (v *OffloadingPostVerifier) scale(target int) { } } -func (v *OffloadingPostVerifier) Verify( +func (v *offloadingPostVerifier) Verify( ctx context.Context, p *shared.Proof, m *shared.ProofMetadata, @@ -237,7 +275,7 @@ func (v *OffloadingPostVerifier) Verify( } } -func (v *OffloadingPostVerifier) Close() error { +func (v *offloadingPostVerifier) Close() error { select { case <-v.stop: return nil @@ -275,3 +313,18 @@ func (w *postVerifierWorker) start() { } } } + +type noopPostVerifier struct{} + +func (v *noopPostVerifier) Verify( + _ context.Context, + _ *shared.Proof, + _ *shared.ProofMetadata, + _ ...verifying.OptionFunc, +) error { + return nil +} + +func (v *noopPostVerifier) Close() error { + return nil +} diff --git a/activation/post_verifier_scaling_test.go b/activation/post_verifier_scaling_test.go index 8d0ff7c2d5..7fbb199319 100644 --- a/activation/post_verifier_scaling_test.go +++ b/activation/post_verifier_scaling_test.go @@ -48,7 +48,7 @@ func TestAutoScaling(t *testing.T) { func TestPostVerifierScaling(t *testing.T) { // 0 workers - no one will verify the proof mockVerifier := NewMockPostVerifier(gomock.NewController(t)) - v := NewOffloadingPostVerifier(mockVerifier, 0, zaptest.NewLogger(t)) + v := newOffloadingPostVerifier(mockVerifier, 0, zaptest.NewLogger(t)) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() diff --git a/activation/post_verifier_test.go b/activation/post_verifier_test.go index fb5a8140d2..db949914e7 100644 --- a/activation/post_verifier_test.go +++ b/activation/post_verifier_test.go @@ -1,4 +1,4 @@ -package activation_test +package activation import ( "context" @@ -9,10 +9,10 @@ import ( "github.com/spacemeshos/post/shared" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "go.uber.org/zap" "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" - "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/common/types" ) @@ -20,8 +20,8 @@ func TestOffloadingPostVerifier(t *testing.T) { proof := shared.Proof{} metadata := shared.ProofMetadata{} - verifier := activation.NewMockPostVerifier(gomock.NewController(t)) - offloadingVerifier := activation.NewOffloadingPostVerifier(verifier, 1, zaptest.NewLogger(t)) + verifier := NewMockPostVerifier(gomock.NewController(t)) + offloadingVerifier := newOffloadingPostVerifier(verifier, 1, zaptest.NewLogger(t)) defer offloadingVerifier.Close() verifier.EXPECT().Close().Return(nil) @@ -35,7 +35,7 @@ func TestOffloadingPostVerifier(t *testing.T) { } func TestPostVerifierDetectsInvalidProof(t *testing.T) { - verifier, err := activation.NewPostVerifier(activation.PostConfig{}, zaptest.NewLogger(t)) + verifier, err := NewPostVerifier(PostConfig{}, zaptest.NewLogger(t)) require.NoError(t, err) defer verifier.Close() require.Error(t, verifier.Verify(context.Background(), &shared.Proof{}, &shared.ProofMetadata{})) @@ -45,8 +45,8 @@ func TestPostVerifierVerifyAfterStop(t *testing.T) { proof := shared.Proof{} metadata := shared.ProofMetadata{} - verifier := activation.NewMockPostVerifier(gomock.NewController(t)) - offloadingVerifier := activation.NewOffloadingPostVerifier(verifier, 1, zaptest.NewLogger(t)) + verifier := NewMockPostVerifier(gomock.NewController(t)) + offloadingVerifier := newOffloadingPostVerifier(verifier, 1, zaptest.NewLogger(t)) defer offloadingVerifier.Close() verifier.EXPECT().Verify(gomock.Any(), &proof, &metadata, gomock.Any()).Return(nil) @@ -65,8 +65,8 @@ func TestPostVerifierNoRaceOnClose(t *testing.T) { var proof shared.Proof var metadata shared.ProofMetadata - verifier := activation.NewMockPostVerifier(gomock.NewController(t)) - offloadingVerifier := activation.NewOffloadingPostVerifier(verifier, 1, zaptest.NewLogger(t)) + verifier := NewMockPostVerifier(gomock.NewController(t)) + offloadingVerifier := newOffloadingPostVerifier(verifier, 1, zaptest.NewLogger(t)) defer offloadingVerifier.Close() verifier.EXPECT().Close().AnyTimes().Return(nil) @@ -91,9 +91,9 @@ func TestPostVerifierNoRaceOnClose(t *testing.T) { } func TestPostVerifierClose(t *testing.T) { - verifier := activation.NewMockPostVerifier(gomock.NewController(t)) + verifier := NewMockPostVerifier(gomock.NewController(t)) // 0 workers - no one will verify the proof - v := activation.NewOffloadingPostVerifier(verifier, 0, zaptest.NewLogger(t)) + v := newOffloadingPostVerifier(verifier, 0, zaptest.NewLogger(t)) verifier.EXPECT().Close().Return(nil) require.NoError(t, v.Close()) @@ -104,8 +104,8 @@ func TestPostVerifierClose(t *testing.T) { func TestPostVerifierPrioritization(t *testing.T) { nodeID := types.RandomNodeID() - verifier := activation.NewMockPostVerifier(gomock.NewController(t)) - v := activation.NewOffloadingPostVerifier(verifier, 2, zaptest.NewLogger(t), activation.PrioritizedIDs(nodeID)) + verifier := NewMockPostVerifier(gomock.NewController(t)) + v := newOffloadingPostVerifier(verifier, 2, zaptest.NewLogger(t), nodeID) verifier.EXPECT(). Verify(gomock.Any(), gomock.Any(), &shared.ProofMetadata{NodeId: nodeID.Bytes()}, gomock.Any()). @@ -114,3 +114,12 @@ func TestPostVerifierPrioritization(t *testing.T) { err := v.Verify(context.Background(), &shared.Proof{}, &shared.ProofMetadata{NodeId: nodeID.Bytes()}) require.NoError(t, err) } + +func TestVerificationDisabled(t *testing.T) { + opts := DefaultPostVerifyingOpts() + opts.Disabled = true + v, err := NewPostVerifier(DefaultPostConfig(), zap.NewNop(), WithVerifyingOpts(opts)) + require.NoError(t, err) + require.NoError(t, v.Verify(context.Background(), &shared.Proof{}, &shared.ProofMetadata{})) + require.NoError(t, v.Close()) +} diff --git a/checkpoint/recovery_test.go b/checkpoint/recovery_test.go index 129701fd5a..d6a066f423 100644 --- a/checkpoint/recovery_test.go +++ b/checkpoint/recovery_test.go @@ -252,7 +252,6 @@ func validateAndPreserveData( mreceiver, mtrtl, lg, - activation.PoetConfig{}, ) mfetch.EXPECT().GetAtxs(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() for i, vatx := range deps { diff --git a/cmd/root.go b/cmd/root.go index d8d56db1f7..768506f513 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -278,6 +278,13 @@ func AddCommands(cmd *cobra.Command) { /**======================== PoST Verifying Flags ========================== **/ + cmd.PersistentFlags().BoolVar( + &cfg.SMESHING.VerifyingOpts.Disabled, + "smeshing-opts-verifying-disable", + false, + "Disable verifying POST proofs. Experimental.\n"+ + "Use with caution, only on private nodes with a trusted public peer that validates the proofs.", + ) cmd.PersistentFlags().IntVar( &cfg.SMESHING.VerifyingOpts.MinWorkers, "smeshing-opts-verifying-min-workers", diff --git a/node/node.go b/node/node.go index db998b36ac..973e4e979b 100644 --- a/node/node.go +++ b/node/node.go @@ -24,7 +24,6 @@ import ( grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" "github.com/mitchellh/mapstructure" "github.com/spacemeshos/poet/server" - "github.com/spacemeshos/post/verifying" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -368,7 +367,7 @@ type App struct { tortoise *tortoise.Tortoise updater *bootstrap.Updater poetDb *activation.PoetDb - postVerifier *activation.OffloadingPostVerifier + postVerifier activation.PostVerifier postSupervisor *activation.PostSupervisor preserve *checkpoint.PreservedData errCh chan error @@ -564,27 +563,17 @@ func (app *App) initServices(ctx context.Context) error { poetDb := activation.NewPoetDb(app.db, app.addLogger(PoetDbLogger, lg)) - nipostValidatorLogger := app.addLogger(NipostValidatorLogger, lg) - - lg.Debug("creating post verifier") verifier, err := activation.NewPostVerifier( app.Config.POST, - nipostValidatorLogger.Zap(), - verifying.WithPowFlags(app.Config.SMESHING.VerifyingOpts.Flags.Value()), + app.addLogger(NipostValidatorLogger, lg).Zap(), + activation.WithVerifyingOpts(app.Config.SMESHING.VerifyingOpts), + activation.PrioritizedIDs(app.edSgn.NodeID()), + activation.WithAutoscaling(), ) - lg.With().Debug("created post verifier", log.Err(err)) if err != nil { - return err + return fmt.Errorf("creating post verifier: %w", err) } - minWorkers := app.Config.SMESHING.VerifyingOpts.MinWorkers - workers := app.Config.SMESHING.VerifyingOpts.Workers - app.postVerifier = activation.NewOffloadingPostVerifier( - verifier, - workers, - nipostValidatorLogger.Zap(), - activation.PrioritizedIDs(app.edSgn.NodeID()), - ) - app.postVerifier.Autoscale(minWorkers, workers) + app.postVerifier = verifier validator := activation.NewValidator( poetDb, @@ -715,7 +704,6 @@ func (app *App) initServices(ctx context.Context) error { beaconProtocol, trtl, app.addLogger(ATXHandlerLogger, lg), - app.Config.POET, ) atxHandler.Register(app.edSgn)