diff --git a/activation/nipost_integration_test.go.bak b/activation/e2e/nipost_test.go similarity index 53% rename from activation/nipost_integration_test.go.bak rename to activation/e2e/nipost_test.go index 00ff706b5cb..5ebdd18623c 100644 --- a/activation/nipost_integration_test.go.bak +++ b/activation/e2e/nipost_test.go @@ -3,15 +3,18 @@ package activation_test import ( "context" "errors" + "fmt" "os" + "os/exec" + "path/filepath" "testing" "time" "github.com/spacemeshos/poet/logging" "github.com/spacemeshos/post/config" "github.com/spacemeshos/post/initialization" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zeebo/assert" "go.uber.org/mock/gomock" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -19,8 +22,10 @@ import ( "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/activation" + "github.com/spacemeshos/go-spacemesh/api/grpcserver" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/datastore" + "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/log/logtest" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" @@ -38,28 +43,68 @@ func TestMain(m *testing.M) { os.Exit(res) } -func initPost(tb testing.TB, log *zap.Logger, dir string) { +func spawnPoet(tb testing.TB, opts ...HTTPPoetOpt) *HTTPPoetTestHarness { tb.Helper() + ctx, cancel := context.WithCancel(logging.NewContext(context.Background(), zaptest.NewLogger(tb))) - cfg := activation.DefaultPostConfig() - - sig, err := signing.NewEdSigner() + poetProver, err := NewHTTPPoetTestHarness(ctx, tb.TempDir(), opts...) require.NoError(tb, err) - id := sig.NodeID() + require.NotNil(tb, poetProver) - opts := activation.DefaultPostSetupOpts() - opts.DataDir = dir - opts.ProviderID.SetInt64(int64(initialization.CPUProviderID())) - opts.Scrypt.N = 2 // Speedup initialization in tests. + var eg errgroup.Group + tb.Cleanup(func() { + cancel() + eg.Wait() + }) + eg.Go(func() error { + err := poetProver.Service.Start(ctx) + return errors.Join(err, poetProver.Service.Close()) + }) - goldenATXID := types.ATXID{2, 3, 4} + return poetProver +} - cdb := datastore.NewCachedDB(sql.InMemory(), logtest.New(tb)) - provingOpts := activation.DefaultPostProvingOpts() - provingOpts.Flags = config.RecommendedPowFlags() - mgr, err := activation.NewPostSetupManager(id, cfg, log.Named("manager"), cdb, goldenATXID, provingOpts) +func launchPostSupervisor(tb testing.TB, log *zap.Logger, cfg grpcserver.Config, postDir string) func() { + path, err := exec.Command("go", "env", "GOMOD").Output() require.NoError(tb, err) + opts := activation.PostSupervisorConfig{ + PostServiceCmd: filepath.Join(filepath.Dir(string(path)), "build", "service"), + DataDir: postDir, + NodeAddress: fmt.Sprintf("http://%s", cfg.PublicListener), + PowDifficulty: activation.DefaultPostConfig().PowDifficulty, + PostServiceMode: "light", + N: 2, + } + + ps, err := activation.NewPostSupervisor(log, opts) + require.NoError(tb, err) + require.NotNil(tb, ps) + return func() { assert.NoError(tb, ps.Close()) } +} + +func launchServer(tb testing.TB, services ...grpcserver.ServiceAPI) (grpcserver.Config, func()) { + cfg := grpcserver.DefaultTestConfig() + + // run on random ports + grpcService := grpcserver.New("127.0.0.1:0", logtest.New(tb).Named("grpc")) + + // attach services + for _, svc := range services { + svc.RegisterService(grpcService) + } + + require.NoError(tb, grpcService.Start()) + + // update config with bound addresses + cfg.PublicListener = grpcService.BoundAddress + + return cfg, func() { assert.NoError(tb, grpcService.Close()) } +} + +func initPost(tb testing.TB, logger *zap.Logger, mgr *activation.PostSetupManager, opts activation.PostSetupOpts) *activation.PostSetupManager { + tb.Helper() + ctx, cancel := context.WithCancel(context.Background()) tb.Cleanup(cancel) @@ -77,7 +122,7 @@ func initPost(tb testing.TB, log *zap.Logger, dir string) { status := mgr.Status() require.GreaterOrEqual(tb, status.NumLabelsWritten, lastStatus.NumLabelsWritten) - if status.NumLabelsWritten == uint64(opts.NumUnits)*cfg.LabelsPerUnit { + if status.NumLabelsWritten == uint64(mgr.LastOpts().NumUnits)*mgr.Config().LabelsPerUnit { return nil } require.Equal(tb, activation.PostSetupStateInProgress, status.State) @@ -90,41 +135,35 @@ func initPost(tb testing.TB, log *zap.Logger, dir string) { require.NoError(tb, mgr.StartSession(context.Background())) require.NoError(tb, eg.Wait()) require.Equal(tb, activation.PostSetupStateComplete, mgr.Status().State) + + return mgr } -func spawnPoet(tb testing.TB, opts ...HTTPPoetOpt) *HTTPPoetTestHarness { - tb.Helper() - ctx, cancel := context.WithCancel(logging.NewContext(context.Background(), zaptest.NewLogger(tb))) +func TestNIPostBuilderWithClients(t *testing.T) { + logger := zaptest.NewLogger(t) + ctrl := gomock.NewController(t) - poetProver, err := NewHTTPPoetTestHarness(ctx, tb.TempDir(), opts...) - require.NoError(tb, err) - require.NotNil(tb, poetProver) + sig, err := signing.NewEdSigner() + require.NoError(t, err) - var eg errgroup.Group - tb.Cleanup(func() { - cancel() - eg.Wait() - }) - eg.Go(func() error { - err := poetProver.Service.Start(ctx) - return errors.Join(err, poetProver.Service.Close()) - }) + postDir := t.TempDir() + goldenATX := types.ATXID{2, 3, 4} - return poetProver -} + cfg := activation.DefaultPostConfig() -// TODO(mafa): start post service with supervisor. -func buildNIPost(tb testing.TB, postProvider *testPostManager, nipostChallenge types.NIPostChallenge, poetDb poetDbAPI, validator nipostValidator) *types.NIPost { - require.NoError(tb, postProvider.PrepareInitializer(context.Background(), postProvider.opts)) - require.NoError(tb, postProvider.StartSession(context.Background())) - mclock := activation.NewMocklayerClock(gomock.NewController(tb)) - mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn( - func(got types.LayerID) time.Time { - // time.Now() ~= currentLayer - genesis := time.Now().Add(-time.Duration(postGenesisEpoch.FirstLayer()) * layerDuration) - return genesis.Add(layerDuration * time.Duration(got)) - }, - ) + cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) + + provingOpts := activation.DefaultPostProvingOpts() + provingOpts.Flags = config.RecommendedPowFlags() + + mgr, err := activation.NewPostSetupManager(sig.NodeID(), cfg, logger, cdb, goldenATX, provingOpts) + require.NoError(t, err) + + opts := activation.DefaultPostSetupOpts() + opts.DataDir = postDir + opts.ProviderID.SetInt64(int64(initialization.CPUProviderID())) + opts.Scrypt.N = 2 // Speedup initialization in tests. + initPost(t, logger.Named("manager"), mgr, opts) epoch := layersPerEpoch * layerDuration poetCfg := activation.PoetConfig{ @@ -135,56 +174,76 @@ func buildNIPost(tb testing.TB, postProvider *testPostManager, nipostChallenge t RequestRetryDelay: epoch / 50, MaxRequestRetries: 10, } + poetProver := spawnPoet(t, WithGenesis(time.Now()), WithEpochDuration(epoch), WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap)) - poetProver := spawnPoet(tb, WithGenesis(time.Now()), WithEpochDuration(epoch), WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap)) + mclock := activation.NewMocklayerClock(ctrl) + mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn( + func(got types.LayerID) time.Time { + // time.Now() ~= currentLayer + genesis := time.Now().Add(-time.Duration(postGenesisEpoch.FirstLayer()) * layerDuration) + return genesis.Add(layerDuration * time.Duration(got)) + }, + ) + + poetDb := activation.NewPoetDb(sql.InMemory(), log.NewFromLog(logger).Named("poetDb")) + + verifier, err := activation.NewPostVerifier(mgr.Config(), logger.Named("verifier")) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, verifier.Close()) }) + + v := activation.NewValidator(poetDb, mgr.Config(), mgr.LastOpts().Scrypt, verifier) - signer, err := signing.NewEdSigner() - require.NoError(tb, err) nb, err := activation.NewNIPostBuilder( - postProvider.id, - postProvider, + sig.NodeID(), + mgr, poetDb, []string{poetProver.RestURL().String()}, - tb.TempDir(), - logtest.New(tb, zapcore.DebugLevel), - signer, + t.TempDir(), + logtest.New(t, zapcore.DebugLevel), + sig, poetCfg, mclock, - activation.WithNipostValidator(validator), + activation.WithNipostValidator(v), ) - require.NoError(tb, err) - nipost, err := nb.BuildNIPost(context.Background(), &nipostChallenge) - require.NoError(tb, err) - return nipost -} + require.NoError(t, err) + + connected := make(chan struct{}) + con := grpcserver.NewMockpostConnectionListener(ctrl) + con.EXPECT().Connected(gomock.Any()).DoAndReturn(func(c activation.PostClient) { + close(connected) + }).Times(1) + con.EXPECT().Disconnected(gomock.Any()).Times(1) + + svc := grpcserver.NewPostService(logger, nb, con) + grpcCfg, cleanup := launchServer(t, svc) + t.Cleanup(cleanup) + + t.Cleanup(launchPostSupervisor(t, logger, grpcCfg, postDir)) + + select { + case <-connected: + case <-time.After(10 * time.Second): + require.Fail(t, "timed out waiting for connection") + } -func TestNIPostBuilderWithClients(t *testing.T) { challenge := types.NIPostChallenge{ PublishEpoch: postGenesisEpoch + 2, } - poetDb := activation.NewPoetDb(sql.InMemory(), logtest.New(t).WithName("poetDb")) - postCfg := activation.DefaultPostConfig() - postCfg.PowDifficulty[0] = 1 - postProvider := newTestPostManager(t, withPostConfig(postCfg)) - verifier, err := activation.NewPostVerifier(postProvider.Config(), zaptest.NewLogger(t).Named("verifier")) - require.NoError(t, err) - defer verifier.Close() - v := activation.NewValidator(poetDb, postProvider.Config(), postProvider.opts.Scrypt, verifier) - nipost := buildNIPost(t, postProvider, challenge, poetDb, v) + nipost, err := nb.BuildNIPost(context.Background(), &challenge) + require.NoError(t, err) _, err = v.NIPost( context.Background(), - postProvider.id, - postProvider.commitmentAtxId, + sig.NodeID(), + goldenATX, nipost, challenge.Hash(), - postProvider.opts.NumUnits, + mgr.LastOpts().NumUnits, ) require.NoError(t, err) } func TestNIPostBuilder_Close(t *testing.T) { - t.Parallel() r := require.New(t) ctrl := gomock.NewController(t) @@ -195,7 +254,7 @@ func TestNIPostBuilder_Close(t *testing.T) { challenge := types.NIPostChallenge{ PublishEpoch: postGenesisEpoch + 2, } - mclock := activation.NewMocklayerClock(gomock.NewController(t)) + mclock := activation.NewMocklayerClock(ctrl) mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn( func(got types.LayerID) time.Time { // time.Now() ~= currentLayer @@ -227,14 +286,24 @@ func TestNIPostBuilder_Close(t *testing.T) { } func TestNewNIPostBuilderNotInitialized(t *testing.T) { - r := require.New(t) + logger := zaptest.NewLogger(t) + ctrl := gomock.NewController(t) - challenge := types.NIPostChallenge{ - PublishEpoch: postGenesisEpoch + 2, - } - challengeHash := challenge.Hash() + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + postDir := t.TempDir() + goldenATX := types.ATXID{2, 3, 4} + + cfg := activation.DefaultPostConfig() + + cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) + + provingOpts := activation.DefaultPostProvingOpts() + provingOpts.Flags = config.RecommendedPowFlags() - postProvider := newTestPostManager(t) + mgr, err := activation.NewPostSetupManager(sig.NodeID(), cfg, logger, cdb, goldenATX, provingOpts) + require.NoError(t, err) epoch := layersPerEpoch * layerDuration poetCfg := activation.PoetConfig{ @@ -246,22 +315,9 @@ func TestNewNIPostBuilderNotInitialized(t *testing.T) { MaxRequestRetries: 10, } - poetClient := activation.NewMockPoetProvingServiceClient(gomock.NewController(t)) - poetClient.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&types.PoetRound{}, nil) - poetClient.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{ServiceID: []byte("poet")}, nil) - poetClient.EXPECT().PowParams(gomock.Any()).Return(&activation.PoetPowParams{}, nil) - poetClient.EXPECT().Address().Return("http://localhost:9999") - poetClient.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{ - PoetProof: types.PoetProof{}, - }, []types.Member{types.Member(challenge.Hash())}, nil) + poetProver := spawnPoet(t, WithGenesis(time.Now()), WithEpochDuration(epoch), WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap)) - ctrl := gomock.NewController(t) - poetDb := activation.NewMockpoetDbAPI(ctrl) - poetDb.EXPECT().GetProof(gomock.Any()).Return( - &types.PoetProof{}, &challengeHash, nil, - ) - poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) - mclock := activation.NewMocklayerClock(gomock.NewController(t)) + mclock := activation.NewMocklayerClock(ctrl) mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn( func(got types.LayerID) time.Time { // time.Now() ~= currentLayer @@ -270,46 +326,74 @@ func TestNewNIPostBuilderNotInitialized(t *testing.T) { }, ) + poetDb := activation.NewPoetDb(sql.InMemory(), log.NewFromLog(logger).Named("poetDb")) + nipostValidator := activation.NewMocknipostValidator(ctrl) nipostValidator.EXPECT().Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil) nb, err := activation.NewNIPostBuilder( - postProvider.id, - postProvider, + sig.NodeID(), + mgr, poetDb, - []string{}, + []string{poetProver.RestURL().String()}, t.TempDir(), logtest.New(t), - postProvider.signer, + sig, poetCfg, mclock, activation.WithNipostValidator(nipostValidator), - withPoetClients([]activation.PoetProvingServiceClient{poetProvider}), ) require.NoError(t, err) + connected := make(chan struct{}) + con := grpcserver.NewMockpostConnectionListener(ctrl) + con.EXPECT().Connected(gomock.Any()).DoAndReturn(func(c activation.PostClient) { + close(connected) + }).Times(1) + con.EXPECT().Disconnected(gomock.Any()).Times(1) + + svc := grpcserver.NewPostService(logger, nb, con) + grpcCfg, cleanup := launchServer(t, svc) + t.Cleanup(cleanup) + + t.Cleanup(launchPostSupervisor(t, logger, grpcCfg, postDir)) + + select { + case <-connected: + case <-time.After(10 * time.Second): + require.Fail(t, "timed out waiting for connection") + } + + challenge := types.NIPostChallenge{ + PublishEpoch: postGenesisEpoch + 2, + } + nipost, err := nb.BuildNIPost(context.Background(), &challenge) - r.EqualError(err, "post setup not complete") - r.Nil(nipost) + require.EqualError(t, err, "post setup not complete") + require.Nil(t, nipost) - r.NoError(postProvider.PrepareInitializer(context.Background(), postProvider.opts)) - r.NoError(postProvider.StartSession(context.Background())) + opts := activation.DefaultPostSetupOpts() + opts.DataDir = postDir + opts.ProviderID.SetInt64(int64(initialization.CPUProviderID())) + opts.Scrypt.N = 2 // Speedup initialization in tests. + initPost(t, logger.Named("manager"), mgr, opts) nipost, err = nb.BuildNIPost(context.Background(), &challenge) - r.NoError(err) - r.NotNil(nipost) + require.NoError(t, err) + require.NotNil(t, nipost) - verifier, err := activation.NewPostVerifier(postProvider.cfg, zaptest.NewLogger(t).Named("verifier")) - r.NoError(err) + verifier, err := activation.NewPostVerifier(mgr.Config(), zaptest.NewLogger(t).Named("verifier")) + require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, verifier.Close()) }) - v := activation.NewValidator(poetDb, postProvider.cfg, postProvider.opts.Scrypt, verifier) + + v := activation.NewValidator(poetDb, mgr.Config(), mgr.LastOpts().Scrypt, verifier) _, err = v.NIPost( context.Background(), - postProvider.id, - postProvider.goldenATXID, + sig.NodeID(), + goldenATX, nipost, challenge.Hash(), - postProvider.opts.NumUnits, + mgr.LastOpts().NumUnits, ) - r.NoError(err) + require.NoError(t, err) } diff --git a/activation/poet_integration_test.go b/activation/e2e/poet_test.go similarity index 100% rename from activation/poet_integration_test.go rename to activation/e2e/poet_test.go diff --git a/activation/validation_integration_test.go.bak b/activation/e2e/validation_test.go.bak similarity index 100% rename from activation/validation_integration_test.go.bak rename to activation/e2e/validation_test.go.bak diff --git a/api/grpcserver/post_client.go b/api/grpcserver/post_client.go index 1c35da90654..f95584d8e26 100644 --- a/api/grpcserver/post_client.go +++ b/api/grpcserver/post_client.go @@ -68,7 +68,8 @@ func (pc *postClient) Proof(ctx context.Context, challenge []byte) (*types.Post, select { case <-ctx.Done(): return nil, nil, ctx.Err() - case <-time.After(2 * time.Second): // TODO(mafa): make polling interval configurable + case <-time.After(2 * time.Second): + // TODO(mafa): make polling interval configurable continue } }