From d16cbe76f11be67cbd65474abadd37793cc09a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 1 Nov 2023 11:42:46 +0100 Subject: [PATCH 01/55] Support for poet certificates WIP --- activation/activation.go | 5 +- activation/activation_test.go | 32 +-- activation/certifier.go | 292 +++++++++++++++++++++++ activation/e2e/certifier_client_test.go | 184 ++++++++++++++ activation/e2e/nipost_test.go | 127 +++------- activation/e2e/poet_test.go | 107 ++++++--- activation/e2e/validation_test.go | 9 +- activation/interface.go | 41 +++- activation/mocks.go | 303 +++++++++++++++++++----- activation/nipost.go | 101 +++++--- activation/nipost_test.go | 152 +++++++----- activation/poet.go | 61 ++++- activation/poet_client_test.go | 2 +- go.mod | 4 +- go.sum | 2 - 15 files changed, 1101 insertions(+), 321 deletions(-) create mode 100644 activation/certifier.go create mode 100644 activation/e2e/certifier_client_test.go diff --git a/activation/activation.go b/activation/activation.go index c6e777c3fa..7926555f7b 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -82,6 +82,7 @@ type Builder struct { postService postService nipostBuilder nipostBuilder validator nipostValidator + certifier certifierService // smeshingMutex protects `StartSmeshing` and `StopSmeshing` from concurrent access smeshingMutex sync.Mutex @@ -382,6 +383,8 @@ func (b *Builder) run(ctx context.Context) { for { err := b.buildInitialPost(ctx) if err == nil { + // TODO certify initial post + // b.certifier.CertifyAll() break } b.log.Error("failed to generate initial proof:", zap.Error(err)) @@ -604,7 +607,7 @@ func (b *Builder) poetRoundStart(epoch types.EpochID) time.Time { func (b *Builder) createAtx(ctx context.Context, challenge *types.NIPostChallenge) (*types.ActivationTx, error) { pubEpoch := challenge.PublishEpoch - nipost, err := b.nipostBuilder.BuildNIPost(ctx, challenge) + nipost, err := b.nipostBuilder.BuildNIPost(ctx, challenge, b.certifier) if err != nil { return nil, fmt.Errorf("build NIPost: %w", err) } diff --git a/activation/activation_test.go b/activation/activation_test.go index 8f6389f019..3e4121599e 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -224,8 +224,8 @@ func publishAtx( NumUnits: DefaultPostSetupOpts().NumUnits, LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, challenge *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { *currLayer = currLayer.Add(buildNIPostLayerDuration) return newNIPostWithChallenge(t, challenge.Hash(), []byte("66666")), nil }) @@ -387,8 +387,8 @@ func TestBuilder_StopSmeshing_Delete(t *testing.T) { return genesis.Add(layerDuration * time.Duration(got)) }).AnyTimes() tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *types.NIPostChallenge) (*types.NIPost, error) { + BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, _ *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { <-ctx.Done() return nil, ctx.Err() }). @@ -505,7 +505,7 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { genesis := time.Now().Add(-time.Duration(currLayer) * layerDuration) return genesis.Add(layerDuration * time.Duration(got)) }).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).Return(nil, ErrATXChallengeExpired) + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, ErrATXChallengeExpired) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -560,8 +560,8 @@ func TestBuilder_PublishActivationTx_FaultyNet(t *testing.T) { NumUnits: DefaultPostSetupOpts().NumUnits, LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, challenge *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { currLayer = currLayer.Add(layersPerEpoch) return newNIPostWithChallenge(t, challenge.Hash(), []byte("66666")), nil }) @@ -636,8 +636,8 @@ func TestBuilder_PublishActivationTx_RebuildNIPostWhenTargetEpochPassed(t *testi NumUnits: DefaultPostSetupOpts().NumUnits, LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil) - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, challenge *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { currLayer = currLayer.Add(layersPerEpoch) return newNIPostWithChallenge(t, challenge.Hash(), []byte("66666")), nil }) @@ -873,8 +873,8 @@ func TestBuilder_PublishActivationTx_PrevATXWithoutPrevATX(t *testing.T) { LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, challenge *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { currentLayer = currentLayer.Add(5) return newNIPostWithChallenge(t, challenge.Hash(), poetBytes), nil }) @@ -962,8 +962,8 @@ func TestBuilder_PublishActivationTx_TargetsEpochBasedOnPosAtx(t *testing.T) { LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, challenge *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { currentLayer = currentLayer.Add(layersPerEpoch) return newNIPostWithChallenge(t, challenge.Hash(), poetBytes), nil }) @@ -1026,7 +1026,7 @@ func TestBuilder_PublishActivationTx_FailsWhenNIPostBuilderFails(t *testing.T) { return genesis.Add(layerDuration * time.Duration(got)) }).AnyTimes() nipostErr := fmt.Errorf("NIPost builder error") - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).Return(nil, nipostErr) + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nipostErr) require.ErrorIs(t, tab.PublishActivationTx(context.Background()), nipostErr) // state is preserved @@ -1139,7 +1139,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { tries := 0 var last time.Time builderConfirmation := make(chan struct{}) - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).Times(expectedTries).DoAndReturn( + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).Times(expectedTries).DoAndReturn( func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { now := time.Now() if now.Sub(last) < retryInterval { @@ -1310,7 +1310,7 @@ func TestWaitPositioningAtx(t *testing.T) { // everything else are stubs that are irrelevant for the test tab.mpostClient.EXPECT().Info(gomock.Any()).Return(&types.PostInfo{}, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).Return(&types.NIPost{}, nil).AnyTimes() + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).Return(&types.NIPost{}, nil).AnyTimes() closed := make(chan struct{}) close(closed) tab.mclock.EXPECT().AwaitLayer(types.EpochID(1).FirstLayer()).Return(closed).AnyTimes() diff --git a/activation/certifier.go b/activation/certifier.go new file mode 100644 index 0000000000..56baec6739 --- /dev/null +++ b/activation/certifier.go @@ -0,0 +1,292 @@ +package activation + +import ( + "bytes" + "context" + "crypto/ed25519" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "sync" + + "github.com/hashicorp/go-retryablehttp" + "github.com/natefinch/atomic" + "github.com/sourcegraph/conc/iter" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/post/shared" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type ProofToCertify struct { + Nonce uint32 `json:"nonce"` + Indices []byte `json:"indices"` + Pow uint64 `json:"pow"` +} + +type ProoToCertifyfMetadata struct { + NodeId []byte `json:"node_id"` + CommitmentAtxId []byte `json:"commitment_atx_id"` + + Challenge []byte `json:"challenge"` + NumUnits uint32 `json:"num_units"` + LabelsPerUnit uint64 `json:"labels_per_unit"` +} + +type CertifyRequest struct { + Proof ProofToCertify `json:"proof"` + Metadata ProoToCertifyfMetadata `json:"metadata"` +} + +type CertifyResponse struct { + Signature []byte `json:"signature"` + PubKey []byte `json:"pub_key"` +} + +type Certifier struct { + client *retryablehttp.Client + logger *zap.Logger + store *certificateStore + + post *types.Post + postInfo *types.PostInfo +} + +func NewCertifier(datadir string, logger *zap.Logger, post *types.Post, postInfo *types.PostInfo) *Certifier { + c := &Certifier{ + client: retryablehttp.NewClient(), + logger: logger, + store: openCertificateStore(datadir, logger), + post: post, + postInfo: postInfo, + } + c.client.Logger = &retryableHttpLogger{logger} + c.client.ResponseLogHook = func(logger retryablehttp.Logger, resp *http.Response) { + c.logger.Info("response received", zap.Stringer("url", resp.Request.URL), zap.Int("status", resp.StatusCode)) + } + + return c +} + +func (c *Certifier) GetCertificate(poet string) *PoetCert { + if cert, ok := c.store.get(poet); ok { + return &cert + } + return nil +} + +func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) { + info, err := poet.CertifierInfo(ctx) + if err != nil { + return nil, fmt.Errorf("querying certifier info: %w", err) + } + cert, err := c.certifyPost(ctx, info.URL, info.PubKey) + if err != nil { + return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), info.URL, info.PubKey) + } + c.store.put(poet.Address(), *cert) + + return cert, nil +} + +// CertifyAll certifies the nodeID for all poets that require a certificate. +// It optimizes the number of certification requests by taking a unique set of +// certifiers among the given poets and sending a single request to each of them. +// It returns a map of a poet address to a certificate for it. +func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[string]PoetCert { + certs := make(map[string]PoetCert) + poetsToCertify := []PoetClient{} + for _, poet := range poets { + if cert := c.GetCertificate(poet.Address()); cert != nil { + certs[poet.Address()] = *cert + } else { + poetsToCertify = append(poetsToCertify, poet) + } + } + if len(poetsToCertify) == 0 { + return certs + } + + type certInfo struct { + CertifierInfo + poet string + } + + certifierInfos := iter.Map(poetsToCertify, func(p *PoetClient) *certInfo { + poet := *p + info, err := poet.CertifierInfo(ctx) + if err != nil { + c.logger.Warn("failed to query for certifier info", zap.Error(err), zap.String("poet", poet.Address())) + return nil + } + return &certInfo{ + CertifierInfo: *info, + poet: poet.Address(), + } + }) + + type certService struct { + CertifierInfo + poets []string + } + certSvcs := make(map[string]*certService) + for _, info := range certifierInfos { + if info == nil { + continue + } + + if svc, ok := certSvcs[string(info.PubKey)]; !ok { + certSvcs[string(info.PubKey)] = &certService{ + CertifierInfo: info.CertifierInfo, + poets: []string{info.poet}, + } + } else { + svc.poets = append(svc.poets, info.poet) + } + } + + for _, svc := range certSvcs { + c.logger.Info( + "certifying for poets", + zap.Stringer("certifier", svc.URL), + zap.Array("poets", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, poet := range svc.poets { + enc.AppendString(poet) + } + return nil + })), + ) + + cert, err := c.certifyPost(ctx, svc.URL, svc.PubKey) + if err != nil { + c.logger.Warn("failed to certify", zap.Error(err), zap.Stringer("certifier", svc.URL)) + continue + } + c.logger.Info( + "sucessfully obtained certificate", + zap.Stringer("certifier", svc.URL), + zap.Binary("cert", cert.Signature), + ) + for _, poet := range svc.poets { + c.store.put(poet, *cert) + certs[poet] = *cert + } + } + c.store.persist() + return certs +} + +func (c *Certifier) certifyPost(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { + request := CertifyRequest{ + Proof: ProofToCertify{ + Pow: c.post.Pow, + Nonce: c.post.Nonce, + Indices: c.post.Indices, + }, + Metadata: ProoToCertifyfMetadata{ + NodeId: c.postInfo.NodeID[:], + CommitmentAtxId: c.postInfo.CommitmentATX[:], + LabelsPerUnit: c.postInfo.LabelsPerUnit, + NumUnits: c.postInfo.NumUnits, + Challenge: shared.ZeroChallenge, + }, + } + + jsonRequest, err := json.Marshal(request) + if err != nil { + return nil, fmt.Errorf("marshaling request: %w", err) + } + + req, err := retryablehttp.NewRequestWithContext(ctx, "POST", url.JoinPath("/certify").String(), jsonRequest) + if err != nil { + return nil, fmt.Errorf("creating HTTP request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("sending request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var body []byte + if resp.Body != nil { + body, _ = io.ReadAll(resp.Body) + } + return nil, fmt.Errorf("request failed with code %d (message: %s)", resp.StatusCode, body) + } + + certRespose := CertifyResponse{} + if err := json.NewDecoder(resp.Body).Decode(&certRespose); err != nil { + return nil, fmt.Errorf("decoding JSON response: %w", err) + } + if !bytes.Equal(certRespose.PubKey, pubkey) { + return nil, errors.New("pubkey is invalid") + } + if !ed25519.Verify(pubkey, c.postInfo.NodeID[:], certRespose.Signature) { + return nil, errors.New("signature is invalid") + } + return &PoetCert{Signature: certRespose.Signature}, nil +} + +type certificateStore struct { + // protects certs map + mu sync.RWMutex + // map of certifier public key to PoET certificate + certs map[string]PoetCert + dir string +} + +func openCertificateStore(dir string, logger *zap.Logger) *certificateStore { + store := &certificateStore{ + dir: dir, + certs: map[string]PoetCert{}, + } + + file, err := os.Open(filepath.Join(dir, "poet_certs.json")) + switch { + case errors.Is(err, os.ErrNotExist): + return store + case err != nil: + logger.Warn("failed to open poet certs file", zap.Error(err)) + return store + } + certs := make(map[string]PoetCert) + if err := json.NewDecoder(file).Decode(&certs); err != nil { + logger.Warn("failed to decode poet certs", zap.Error(err)) + return store + } + return &certificateStore{ + certs: certs, + dir: dir, + } +} + +func (s *certificateStore) get(poet string) (PoetCert, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + cert, ok := s.certs[poet] + return cert, ok +} + +func (s *certificateStore) put(poet string, cert PoetCert) { + s.mu.Lock() + defer s.mu.Unlock() + s.certs[poet] = cert +} + +func (s *certificateStore) persist() error { + s.mu.RLock() + defer s.mu.RUnlock() + buf := &bytes.Buffer{} + if err := json.NewEncoder(buf).Encode(s.certs); err != nil { + return err + } + return atomic.WriteFile(filepath.Join(s.dir, "poet_certs.json"), buf) +} diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go new file mode 100644 index 0000000000..c8fdd4d01d --- /dev/null +++ b/activation/e2e/certifier_client_test.go @@ -0,0 +1,184 @@ +package activation_test + +import ( + "context" + "crypto/ed25519" + "encoding/json" + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/spacemeshos/poet/registration" + "github.com/spacemeshos/post/initialization" + "github.com/spacemeshos/post/shared" + "github.com/spacemeshos/post/verifying" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + "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/signing" + "github.com/spacemeshos/go-spacemesh/sql" +) + +func TestCertification(t *testing.T) { + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + logger := zaptest.NewLogger(t) + cfg := activation.DefaultPostConfig() + cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) + + mgr, err := activation.NewPostSetupManager(sig.NodeID(), cfg, logger, cdb, types.ATXID{2, 3, 4}) + require.NoError(t, err) + + opts := activation.DefaultPostSetupOpts() + opts.DataDir = t.TempDir() + opts.ProviderID.SetUint32(initialization.CPUProviderID()) + opts.Scrypt.N = 2 // Speedup initialization in tests. + initPost(t, logger.Named("manager"), mgr, opts) + + svc := grpcserver.NewPostService(logger) + grpcCfg, cleanup := launchServer(t, svc) + t.Cleanup(cleanup) + t.Cleanup(launchPostSupervisor(t, logger, mgr, grpcCfg, opts)) + + require.Eventually(t, func() bool { + _, err := svc.Client(sig.NodeID()) + return err == nil + }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") + + postClient, err := svc.Client(sig.NodeID()) + require.NoError(t, err) + post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) + require.NoError(t, err) + + poets := []activation.PoetClient{} + // Spawn certifier and 2 poets using it + pubKey, addr := spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) + certifierCfg := ®istration.CertifierConfig{ + URL: "http://" + addr.String(), + PubKey: pubKey, + } + + for i := 0; i < 2; i++ { + poet := spawnPoet(t, WithCertifier(certifierCfg)) + client, err := activation.NewHTTPPoetClient(poet.RestURL().String(), activation.DefaultPoetConfig()) + require.NoError(t, err) + poets = append(poets, client) + } + + // Spawn another certifier and 1 poet using it + pubKey, addr = spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) + certifierCfg = ®istration.CertifierConfig{ + URL: "http://" + addr.String(), + PubKey: pubKey, + } + + poet := spawnPoet(t, WithCertifier(certifierCfg)) + client, err := activation.NewHTTPPoetClient(poet.RestURL().String(), activation.DefaultPoetConfig()) + require.NoError(t, err) + poets = append(poets, client) + + // poet not using certifier + poet = spawnPoet(t) + client, err = activation.NewHTTPPoetClient(poet.RestURL().String(), activation.DefaultPoetConfig()) + require.NoError(t, err) + poets = append(poets, client) + + certifierClient := activation.NewCertifier(t.TempDir(), zaptest.NewLogger(t), post, info) + certs := certifierClient.CertifyAll(context.Background(), poets) + require.Len(t, certs, 3) + require.Contains(t, certs, poets[0].Address()) + require.Contains(t, certs, poets[1].Address()) + require.Contains(t, certs, poets[2].Address()) +} + +// A testCertifier for use in tests. +// Will verify any certificate valid POST proofs. +type testCertifier struct { + privKey ed25519.PrivateKey + postVerifier activation.PostVerifier + opts []verifying.OptionFunc +} + +func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { + var req activation.CertifyRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, fmt.Sprintf("decoding request: %v", err), http.StatusBadRequest) + return + } + + // Verify the POST proof. + proof := &shared.Proof{ + Nonce: req.Proof.Nonce, + Indices: req.Proof.Indices, + Pow: req.Proof.Pow, + } + metadata := &shared.ProofMetadata{ + NodeId: req.Metadata.NodeId, + CommitmentAtxId: req.Metadata.CommitmentAtxId, + Challenge: req.Metadata.Challenge, + NumUnits: req.Metadata.NumUnits, + LabelsPerUnit: req.Metadata.LabelsPerUnit, + } + if err := c.postVerifier.Verify(context.Background(), proof, metadata, c.opts...); err != nil { + http.Error(w, fmt.Sprintf("verifying POST: %v", err), http.StatusBadRequest) + return + } + + // Certify nodeID + resp := activation.CertifyResponse{ + Signature: ed25519.Sign(c.privKey, req.Metadata.NodeId), + PubKey: c.privKey.Public().(ed25519.PublicKey), + } + if err := json.NewEncoder(w).Encode(resp); err != nil { + http.Error(w, fmt.Sprintf("encoding response: %v", err), http.StatusInternalServerError) + return + } +} + +func spawnTestCertifier( + t *testing.T, + cfg activation.PostConfig, + opts ...verifying.OptionFunc, +) (ed25519.PublicKey, net.Addr) { + t.Helper() + + pub, private, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + + postVerifier, err := activation.NewPostVerifier( + cfg, + zaptest.NewLogger(t), + opts..., + ) + require.NoError(t, err) + var eg errgroup.Group + l, err := net.Listen("tcp", ":0") + require.NoError(t, err) + eg.Go(func() error { + certifier := &testCertifier{ + privKey: private, + postVerifier: postVerifier, + opts: opts, + } + + mux := http.NewServeMux() + mux.HandleFunc("/certify", certifier.certify) + http.Serve(l, mux) + return nil + }) + t.Cleanup(func() { + require.NoError(t, l.Close()) + require.NoError(t, eg.Wait()) + }) + + return pub, l.Addr() +} diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index eb780d32e4..3f4037700f 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -9,7 +9,10 @@ import ( "time" "github.com/spacemeshos/poet/logging" + "github.com/spacemeshos/poet/registration" "github.com/spacemeshos/post/initialization" + "github.com/spacemeshos/post/shared" + "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -143,12 +146,20 @@ func TestNIPostBuilderWithClients(t *testing.T) { RequestRetryDelay: epoch / 50, MaxRequestRetries: 10, } + + pubKey, addr := spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) + certifierCfg := ®istration.CertifierConfig{ + URL: "http://" + addr.String(), + PubKey: pubKey, + } + poetProver := spawnPoet( t, WithGenesis(time.Now()), WithEpochDuration(epoch), WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap), + WithCertifier(certifierCfg), ) mclock := activation.NewMocklayerClock(ctrl) @@ -177,6 +188,21 @@ func TestNIPostBuilderWithClients(t *testing.T) { return err == nil }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") + postClient, err := svc.Client(sig.NodeID()) + require.NoError(t, err) + post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) + require.NoError(t, err) + + client, err := activation.NewHTTPPoetClient( + poetProver.RestURL().String(), + poetCfg, + activation.WithLogger(logger), + ) + require.NoError(t, err) + + certifier := activation.NewCertifier(t.TempDir(), logger, post, info) + certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) + nb, err := activation.NewNIPostBuilder( poetDb, svc, @@ -193,7 +219,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { PublishEpoch: postGenesisEpoch + 2, } - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) v := activation.NewValidator(poetDb, cfg, opts.Scrypt, verifier) @@ -247,100 +273,11 @@ func TestNIPostBuilder_Close(t *testing.T) { challenge := types.NIPostChallenge{ PublishEpoch: postGenesisEpoch + 2, } - nipost, err := nb.BuildNIPost(ctx, &challenge) - require.ErrorIs(t, err, context.Canceled) - require.Nil(t, nipost) -} - -func TestNewNIPostBuilderNotInitialized(t *testing.T) { - ctrl := gomock.NewController(t) - - sig, err := signing.NewEdSigner() - require.NoError(t, err) - - logger := zaptest.NewLogger(t) - goldenATX := types.ATXID{2, 3, 4} - cfg := activation.DefaultPostConfig() - cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) - - mgr, err := activation.NewPostSetupManager(sig.NodeID(), cfg, logger, cdb, goldenATX) - require.NoError(t, err) - - epoch := layersPerEpoch * layerDuration - poetCfg := activation.PoetConfig{ - PhaseShift: epoch / 5, - CycleGap: epoch / 10, - GracePeriod: epoch / 10, - RequestTimeout: epoch / 10, - RequestRetryDelay: epoch / 100, - MaxRequestRetries: 10, - } - poetProver := spawnPoet( - t, - 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")) - - svc := grpcserver.NewPostService(logger) - grpcCfg, cleanup := launchServer(t, svc) - t.Cleanup(cleanup) - - nb, err := activation.NewNIPostBuilder( - poetDb, - svc, - []string{poetProver.RestURL().String()}, - t.TempDir(), - logger.Named("nipostBuilder"), - sig, - poetCfg, - mclock, - ) - require.NoError(t, err) - - opts := activation.DefaultPostSetupOpts() - opts.DataDir = t.TempDir() - opts.ProviderID.SetUint32(initialization.CPUProviderID()) - opts.Scrypt.N = 2 // Speedup initialization in tests. - t.Cleanup(launchPostSupervisor(t, logger, mgr, grpcCfg, opts)) - - require.Eventually(t, func() bool { - _, err := svc.Client(sig.NodeID()) - return err == nil - }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") - - challenge := types.NIPostChallenge{ - PublishEpoch: postGenesisEpoch + 2, - } - - nipost, err := nb.BuildNIPost(context.Background(), &challenge) - require.NoError(t, err) - require.NotNil(t, nipost) - verifier, err := activation.NewPostVerifier(cfg, logger.Named("verifier")) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, verifier.Close()) }) + certifier := activation.NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) - v := activation.NewValidator(poetDb, cfg, opts.Scrypt, verifier) - _, err = v.NIPost( - context.Background(), - sig.NodeID(), - goldenATX, - nipost, - challenge.Hash(), - opts.NumUnits, - ) - require.NoError(t, err) + nipost, err := nb.BuildNIPost(ctx, &challenge, certifier) + require.ErrorIs(t, err, context.Canceled) + require.Nil(t, nipost) } diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index d285f36485..3ec4776b28 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" + "github.com/spacemeshos/poet/registration" "github.com/spacemeshos/poet/server" - "github.com/spacemeshos/poet/shared" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" @@ -58,6 +58,12 @@ func WithCycleGap(gap time.Duration) HTTPPoetOpt { } } +func WithCertifier(certifier *registration.CertifierConfig) HTTPPoetOpt { + return func(cfg *server.Config) { + cfg.Registration.Certifier = certifier + } +} + // NewHTTPPoetTestHarness returns a new instance of HTTPPoetHarness. func NewHTTPPoetTestHarness(ctx context.Context, poetdir string, opts ...HTTPPoetOpt) (*HTTPPoetTestHarness, error) { cfg := server.DefaultConfig() @@ -107,22 +113,10 @@ func TestHTTPPoet(t *testing.T) { ) require.NoError(t, err) - resp, err := client.PowParams(context.Background()) - r.NoError(err) - signer, err := signing.NewEdSigner(signing.WithPrefix([]byte("prefix"))) require.NoError(t, err) ch := types.RandomHash() - nonce, err := shared.FindSubmitPowNonce( - context.Background(), - resp.Challenge, - ch.Bytes(), - signer.NodeID().Bytes(), - uint(resp.Difficulty), - ) - r.NoError(err) - signature := signer.Sign(signing.POET, ch.Bytes()) prefix := bytes.Join([][]byte{signer.Prefix(), {byte(signing.POET)}}, nil) @@ -133,10 +127,7 @@ func TestHTTPPoet(t *testing.T) { ch.Bytes(), signature, signer.NodeID(), - activation.PoetPoW{ - Nonce: nonce, - Params: *resp, - }, + activation.PoetAuth{}, ) r.NoError(err) r.NotNil(poetRound) @@ -168,22 +159,10 @@ func TestSubmitTooLate(t *testing.T) { ) require.NoError(t, err) - resp, err := client.PowParams(context.Background()) - r.NoError(err) - signer, err := signing.NewEdSigner(signing.WithPrefix([]byte("prefix"))) require.NoError(t, err) ch := types.RandomHash() - nonce, err := shared.FindSubmitPowNonce( - context.Background(), - resp.Challenge, - ch.Bytes(), - signer.NodeID().Bytes(), - uint(resp.Difficulty), - ) - r.NoError(err) - signature := signer.Sign(signing.POET, ch.Bytes()) prefix := bytes.Join([][]byte{signer.Prefix(), {byte(signing.POET)}}, nil) @@ -194,10 +173,72 @@ func TestSubmitTooLate(t *testing.T) { ch.Bytes(), signature, signer.NodeID(), - activation.PoetPoW{ - Nonce: nonce, - Params: *resp, - }, + activation.PoetAuth{}, ) r.ErrorIs(err, activation.ErrInvalidRequest) } + +func TestCertifierInfo(t *testing.T) { + t.Parallel() + r := require.New(t) + + var eg errgroup.Group + poetDir := t.TempDir() + t.Cleanup(func() { r.NoError(eg.Wait()) }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + c, err := NewHTTPPoetTestHarness(ctx, poetDir, WithCertifier(®istration.CertifierConfig{ + URL: "http://localhost:8080", + PubKey: []byte("pubkey"), + })) + r.NoError(err) + r.NotNil(c) + + eg.Go(func() error { + err := c.Service.Start(ctx) + return errors.Join(err, c.Service.Close()) + }) + + client, err := activation.NewHTTPPoetClient( + c.RestURL().String(), + activation.DefaultPoetConfig(), + activation.WithLogger(zaptest.NewLogger(t)), + ) + require.NoError(t, err) + + info, err := client.CertifierInfo(context.Background()) + r.NoError(err) + r.Equal("http://localhost:8080", info.URL.String()) + r.Equal([]byte("pubkey"), info.PubKey) +} + +func TestNoCertifierInfo(t *testing.T) { + t.Parallel() + r := require.New(t) + + var eg errgroup.Group + poetDir := t.TempDir() + t.Cleanup(func() { r.NoError(eg.Wait()) }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + c, err := NewHTTPPoetTestHarness(ctx, poetDir) + r.NoError(err) + r.NotNil(c) + + eg.Go(func() error { + err := c.Service.Start(ctx) + return errors.Join(err, c.Service.Close()) + }) + + client, err := activation.NewHTTPPoetClient( + c.RestURL().String(), + activation.DefaultPoetConfig(), + activation.WithLogger(zaptest.NewLogger(t)), + ) + require.NoError(t, err) + + _, err = client.CertifierInfo(context.Background()) + r.ErrorContains(err, "poet doesn't support certifier") +} diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index 7e4b505a2c..e93a81735b 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/spacemeshos/post/initialization" + "github.com/spacemeshos/post/shared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -84,6 +85,11 @@ func TestValidator_Validate(t *testing.T) { return err == nil }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") + postClient, err := svc.Client(sig.NodeID()) + require.NoError(t, err) + post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) + require.NoError(t, err) + nb, err := activation.NewNIPostBuilder( poetDb, svc, @@ -101,7 +107,8 @@ func TestValidator_Validate(t *testing.T) { } challengeHash := challenge.Hash() - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := activation.NewCertifier(t.TempDir(), logger, post, info) + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) v := activation.NewValidator(poetDb, cfg, opts.Scrypt, verifier) diff --git a/activation/interface.go b/activation/interface.go index 9e4792cacd..1ee502a457 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -3,6 +3,7 @@ package activation import ( "context" "io" + "net/url" "time" "github.com/spacemeshos/post/shared" @@ -61,7 +62,7 @@ type layerClock interface { } type nipostBuilder interface { - BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) + BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge, certifier certifierService) (*types.NIPost, error) DataDir() string } @@ -93,13 +94,24 @@ type SmeshingProvider interface { SetCoinbase(coinbase types.Address) } -// poetClient servers as an interface to communicate with a PoET server. +type PoetCert struct { + Signature []byte +} + +type PoetAuth struct { + *PoetPoW + *PoetCert +} + +// PoetClient servers as an interface to communicate with a PoET server. // It is used to submit challenges and fetch proofs. -type poetClient interface { +type PoetClient interface { Address() string + // FIXME: remove support for deprecated poet PoW PowParams(ctx context.Context) (*PoetPowParams, error) + // FIXME: remove support for deprecated poet PoW // Submit registers a challenge in the proving service current open round. Submit( ctx context.Context, @@ -107,16 +119,37 @@ type poetClient interface { prefix, challenge []byte, signature types.EdSignature, nodeID types.NodeID, - pow PoetPoW, + auth PoetAuth, ) (*types.PoetRound, error) // PoetServiceID returns the public key of the PoET proving service. PoetServiceID(context.Context) (types.PoetServiceID, error) + CertifierInfo(context.Context) (*CertifierInfo, error) + // Proof returns the proof for the given round ID. Proof(ctx context.Context, roundID string) (*types.PoetProofMessage, []types.Member, error) } +type CertifierInfo struct { + URL *url.URL + PubKey []byte +} + +// certifierService is used to certify nodeID for registerting in the poet. +type certifierService interface { + // Acquire a certificate for the given poet. + GetCertificate(poet string) *PoetCert + // Recertify the nodeID and return a certificate confirming that + // it is verified. The certificate can be later used to submit in poet. + Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) + + // Certify the nodeID for all given poets. + // It won't recertify poets that already have a certificate. + // It returns a map of a poet address to a certificate for it. + CertifyAll(ctx context.Context, poets []PoetClient) map[string]PoetCert +} + type poetDbAPI interface { GetProof(types.PoetProofRef) (*types.PoetProof, *types.Hash32, error) ValidateAndStore(ctx context.Context, proofMessage *types.PoetProofMessage) error diff --git a/activation/mocks.go b/activation/mocks.go index c444376987..d3fdcdff01 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -671,18 +671,18 @@ func (m *MocknipostBuilder) EXPECT() *MocknipostBuilderMockRecorder { } // BuildNIPost mocks base method. -func (m *MocknipostBuilder) BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { +func (m *MocknipostBuilder) BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge, certifier certifierService) (*types.NIPost, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildNIPost", ctx, challenge) + ret := m.ctrl.Call(m, "BuildNIPost", ctx, challenge, certifier) ret0, _ := ret[0].(*types.NIPost) ret1, _ := ret[1].(error) return ret0, ret1 } // BuildNIPost indicates an expected call of BuildNIPost. -func (mr *MocknipostBuilderMockRecorder) BuildNIPost(ctx, challenge any) *nipostBuilderBuildNIPostCall { +func (mr *MocknipostBuilderMockRecorder) BuildNIPost(ctx, challenge, certifier any) *nipostBuilderBuildNIPostCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildNIPost", reflect.TypeOf((*MocknipostBuilder)(nil).BuildNIPost), ctx, challenge) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildNIPost", reflect.TypeOf((*MocknipostBuilder)(nil).BuildNIPost), ctx, challenge, certifier) return &nipostBuilderBuildNIPostCall{Call: call} } @@ -698,13 +698,13 @@ func (c *nipostBuilderBuildNIPostCall) Return(arg0 *types.NIPost, arg1 error) *n } // Do rewrite *gomock.Call.Do -func (c *nipostBuilderBuildNIPostCall) Do(f func(context.Context, *types.NIPostChallenge) (*types.NIPost, error)) *nipostBuilderBuildNIPostCall { +func (c *nipostBuilderBuildNIPostCall) Do(f func(context.Context, *types.NIPostChallenge, certifierService) (*types.NIPost, error)) *nipostBuilderBuildNIPostCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *nipostBuilderBuildNIPostCall) DoAndReturn(f func(context.Context, *types.NIPostChallenge) (*types.NIPost, error)) *nipostBuilderBuildNIPostCall { +func (c *nipostBuilderBuildNIPostCall) DoAndReturn(f func(context.Context, *types.NIPostChallenge, certifierService) (*types.NIPost, error)) *nipostBuilderBuildNIPostCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1294,31 +1294,31 @@ func (c *SmeshingProviderStopSmeshingCall) DoAndReturn(f func(bool) error) *Smes return c } -// MockpoetClient is a mock of poetClient interface. -type MockpoetClient struct { +// MockPoetClient is a mock of PoetClient interface. +type MockPoetClient struct { ctrl *gomock.Controller - recorder *MockpoetClientMockRecorder + recorder *MockPoetClientMockRecorder } -// MockpoetClientMockRecorder is the mock recorder for MockpoetClient. -type MockpoetClientMockRecorder struct { - mock *MockpoetClient +// MockPoetClientMockRecorder is the mock recorder for MockPoetClient. +type MockPoetClientMockRecorder struct { + mock *MockPoetClient } -// NewMockpoetClient creates a new mock instance. -func NewMockpoetClient(ctrl *gomock.Controller) *MockpoetClient { - mock := &MockpoetClient{ctrl: ctrl} - mock.recorder = &MockpoetClientMockRecorder{mock} +// NewMockPoetClient creates a new mock instance. +func NewMockPoetClient(ctrl *gomock.Controller) *MockPoetClient { + mock := &MockPoetClient{ctrl: ctrl} + mock.recorder = &MockPoetClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockpoetClient) EXPECT() *MockpoetClientMockRecorder { +func (m *MockPoetClient) EXPECT() *MockPoetClientMockRecorder { return m.recorder } // Address mocks base method. -func (m *MockpoetClient) Address() string { +func (m *MockPoetClient) Address() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Address") ret0, _ := ret[0].(string) @@ -1326,37 +1326,76 @@ func (m *MockpoetClient) Address() string { } // Address indicates an expected call of Address. -func (mr *MockpoetClientMockRecorder) Address() *poetClientAddressCall { +func (mr *MockPoetClientMockRecorder) Address() *PoetClientAddressCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockpoetClient)(nil).Address)) - return &poetClientAddressCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockPoetClient)(nil).Address)) + return &PoetClientAddressCall{Call: call} } -// poetClientAddressCall wrap *gomock.Call -type poetClientAddressCall struct { +// PoetClientAddressCall wrap *gomock.Call +type PoetClientAddressCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *poetClientAddressCall) Return(arg0 string) *poetClientAddressCall { +func (c *PoetClientAddressCall) Return(arg0 string) *PoetClientAddressCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *poetClientAddressCall) Do(f func() string) *poetClientAddressCall { +func (c *PoetClientAddressCall) Do(f func() string) *PoetClientAddressCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *poetClientAddressCall) DoAndReturn(f func() string) *poetClientAddressCall { +func (c *PoetClientAddressCall) DoAndReturn(f func() string) *PoetClientAddressCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CertifierInfo mocks base method. +func (m *MockPoetClient) CertifierInfo(arg0 context.Context) (*CertifierInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CertifierInfo", arg0) + ret0, _ := ret[0].(*CertifierInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CertifierInfo indicates an expected call of CertifierInfo. +func (mr *MockPoetClientMockRecorder) CertifierInfo(arg0 any) *PoetClientCertifierInfoCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifierInfo", reflect.TypeOf((*MockPoetClient)(nil).CertifierInfo), arg0) + return &PoetClientCertifierInfoCall{Call: call} +} + +// PoetClientCertifierInfoCall wrap *gomock.Call +type PoetClientCertifierInfoCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *PoetClientCertifierInfoCall) Return(arg0 *CertifierInfo, arg1 error) *PoetClientCertifierInfoCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *PoetClientCertifierInfoCall) Do(f func(context.Context) (*CertifierInfo, error)) *PoetClientCertifierInfoCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *PoetClientCertifierInfoCall) DoAndReturn(f func(context.Context) (*CertifierInfo, error)) *PoetClientCertifierInfoCall { c.Call = c.Call.DoAndReturn(f) return c } // PoetServiceID mocks base method. -func (m *MockpoetClient) PoetServiceID(arg0 context.Context) (types.PoetServiceID, error) { +func (m *MockPoetClient) PoetServiceID(arg0 context.Context) (types.PoetServiceID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PoetServiceID", arg0) ret0, _ := ret[0].(types.PoetServiceID) @@ -1365,37 +1404,37 @@ func (m *MockpoetClient) PoetServiceID(arg0 context.Context) (types.PoetServiceI } // PoetServiceID indicates an expected call of PoetServiceID. -func (mr *MockpoetClientMockRecorder) PoetServiceID(arg0 any) *poetClientPoetServiceIDCall { +func (mr *MockPoetClientMockRecorder) PoetServiceID(arg0 any) *PoetClientPoetServiceIDCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PoetServiceID", reflect.TypeOf((*MockpoetClient)(nil).PoetServiceID), arg0) - return &poetClientPoetServiceIDCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PoetServiceID", reflect.TypeOf((*MockPoetClient)(nil).PoetServiceID), arg0) + return &PoetClientPoetServiceIDCall{Call: call} } -// poetClientPoetServiceIDCall wrap *gomock.Call -type poetClientPoetServiceIDCall struct { +// PoetClientPoetServiceIDCall wrap *gomock.Call +type PoetClientPoetServiceIDCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *poetClientPoetServiceIDCall) Return(arg0 types.PoetServiceID, arg1 error) *poetClientPoetServiceIDCall { +func (c *PoetClientPoetServiceIDCall) Return(arg0 types.PoetServiceID, arg1 error) *PoetClientPoetServiceIDCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *poetClientPoetServiceIDCall) Do(f func(context.Context) (types.PoetServiceID, error)) *poetClientPoetServiceIDCall { +func (c *PoetClientPoetServiceIDCall) Do(f func(context.Context) (types.PoetServiceID, error)) *PoetClientPoetServiceIDCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *poetClientPoetServiceIDCall) DoAndReturn(f func(context.Context) (types.PoetServiceID, error)) *poetClientPoetServiceIDCall { +func (c *PoetClientPoetServiceIDCall) DoAndReturn(f func(context.Context) (types.PoetServiceID, error)) *PoetClientPoetServiceIDCall { c.Call = c.Call.DoAndReturn(f) return c } // PowParams mocks base method. -func (m *MockpoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) { +func (m *MockPoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PowParams", ctx) ret0, _ := ret[0].(*PoetPowParams) @@ -1404,37 +1443,37 @@ func (m *MockpoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) } // PowParams indicates an expected call of PowParams. -func (mr *MockpoetClientMockRecorder) PowParams(ctx any) *poetClientPowParamsCall { +func (mr *MockPoetClientMockRecorder) PowParams(ctx any) *PoetClientPowParamsCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowParams", reflect.TypeOf((*MockpoetClient)(nil).PowParams), ctx) - return &poetClientPowParamsCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowParams", reflect.TypeOf((*MockPoetClient)(nil).PowParams), ctx) + return &PoetClientPowParamsCall{Call: call} } -// poetClientPowParamsCall wrap *gomock.Call -type poetClientPowParamsCall struct { +// PoetClientPowParamsCall wrap *gomock.Call +type PoetClientPowParamsCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *poetClientPowParamsCall) Return(arg0 *PoetPowParams, arg1 error) *poetClientPowParamsCall { +func (c *PoetClientPowParamsCall) Return(arg0 *PoetPowParams, arg1 error) *PoetClientPowParamsCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *poetClientPowParamsCall) Do(f func(context.Context) (*PoetPowParams, error)) *poetClientPowParamsCall { +func (c *PoetClientPowParamsCall) Do(f func(context.Context) (*PoetPowParams, error)) *PoetClientPowParamsCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *poetClientPowParamsCall) DoAndReturn(f func(context.Context) (*PoetPowParams, error)) *poetClientPowParamsCall { +func (c *PoetClientPowParamsCall) DoAndReturn(f func(context.Context) (*PoetPowParams, error)) *PoetClientPowParamsCall { c.Call = c.Call.DoAndReturn(f) return c } // Proof mocks base method. -func (m *MockpoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProofMessage, []types.Member, error) { +func (m *MockPoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProofMessage, []types.Member, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Proof", ctx, roundID) ret0, _ := ret[0].(*types.PoetProofMessage) @@ -1444,70 +1483,208 @@ func (m *MockpoetClient) Proof(ctx context.Context, roundID string) (*types.Poet } // Proof indicates an expected call of Proof. -func (mr *MockpoetClientMockRecorder) Proof(ctx, roundID any) *poetClientProofCall { +func (mr *MockPoetClientMockRecorder) Proof(ctx, roundID any) *PoetClientProofCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Proof", reflect.TypeOf((*MockpoetClient)(nil).Proof), ctx, roundID) - return &poetClientProofCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Proof", reflect.TypeOf((*MockPoetClient)(nil).Proof), ctx, roundID) + return &PoetClientProofCall{Call: call} } -// poetClientProofCall wrap *gomock.Call -type poetClientProofCall struct { +// PoetClientProofCall wrap *gomock.Call +type PoetClientProofCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *poetClientProofCall) Return(arg0 *types.PoetProofMessage, arg1 []types.Member, arg2 error) *poetClientProofCall { +func (c *PoetClientProofCall) Return(arg0 *types.PoetProofMessage, arg1 []types.Member, arg2 error) *PoetClientProofCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *poetClientProofCall) Do(f func(context.Context, string) (*types.PoetProofMessage, []types.Member, error)) *poetClientProofCall { +func (c *PoetClientProofCall) Do(f func(context.Context, string) (*types.PoetProofMessage, []types.Member, error)) *PoetClientProofCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *poetClientProofCall) DoAndReturn(f func(context.Context, string) (*types.PoetProofMessage, []types.Member, error)) *poetClientProofCall { +func (c *PoetClientProofCall) DoAndReturn(f func(context.Context, string) (*types.PoetProofMessage, []types.Member, error)) *PoetClientProofCall { c.Call = c.Call.DoAndReturn(f) return c } // Submit mocks base method. -func (m *MockpoetClient) Submit(ctx context.Context, deadline time.Time, prefix, challenge []byte, signature types.EdSignature, nodeID types.NodeID, pow PoetPoW) (*types.PoetRound, error) { +func (m *MockPoetClient) Submit(ctx context.Context, deadline time.Time, prefix, challenge []byte, signature types.EdSignature, nodeID types.NodeID, auth PoetAuth) (*types.PoetRound, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Submit", ctx, deadline, prefix, challenge, signature, nodeID, pow) + ret := m.ctrl.Call(m, "Submit", ctx, deadline, prefix, challenge, signature, nodeID, auth) ret0, _ := ret[0].(*types.PoetRound) ret1, _ := ret[1].(error) return ret0, ret1 } // Submit indicates an expected call of Submit. -func (mr *MockpoetClientMockRecorder) Submit(ctx, deadline, prefix, challenge, signature, nodeID, pow any) *poetClientSubmitCall { +func (mr *MockPoetClientMockRecorder) Submit(ctx, deadline, prefix, challenge, signature, nodeID, auth any) *PoetClientSubmitCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockPoetClient)(nil).Submit), ctx, deadline, prefix, challenge, signature, nodeID, auth) + return &PoetClientSubmitCall{Call: call} +} + +// PoetClientSubmitCall wrap *gomock.Call +type PoetClientSubmitCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *PoetClientSubmitCall) Return(arg0 *types.PoetRound, arg1 error) *PoetClientSubmitCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *PoetClientSubmitCall) Do(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID, PoetAuth) (*types.PoetRound, error)) *PoetClientSubmitCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *PoetClientSubmitCall) DoAndReturn(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID, PoetAuth) (*types.PoetRound, error)) *PoetClientSubmitCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockcertifierService is a mock of certifierService interface. +type MockcertifierService struct { + ctrl *gomock.Controller + recorder *MockcertifierServiceMockRecorder +} + +// MockcertifierServiceMockRecorder is the mock recorder for MockcertifierService. +type MockcertifierServiceMockRecorder struct { + mock *MockcertifierService +} + +// NewMockcertifierService creates a new mock instance. +func NewMockcertifierService(ctrl *gomock.Controller) *MockcertifierService { + mock := &MockcertifierService{ctrl: ctrl} + mock.recorder = &MockcertifierServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockcertifierService) EXPECT() *MockcertifierServiceMockRecorder { + return m.recorder +} + +// CertifyAll mocks base method. +func (m *MockcertifierService) CertifyAll(ctx context.Context, poets []PoetClient) map[string]PoetCert { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CertifyAll", ctx, poets) + ret0, _ := ret[0].(map[string]PoetCert) + return ret0 +} + +// CertifyAll indicates an expected call of CertifyAll. +func (mr *MockcertifierServiceMockRecorder) CertifyAll(ctx, poets any) *certifierServiceCertifyAllCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifyAll", reflect.TypeOf((*MockcertifierService)(nil).CertifyAll), ctx, poets) + return &certifierServiceCertifyAllCall{Call: call} +} + +// certifierServiceCertifyAllCall wrap *gomock.Call +type certifierServiceCertifyAllCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *certifierServiceCertifyAllCall) Return(arg0 map[string]PoetCert) *certifierServiceCertifyAllCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *certifierServiceCertifyAllCall) Do(f func(context.Context, []PoetClient) map[string]PoetCert) *certifierServiceCertifyAllCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *certifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []PoetClient) map[string]PoetCert) *certifierServiceCertifyAllCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetCertificate mocks base method. +func (m *MockcertifierService) GetCertificate(poet string) *PoetCert { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCertificate", poet) + ret0, _ := ret[0].(*PoetCert) + return ret0 +} + +// GetCertificate indicates an expected call of GetCertificate. +func (mr *MockcertifierServiceMockRecorder) GetCertificate(poet any) *certifierServiceGetCertificateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificate", reflect.TypeOf((*MockcertifierService)(nil).GetCertificate), poet) + return &certifierServiceGetCertificateCall{Call: call} +} + +// certifierServiceGetCertificateCall wrap *gomock.Call +type certifierServiceGetCertificateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *certifierServiceGetCertificateCall) Return(arg0 *PoetCert) *certifierServiceGetCertificateCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *certifierServiceGetCertificateCall) Do(f func(string) *PoetCert) *certifierServiceGetCertificateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *certifierServiceGetCertificateCall) DoAndReturn(f func(string) *PoetCert) *certifierServiceGetCertificateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Recertify mocks base method. +func (m *MockcertifierService) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Recertify", ctx, poet) + ret0, _ := ret[0].(*PoetCert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Recertify indicates an expected call of Recertify. +func (mr *MockcertifierServiceMockRecorder) Recertify(ctx, poet any) *certifierServiceRecertifyCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockpoetClient)(nil).Submit), ctx, deadline, prefix, challenge, signature, nodeID, pow) - return &poetClientSubmitCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recertify", reflect.TypeOf((*MockcertifierService)(nil).Recertify), ctx, poet) + return &certifierServiceRecertifyCall{Call: call} } -// poetClientSubmitCall wrap *gomock.Call -type poetClientSubmitCall struct { +// certifierServiceRecertifyCall wrap *gomock.Call +type certifierServiceRecertifyCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *poetClientSubmitCall) Return(arg0 *types.PoetRound, arg1 error) *poetClientSubmitCall { +func (c *certifierServiceRecertifyCall) Return(arg0 *PoetCert, arg1 error) *certifierServiceRecertifyCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *poetClientSubmitCall) Do(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID, PoetPoW) (*types.PoetRound, error)) *poetClientSubmitCall { +func (c *certifierServiceRecertifyCall) Do(f func(context.Context, PoetClient) (*PoetCert, error)) *certifierServiceRecertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *poetClientSubmitCall) DoAndReturn(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID, PoetPoW) (*types.PoetRound, error)) *poetClientSubmitCall { +func (c *certifierServiceRecertifyCall) DoAndReturn(f func(context.Context, PoetClient) (*PoetCert, error)) *certifierServiceRecertifyCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index 505d533676..ffba879e04 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -64,7 +64,7 @@ func (nb *NIPostBuilder) persistState() { // NIPostBuilder holds the required state and dependencies to create Non-Interactive Proofs of Space-Time (NIPost). type NIPostBuilder struct { dataDir string - poetProvers map[string]poetClient + poetProvers map[string]PoetClient poetDB poetDbAPI postService postService state *types.NIPostBuilderState @@ -77,9 +77,9 @@ type NIPostBuilder struct { type NIPostBuilderOption func(*NIPostBuilder) // withPoetClients allows to pass in clients directly (for testing purposes). -func withPoetClients(clients []poetClient) NIPostBuilderOption { +func withPoetClients(clients []PoetClient) NIPostBuilderOption { return func(nb *NIPostBuilder) { - nb.poetProvers = make(map[string]poetClient, len(clients)) + nb.poetProvers = make(map[string]PoetClient, len(clients)) for _, client := range clients { nb.poetProvers[client.Address()] = client } @@ -98,7 +98,7 @@ func NewNIPostBuilder( layerClock layerClock, opts ...NIPostBuilderOption, ) (*NIPostBuilder, error) { - poetClients := make(map[string]poetClient, len(poetServers)) + poetClients := make(map[string]PoetClient, len(poetServers)) for _, address := range poetServers { client, err := NewHTTPPoetClient(address, poetCfg, WithLogger(lg.Named("poet"))) if err != nil { @@ -139,7 +139,7 @@ func (nb *NIPostBuilder) proof(ctx context.Context, challenge []byte) (*types.Po } // UpdatePoETProvers updates poetProver reference. It should not be executed concurrently with BuildNIPoST. -func (nb *NIPostBuilder) UpdatePoETProvers(poetProvers []poetClient) { +func (nb *NIPostBuilder) UpdatePoETProvers(poetProvers []PoetClient) { // TODO(mafa): this seems incorrect - this makes it impossible for the node to fetch a submitted challenge // thereby skipping an epoch they could have published an ATX for @@ -147,7 +147,7 @@ func (nb *NIPostBuilder) UpdatePoETProvers(poetProvers []poetClient) { nb.state = &types.NIPostBuilderState{ NIPost: &types.NIPost{}, } - nb.poetProvers = make(map[string]poetClient, len(poetProvers)) + nb.poetProvers = make(map[string]PoetClient, len(poetProvers)) for _, poetProver := range poetProvers { nb.poetProvers[poetProver.Address()] = poetProver } @@ -157,7 +157,7 @@ func (nb *NIPostBuilder) UpdatePoETProvers(poetProvers []poetClient) { // BuildNIPost uses the given challenge to build a NIPost. // The process can take considerable time, because it includes waiting for the poet service to // publish a proof - a process that takes about an epoch. -func (nb *NIPostBuilder) BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { +func (nb *NIPostBuilder) BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge, certifier certifierService) (*types.NIPost, error) { logger := nb.log.With(log.ZContext(ctx)) // Note: to avoid missing next PoET round, we need to publish the ATX before the next PoET round starts. // We can still publish an ATX late (i.e. within publish epoch) and receive rewards, but we will miss one @@ -212,7 +212,8 @@ func (nb *NIPostBuilder) BuildNIPost(ctx context.Context, challenge *types.NIPos submitCtx, cancel := context.WithDeadline(ctx, poetRoundStart) defer cancel() - poetRequests, err := nb.submitPoetChallenges(submitCtx, poetProofDeadline, challengeHash.Bytes()) + + poetRequests, err := nb.submitPoetChallenges(submitCtx, poetProofDeadline, challengeHash.Bytes(), certifier) if err != nil { return nil, fmt.Errorf("submitting to poets: %w", err) } @@ -310,9 +311,10 @@ func withConditionalTimeout(ctx context.Context, timeout time.Duration) (context func (nb *NIPostBuilder) submitPoetChallenge( ctx context.Context, deadline time.Time, - client poetClient, + client PoetClient, prefix, challenge []byte, signature types.EdSignature, + certifier certifierService, ) (*types.PoetRequest, error) { poetServiceID, err := client.PoetServiceID(ctx) if err != nil { @@ -320,40 +322,60 @@ func (nb *NIPostBuilder) submitPoetChallenge( } logger := nb.log.With(log.ZContext(ctx), zap.String("poet", client.Address())) - logger.Debug("querying for poet pow parameters") - powCtx, cancel := withConditionalTimeout(ctx, nb.poetCfg.RequestTimeout) - defer cancel() - powParams, err := client.PowParams(powCtx) - if err != nil { - return nil, &PoetSvcUnstableError{msg: "failed to get PoW params", source: err} + // FIXME: remove support for deprecated poet PoW + auth := PoetAuth{ + PoetCert: certifier.GetCertificate(client.Address()), } - - logger.Debug("doing pow with params", zap.Any("pow_params", powParams)) - startTime := time.Now() - nonce, err := shared.FindSubmitPowNonce( - ctx, - powParams.Challenge, - challenge, - nb.signer.NodeID().Bytes(), - powParams.Difficulty, - ) - metrics.PoetPowDuration.Set(float64(time.Since(startTime).Nanoseconds())) - if err != nil { - return nil, fmt.Errorf("running poet PoW: %w", err) + if auth.PoetCert == nil || auth.PoetCert.Signature == nil { + logger.Info("missing poet cert - falling back to PoW") + powCtx, cancel := withConditionalTimeout(ctx, nb.poetCfg.RequestTimeout) + defer cancel() + powParams, err := client.PowParams(powCtx) + if err != nil { + return nil, &PoetSvcUnstableError{msg: "failed to get PoW params", source: err} + } + logger.Debug("doing pow with params", zap.Any("pow_params", powParams)) + startTime := time.Now() + nonce, err := shared.FindSubmitPowNonce( + ctx, + powParams.Challenge, + challenge, + nb.signer.NodeID().Bytes(), + powParams.Difficulty, + ) + metrics.PoetPowDuration.Set(float64(time.Since(startTime).Nanoseconds())) + if err != nil { + return nil, fmt.Errorf("running poet PoW: %w", err) + } + auth.PoetPoW = &PoetPoW{ + Nonce: nonce, + Params: *powParams, + } + } else { + logger.Info("registering with a certificate", zap.Binary("cert", auth.PoetCert.Signature)) } logger.Debug("submitting challenge to poet proving service") submitCtx, cancel := withConditionalTimeout(ctx, nb.poetCfg.RequestTimeout) defer cancel() - round, err := client.Submit(submitCtx, deadline, prefix, challenge, signature, nb.signer.NodeID(), PoetPoW{ - Nonce: nonce, - Params: *powParams, - }) - if err != nil { - return nil, &PoetSvcUnstableError{msg: "failed to submit challenge to poet service", source: err} - } + var round *types.PoetRound + for try := 0; try < 2; try++ { + round, err = client.Submit(submitCtx, deadline, prefix, challenge, signature, nb.signer.NodeID(), auth) + if err == nil { + break + } + if errors.Is(err, ErrUnathorized) && try == 0 { + logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) + auth.PoetCert, err = certifier.Recertify(ctx, client) + if err != nil { + return nil, &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} + } + } else { + return nil, &PoetSvcUnstableError{msg: "failed to submit challenge to poet service", source: err} + } + } logger.Info("challenge submitted to poet proving service", zap.String("round", round.ID)) return &types.PoetRequest{ @@ -367,6 +389,7 @@ func (nb *NIPostBuilder) submitPoetChallenges( ctx context.Context, deadline time.Time, challenge []byte, + certifier certifierService, ) ([]types.PoetRequest, error) { signature := nb.signer.Sign(signing.POET, challenge) prefix := bytes.Join([][]byte{nb.signer.Prefix(), {byte(signing.POET)}}, nil) @@ -377,10 +400,10 @@ func (nb *NIPostBuilder) submitPoetChallenges( err error } poetRequestsChannel := make(chan submitResult, len(nb.poetProvers)) - for _, poetProver := range nb.poetProvers { - poet := poetProver + for _, poet := range nb.poetProvers { + poet := poet g.Go(func() error { - poetRequest, err := nb.submitPoetChallenge(ctx, deadline, poet, prefix, challenge, signature) + poetRequest, err := nb.submitPoetChallenge(ctx, deadline, poet, prefix, challenge, signature, certifier) poetRequestsChannel <- submitResult{ request: poetRequest, err: err, @@ -412,7 +435,7 @@ func (nb *NIPostBuilder) submitPoetChallenges( return poetRequests, nil } -func (nb *NIPostBuilder) getPoetClient(ctx context.Context, id types.PoetServiceID) poetClient { +func (nb *NIPostBuilder) getPoetClient(ctx context.Context, id types.PoetServiceID) PoetClient { for _, client := range nb.poetProvers { if clientId, err := client.PoetServiceID(ctx); err == nil && bytes.Equal(id.ServiceID, clientId.ServiceID) { return client diff --git a/activation/nipost_test.go b/activation/nipost_test.go index e20a26809b..f0dc67427a 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -17,8 +17,8 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" ) -func defaultPoetServiceMock(ctrl *gomock.Controller, id []byte, address string) *MockpoetClient { - poetClient := NewMockpoetClient(ctrl) +func defaultPoetServiceMock(ctrl *gomock.Controller, id []byte, address string) *MockPoetClient { + poetClient := NewMockPoetClient(ctrl) poetClient.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). AnyTimes(). @@ -76,11 +76,14 @@ func TestNIPostBuilderWithMocks(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProvider}), + withPoetClients([]PoetClient{poetProvider}), ) require.NoError(t, err) - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProvider}).Return(nil) + + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) require.NotNil(t, nipost) } @@ -115,7 +118,7 @@ func TestPostSetup(t *testing.T) { postProvider.signer, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProvider}), + withPoetClients([]PoetClient{poetProvider}), ) require.NoError(t, err) @@ -123,7 +126,10 @@ func TestPostSetup(t *testing.T) { require.NoError(t, postProvider.StartSession(context.Background())) t.Cleanup(func() { assert.NoError(t, postProvider.Reset()) }) - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProvider}).Return(nil) + + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) require.NotNil(t, nipost) } @@ -177,11 +183,13 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -198,14 +206,15 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("error")).Times(1) // check that proof ref is not called again - nipost, err = nb.BuildNIPost(context.Background(), &challenge2) + certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) + nipost, err = nb.BuildNIPost(context.Background(), &challenge2, certifier) require.Nil(t, nipost) require.Error(t, err) @@ -221,20 +230,21 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{}, &types.PostInfo{}, nil).Times(1) // check that proof ref is not called again - nipost, err = nb.BuildNIPost(context.Background(), &challenge2) + nipost, err = nb.BuildNIPost(context.Background(), &challenge2, certifier) require.NoError(t, err) require.NotNil(t, nipost) // test state not loading if other challenge provided poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{}, &types.PostInfo{}, nil).Times(1) - nipost, err = nb.BuildNIPost(context.Background(), &challenge3) + certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) + nipost, err = nb.BuildNIPost(context.Background(), &challenge3, certifier) require.NoError(t, err) require.NotNil(t, nipost) } @@ -253,9 +263,9 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) mclock := defaultLayerClockMock(ctrl) - poets := make([]poetClient, 0, 2) + poets := make([]PoetClient, 0, 2) { - poet := NewMockpoetClient(ctrl) + poet := NewMockPoetClient(ctrl) poet.EXPECT(). PoetServiceID(gomock.Any()). AnyTimes(). @@ -268,7 +278,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. _, _ []byte, _ types.EdSignature, _ types.NodeID, - _ PoetPoW, + _ PoetAuth, ) (*types.PoetRound, error) { <-ctx.Done() return nil, ctx.Err() @@ -278,7 +288,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. poets = append(poets, poet) } { - poet := NewMockpoetClient(ctrl) + poet := NewMockPoetClient(ctrl) poet.EXPECT(). PoetServiceID(gomock.Any()). AnyTimes(). @@ -318,8 +328,11 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. ) require.NoError(t, err) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), poets).Return(nil) + // Act - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) // Verify @@ -351,7 +364,7 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Times(2).Return(nil) mclock := defaultLayerClockMock(ctrl) - poets := make([]poetClient, 0, 2) + poets := make([]PoetClient, 0, 2) { poet := defaultPoetServiceMock(ctrl, []byte("poet0"), "http://localhost:9999") poet.EXPECT().Proof(gomock.Any(), "").Return(proofWorse, []types.Member{types.Member(challenge.Hash())}, nil) @@ -384,8 +397,11 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { ) require.NoError(t, err) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), poets).Return(nil) + // Act - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) // Verify @@ -410,7 +426,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) - poetProver := NewMockpoetClient(ctrl) + poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{}, errors.New("test")) poetProver.EXPECT().Address().Return("http://localhost:9999") postService := NewMockpostService(ctrl) @@ -424,10 +440,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), &challenge) + + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + + nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) require.Nil(t, nipst) }) @@ -436,7 +456,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) - poetProver := NewMockpoetClient(ctrl) + poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte{}}, nil) poetProver.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -454,11 +474,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + + nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) require.Nil(t, nipst) }) @@ -467,7 +490,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) - poetProver := NewMockpoetClient(ctrl) + poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte{}}, nil) poetProver.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -477,7 +500,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { _, _ []byte, _ types.EdSignature, _ types.NodeID, - _ PoetPoW, + _ PoetAuth, ) (*types.PoetRound, error) { <-ctx.Done() return nil, ctx.Err() @@ -495,10 +518,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), &challenge) + + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + + nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) require.Nil(t, nipst) }) @@ -520,10 +547,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), &challenge) + + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + + nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetProofNotReceived) require.Nil(t, nipst) }) @@ -548,10 +579,14 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) - nipst, err := nb.BuildNIPost(context.Background(), &challenge) + + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + + nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetProofNotReceived) require.Nil(t, nipst) }) @@ -573,7 +608,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) - poetProver := NewMockpoetClient(ctrl) + poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT().Address().Return("http://localhost:9999") mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { @@ -591,12 +626,14 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) + certifier := NewMockcertifierService(ctrl) + challenge := types.NIPostChallenge{PublishEpoch: currLayer.GetEpoch()} - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrATXChallengeExpired) require.ErrorContains(t, err, "poet round has already started") require.Nil(t, nipost) @@ -605,7 +642,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) - poetProver := NewMockpoetClient(ctrl) + poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT().Address().Return("http://localhost:9999") mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { @@ -623,7 +660,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) challenge := types.NIPostChallenge{PublishEpoch: currLayer.GetEpoch() - 1} @@ -633,7 +670,9 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { } require.NoError(t, saveBuilderState(dir, &state)) - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrATXChallengeExpired) require.ErrorContains(t, err, "poet proof for pub epoch") require.Nil(t, nipost) @@ -642,7 +681,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) - poetProver := NewMockpoetClient(ctrl) + poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT().Address().Return("http://localhost:9999") mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { @@ -660,7 +699,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, - withPoetClients([]poetClient{poetProver}), + withPoetClients([]PoetClient{poetProver}), ) require.NoError(t, err) challenge := types.NIPostChallenge{PublishEpoch: currLayer.GetEpoch() - 1} @@ -672,7 +711,9 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { } require.NoError(t, saveBuilderState(dir, &state)) - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrATXChallengeExpired) require.ErrorContains(t, err, "deadline to publish ATX for pub epoch") require.Nil(t, nipost) @@ -702,7 +743,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { buildCtx, cancel := context.WithCancel(context.Background()) - poet := NewMockpoetClient(ctrl) + poet := NewMockPoetClient(ctrl) poet.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte("poet0")}, nil) poet.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -712,7 +753,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { _, _ []byte, _ types.EdSignature, _ types.NodeID, - _ PoetPoW, + _ PoetAuth, ) (*types.PoetRound, error) { cancel() return &types.PoetRound{}, context.Canceled @@ -742,12 +783,15 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { sig, poetCfg, mclock, - withPoetClients([]poetClient{poet}), + withPoetClients([]PoetClient{poet}), ) require.NoError(t, err) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poet}).Return(nil).Times(2) + // Act - nipost, err := nb.BuildNIPost(buildCtx, &challenge) + nipost, err := nb.BuildNIPost(buildCtx, &challenge, certifier) require.ErrorIs(t, err, context.Canceled) require.Nil(t, nipost) @@ -756,7 +800,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&types.PoetRound{}, nil) - nipost, err = nb.BuildNIPost(context.Background(), &challenge) + nipost, err = nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) // Verify @@ -831,15 +875,14 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { } ctrl := gomock.NewController(t) - poets := make([]poetClient, 0, 2) + poets := make([]PoetClient, 0, 2) { - poetProvider := NewMockpoetClient(ctrl) + poetProvider := NewMockPoetClient(ctrl) poetProvider.EXPECT().Address().Return(tc.from) poetProvider.EXPECT(). PoetServiceID(gomock.Any()). Return(types.PoetServiceID{ServiceID: []byte("poet-from")}, nil). AnyTimes() - poetProvider.EXPECT().PowParams(gomock.Any()).AnyTimes().Return(&PoetPowParams{}, nil) // PoET succeeds to submit poetProvider.EXPECT(). @@ -855,7 +898,7 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { { // PoET fails submission - poetProvider := NewMockpoetClient(ctrl) + poetProvider := NewMockPoetClient(ctrl) poetProvider.EXPECT().Address().Return(tc.to) poetProvider.EXPECT(). PoetServiceID(gomock.Any()). @@ -908,7 +951,10 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { ) require.NoError(t, err) - nipost, err := nb.BuildNIPost(context.Background(), &challenge) + certifier := NewMockcertifierService(ctrl) + certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) + + nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) require.NotNil(t, nipost) }) diff --git a/activation/poet.go b/activation/poet.go index 61e875fbb9..4b3f8366ce 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -24,6 +24,7 @@ var ( ErrNotFound = errors.New("not found") ErrUnavailable = errors.New("unavailable") ErrInvalidRequest = errors.New("invalid request") + ErrUnathorized = errors.New("unauthorized") ) type PoetPowParams struct { @@ -149,6 +150,25 @@ func (c *HTTPPoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) }, nil } +func (c *HTTPPoetClient) CertifierInfo(ctx context.Context) (*CertifierInfo, error) { + info, err := c.info(ctx) + if err != nil { + return nil, err + } + certifierInfo := info.GetCertifier() + if certifierInfo == nil { + return nil, errors.New("poet doesn't support certifier") + } + url, err := url.Parse(certifierInfo.Url) + if err != nil { + return nil, fmt.Errorf("parsing certifier address: %w", err) + } + return &CertifierInfo{ + PubKey: certifierInfo.Pubkey, + URL: url, + }, nil +} + // Submit registers a challenge in the proving service current open round. func (c *HTTPPoetClient) Submit( ctx context.Context, @@ -156,20 +176,28 @@ func (c *HTTPPoetClient) Submit( prefix, challenge []byte, signature types.EdSignature, nodeID types.NodeID, - pow PoetPoW, + auth PoetAuth, ) (*types.PoetRound, error) { request := rpcapi.SubmitRequest{ Prefix: prefix, Challenge: challenge, Signature: signature.Bytes(), Pubkey: nodeID.Bytes(), - Nonce: pow.Nonce, - PowParams: &rpcapi.PowParams{ - Challenge: pow.Params.Challenge, - Difficulty: uint32(pow.Params.Difficulty), - }, - Deadline: timestamppb.New(deadline), + Deadline: timestamppb.New(deadline), + } + if auth.PoetPoW != nil { + request.PowParams = &rpcapi.PowParams{ + Challenge: auth.PoetPoW.Params.Challenge, + Difficulty: uint32(auth.PoetPoW.Params.Difficulty), + } + request.Nonce = auth.PoetPoW.Nonce + } + if auth.PoetCert != nil { + request.Certificate = &rpcapi.SubmitRequest_Certificate{ + Signature: auth.PoetCert.Signature, + } } + resBody := rpcapi.SubmitResponse{} if err := c.req(ctx, http.MethodPost, "/v1/submit", &request, &resBody); err != nil { return nil, fmt.Errorf("submitting challenge: %w", err) @@ -182,18 +210,25 @@ func (c *HTTPPoetClient) Submit( return &types.PoetRound{ID: resBody.RoundId, End: types.RoundEnd(roundEnd)}, nil } +func (c *HTTPPoetClient) info(ctx context.Context) (*rpcapi.InfoResponse, error) { + resBody := rpcapi.InfoResponse{} + if err := c.req(ctx, http.MethodGet, "/v1/info", nil, &resBody); err != nil { + return nil, fmt.Errorf("getting poet ID: %w", err) + } + // cache the poet service ID + c.poetServiceID.ServiceID = resBody.ServicePubkey + return &resBody, nil +} + // PoetServiceID returns the public key of the PoET proving service. func (c *HTTPPoetClient) PoetServiceID(ctx context.Context) (types.PoetServiceID, error) { if c.poetServiceID.ServiceID != nil { return c.poetServiceID, nil } - resBody := rpcapi.InfoResponse{} - - if err := c.req(ctx, http.MethodGet, "/v1/info", nil, &resBody); err != nil { - return types.PoetServiceID{}, fmt.Errorf("getting poet ID: %w", err) + if _, err := c.info(ctx); err != nil { + return types.PoetServiceID{}, err } - c.poetServiceID.ServiceID = resBody.ServicePubkey return c.poetServiceID, nil } @@ -271,6 +306,8 @@ func (c *HTTPPoetClient) req(ctx context.Context, method, path string, reqBody, return fmt.Errorf("%w: response status code: %s, body: %s", ErrUnavailable, res.Status, string(data)) case http.StatusBadRequest: return fmt.Errorf("%w: response status code: %s, body: %s", ErrInvalidRequest, res.Status, string(data)) + case http.StatusUnauthorized: + return fmt.Errorf("%w: response status code: %s, body: %s", ErrUnathorized, res.Status, string(data)) default: return fmt.Errorf("unrecognized error: status code: %s, body: %s", res.Status, string(data)) } diff --git a/activation/poet_client_test.go b/activation/poet_client_test.go index 0849dadef9..89be67cfd8 100644 --- a/activation/poet_client_test.go +++ b/activation/poet_client_test.go @@ -64,7 +64,7 @@ func Test_HTTPPoetClient_Submit(t *testing.T) { nil, types.EmptyEdSignature, types.NodeID{}, - PoetPoW{}, + PoetAuth{}, ) require.NoError(t, err) } diff --git a/go.mod b/go.mod index 0ae2ceef66..92a7344f8a 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/spacemeshos/go-spacemesh go 1.21.3 +replace github.com/spacemeshos/poet => ../poet + require ( cloud.google.com/go/storage v1.34.1 github.com/ALTree/bigfloat v0.2.0 @@ -33,6 +35,7 @@ require ( github.com/prometheus/common v0.45.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 + github.com/sourcegraph/conc v0.3.0 github.com/spacemeshos/api/release/go v1.24.0 github.com/spacemeshos/economics v0.1.1 github.com/spacemeshos/fixed v0.1.1 @@ -185,7 +188,6 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect github.com/spacemeshos/sha256-simd v0.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cast v1.5.1 // indirect diff --git a/go.sum b/go.sum index 775cf8eeaa..f44b6d35ea 100644 --- a/go.sum +++ b/go.sum @@ -647,8 +647,6 @@ github.com/spacemeshos/go-scale v1.1.12 h1:O66yfIBaXSCqbxJYlDP6QSI2s9Lz8rvZjPe3q github.com/spacemeshos/go-scale v1.1.12/go.mod h1:loK9wrj9IHxATTrVqIyR2o9SB+E9/SAsiDDXuUfvbA8= github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas= github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww= -github.com/spacemeshos/poet v0.9.7 h1:FmKhgUKj//8Tzn8czWSIrn6+FVUFZbvLh8zqLfB8dfE= -github.com/spacemeshos/poet v0.9.7/go.mod h1:wGCdhs2jnfQ52Amcmygv9uEEwYpdHAPjbiPg0Uf6cNQ= github.com/spacemeshos/post v0.10.1 h1:dFWB1xHa4Z+mDqmZjlQxBVn0e5gBs4QS/j50WXfHoac= github.com/spacemeshos/post v0.10.1/go.mod h1:FPa130ioHforcqca0vqJrYjgUsYzTBwivy6lyLy7sKA= github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= From 08677b35e3f1f8775fda976b5f0ea140785001ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 3 Nov 2023 12:21:44 +0100 Subject: [PATCH 02/55] Support for poet certificates cont --- activation/activation.go | 23 +- activation/activation_test.go | 2 +- activation/certifier.go | 61 ++++-- activation/certifier_test.go | 48 +++++ activation/e2e/certifier_client_test.go | 9 +- activation/e2e/nipost_test.go | 55 +---- activation/e2e/validation_test.go | 7 +- activation/interface.go | 4 + activation/mocks.go | 63 ++++++ activation/nipost.go | 20 +- activation/nipost_test.go | 239 +++++++++++++++------ go.mod | 4 +- go.sum | 2 + node/node.go | 15 +- systest/Makefile | 4 +- systest/cluster/cluster.go | 40 ++++ systest/cluster/nodes.go | 103 ++++++++- systest/parameters/bignet/certifier.yaml | 17 ++ systest/parameters/fastnet/certifier.yaml | 17 ++ systest/parameters/fastnet/embed.go | 3 + systest/parameters/fastnet/poet.conf | 2 + systest/parameters/longfast/certifier.yaml | 17 ++ systest/testcontext/context.go | 7 + 23 files changed, 586 insertions(+), 176 deletions(-) create mode 100644 activation/certifier_test.go create mode 100644 systest/parameters/bignet/certifier.yaml create mode 100644 systest/parameters/fastnet/certifier.yaml create mode 100644 systest/parameters/longfast/certifier.yaml diff --git a/activation/activation.go b/activation/activation.go index 7926555f7b..7ef2676c8f 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -88,11 +88,13 @@ type Builder struct { smeshingMutex sync.Mutex started bool - layerClock layerClock - syncer syncer - log *zap.Logger - parentCtx context.Context - stop context.CancelFunc + layerClock layerClock + syncer syncer + log *zap.Logger + parentCtx context.Context + stop context.CancelFunc + + poets []PoetClient poetCfg PoetConfig poetRetryInterval time.Duration } @@ -122,6 +124,12 @@ func WithPoetConfig(c PoetConfig) BuilderOption { } } +func WithPoets(poets ...PoetClient) BuilderOption { + return func(b *Builder) { + b.poets = poets + } +} + func WithValidator(v nipostValidator) BuilderOption { return func(b *Builder) { b.validator = v @@ -383,8 +391,9 @@ func (b *Builder) run(ctx context.Context) { for { err := b.buildInitialPost(ctx) if err == nil { - // TODO certify initial post - // b.certifier.CertifyAll() + client := NewCertifierClient(b.log.Zap(), b.initialPost, b.initialPostInfo) + b.certifier = NewCertifier(b.nipostBuilder.DataDir(), b.log.Zap(), client) + b.certifier.CertifyAll(ctx, b.poets) break } b.log.Error("failed to generate initial proof:", zap.Error(err)) diff --git a/activation/activation_test.go b/activation/activation_test.go index 3e4121599e..0108ea4cfe 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1140,7 +1140,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { var last time.Time builderConfirmation := make(chan struct{}) tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).Times(expectedTries).DoAndReturn( - func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + func(_ context.Context, challenge *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { now := time.Now() if now.Sub(last) < retryInterval { require.FailNow(t, "retry interval not respected") diff --git a/activation/certifier.go b/activation/certifier.go index 56baec6739..1014c9f1f8 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -17,10 +17,11 @@ import ( "github.com/hashicorp/go-retryablehttp" "github.com/natefinch/atomic" "github.com/sourcegraph/conc/iter" - "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/post/shared" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + "github.com/spacemeshos/go-spacemesh/common/types" ) type ProofToCertify struct { @@ -49,28 +50,17 @@ type CertifyResponse struct { } type Certifier struct { - client *retryablehttp.Client logger *zap.Logger store *certificateStore - - post *types.Post - postInfo *types.PostInfo + client certifierClient } -func NewCertifier(datadir string, logger *zap.Logger, post *types.Post, postInfo *types.PostInfo) *Certifier { - c := &Certifier{ - client: retryablehttp.NewClient(), - logger: logger, - store: openCertificateStore(datadir, logger), - post: post, - postInfo: postInfo, - } - c.client.Logger = &retryableHttpLogger{logger} - c.client.ResponseLogHook = func(logger retryablehttp.Logger, resp *http.Response) { - c.logger.Info("response received", zap.Stringer("url", resp.Request.URL), zap.Int("status", resp.StatusCode)) +func NewCertifier(datadir string, logger *zap.Logger, client certifierClient) *Certifier { + return &Certifier{ + client: client, + logger: logger, + store: openCertificateStore(datadir, logger), } - - return c } func (c *Certifier) GetCertificate(poet string) *PoetCert { @@ -85,11 +75,14 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, if err != nil { return nil, fmt.Errorf("querying certifier info: %w", err) } - cert, err := c.certifyPost(ctx, info.URL, info.PubKey) + cert, err := c.client.Certify(ctx, info.URL, info.PubKey) if err != nil { - return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), info.URL, info.PubKey) + return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), info.URL, err) } c.store.put(poet.Address(), *cert) + if err := c.store.persist(); err != nil { + c.logger.Warn("failed to persist poet certs", zap.Error(err)) + } return cert, nil } @@ -162,13 +155,13 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri })), ) - cert, err := c.certifyPost(ctx, svc.URL, svc.PubKey) + cert, err := c.client.Certify(ctx, svc.URL, svc.PubKey) if err != nil { c.logger.Warn("failed to certify", zap.Error(err), zap.Stringer("certifier", svc.URL)) continue } c.logger.Info( - "sucessfully obtained certificate", + "successfully obtained certificate", zap.Stringer("certifier", svc.URL), zap.Binary("cert", cert.Signature), ) @@ -181,7 +174,29 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri return certs } -func (c *Certifier) certifyPost(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { +type CertifierClient struct { + client *retryablehttp.Client + post *types.Post + postInfo *types.PostInfo + logger *zap.Logger +} + +func NewCertifierClient(logger *zap.Logger, post *types.Post, postInfo *types.PostInfo) *CertifierClient { + c := &CertifierClient{ + client: retryablehttp.NewClient(), + logger: logger, + post: post, + postInfo: postInfo, + } + c.client.Logger = &retryableHttpLogger{logger} + c.client.ResponseLogHook = func(logger retryablehttp.Logger, resp *http.Response) { + c.logger.Info("response received", zap.Stringer("url", resp.Request.URL), zap.Int("status", resp.StatusCode)) + } + + return c +} + +func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { request := CertifyRequest{ Proof: ProofToCertify{ Pow: c.post.Pow, diff --git a/activation/certifier_test.go b/activation/certifier_test.go new file mode 100644 index 0000000000..e0ca0920a8 --- /dev/null +++ b/activation/certifier_test.go @@ -0,0 +1,48 @@ +package activation_test + +import ( + "context" + "net/url" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "go.uber.org/zap/zaptest" + + "github.com/spacemeshos/go-spacemesh/activation" +) + +func TestPersistsCerts(t *testing.T) { + client := activation.NewMockcertifierClient(gomock.NewController(t)) + datadir := t.TempDir() + + { + certifier := activation.NewCertifier(datadir, zaptest.NewLogger(t), client) + + poetMock := activation.NewMockPoetClient(gomock.NewController(t)) + poetMock.EXPECT().Address().Return("http://poet") + poetMock.EXPECT().CertifierInfo(gomock.Any()).Return(&activation.CertifierInfo{ + URL: &url.URL{Scheme: "http", Host: "certifier.org"}, + PubKey: []byte("pubkey"), + }, nil) + + client.EXPECT(). + Certify(gomock.Any(), &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). + Return(&activation.PoetCert{Signature: []byte("cert")}, nil) + + require.Nil(t, certifier.GetCertificate("http://poet")) + certs, err := certifier.Recertify(context.Background(), poetMock) + require.NoError(t, err) + require.Equal(t, []byte("cert"), certs.Signature) + + cert := certifier.GetCertificate("http://poet") + require.Equal(t, []byte("cert"), cert.Signature) + require.Nil(t, certifier.GetCertificate("http://other-poet")) + } + { + // Create new certifier and check that it loads the certs back. + certifier := activation.NewCertifier(datadir, zaptest.NewLogger(t), client) + cert := certifier.GetCertificate("http://poet") + require.Equal(t, []byte("cert"), cert.Signature) + } +} diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index c8fdd4d01d..ca3872b01f 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -64,7 +64,7 @@ func TestCertification(t *testing.T) { pubKey, addr := spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) certifierCfg := ®istration.CertifierConfig{ URL: "http://" + addr.String(), - PubKey: pubKey, + PubKey: registration.Base64Enc(pubKey), } for i := 0; i < 2; i++ { @@ -78,7 +78,7 @@ func TestCertification(t *testing.T) { pubKey, addr = spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) certifierCfg = ®istration.CertifierConfig{ URL: "http://" + addr.String(), - PubKey: pubKey, + PubKey: registration.Base64Enc(pubKey), } poet := spawnPoet(t, WithCertifier(certifierCfg)) @@ -92,8 +92,9 @@ func TestCertification(t *testing.T) { require.NoError(t, err) poets = append(poets, client) - certifierClient := activation.NewCertifier(t.TempDir(), zaptest.NewLogger(t), post, info) - certs := certifierClient.CertifyAll(context.Background(), poets) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info) + certifier := activation.NewCertifier(t.TempDir(), zaptest.NewLogger(t), certifierClient) + certs := certifier.CertifyAll(context.Background(), poets) require.Len(t, certs, 3) require.Contains(t, certs, poets[0].Address()) require.Contains(t, certs, poets[1].Address()) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 3f4037700f..a98784bf83 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -150,7 +150,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { pubKey, addr := spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) certifierCfg := ®istration.CertifierConfig{ URL: "http://" + addr.String(), - PubKey: pubKey, + PubKey: registration.Base64Enc(pubKey), } poetProver := spawnPoet( @@ -200,18 +200,19 @@ func TestNIPostBuilderWithClients(t *testing.T) { ) require.NoError(t, err) - certifier := activation.NewCertifier(t.TempDir(), logger, post, info) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info) + certifier := activation.NewCertifier(t.TempDir(), logger, certifierClient) certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) nb, err := activation.NewNIPostBuilder( poetDb, svc, - []string{poetProver.RestURL().String()}, t.TempDir(), logger.Named("nipostBuilder"), sig, poetCfg, mclock, + activation.WithPoetClients(client), ) require.NoError(t, err) @@ -233,51 +234,3 @@ func TestNIPostBuilderWithClients(t *testing.T) { ) require.NoError(t, err) } - -func TestNIPostBuilder_Close(t *testing.T) { - ctrl := gomock.NewController(t) - - sig, err := signing.NewEdSigner() - require.NoError(t, err) - - logger := zaptest.NewLogger(t) - - poetProver := spawnPoet(t, WithGenesis(time.Now()), WithEpochDuration(time.Second)) - poetDb := activation.NewMockpoetDbAPI(ctrl) - - 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)) - }, - ) - - svc := grpcserver.NewPostService(logger) - - nb, err := activation.NewNIPostBuilder( - poetDb, - svc, - []string{poetProver.RestURL().String()}, - t.TempDir(), - logger.Named("nipostBuilder"), - sig, - activation.PoetConfig{}, - mclock, - ) - require.NoError(t, err) - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - challenge := types.NIPostChallenge{ - PublishEpoch: postGenesisEpoch + 2, - } - - certifier := activation.NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) - - nipost, err := nb.BuildNIPost(ctx, &challenge, certifier) - require.ErrorIs(t, err, context.Canceled) - require.Nil(t, nipost) -} diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index e93a81735b..65dc5271bd 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -58,6 +58,8 @@ func TestValidator_Validate(t *testing.T) { WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap), ) + client, err := activation.NewHTTPPoetClient(poetProver.RestURL().String(), poetCfg) + require.NoError(t, err) mclock := activation.NewMocklayerClock(ctrl) mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn( @@ -93,12 +95,12 @@ func TestValidator_Validate(t *testing.T) { nb, err := activation.NewNIPostBuilder( poetDb, svc, - []string{poetProver.RestURL().String()}, t.TempDir(), logger.Named("nipostBuilder"), sig, poetCfg, mclock, + activation.WithPoetClients(client), ) require.NoError(t, err) @@ -107,7 +109,8 @@ func TestValidator_Validate(t *testing.T) { } challengeHash := challenge.Hash() - certifier := activation.NewCertifier(t.TempDir(), logger, post, info) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info) + certifier := activation.NewCertifier(t.TempDir(), logger, certifierClient) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) diff --git a/activation/interface.go b/activation/interface.go index 1ee502a457..8e36acf937 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -136,6 +136,10 @@ type CertifierInfo struct { PubKey []byte } +type certifierClient interface { + Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) +} + // certifierService is used to certify nodeID for registerting in the poet. type certifierService interface { // Acquire a certificate for the given poet. diff --git a/activation/mocks.go b/activation/mocks.go index d3fdcdff01..bf8ce850f9 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -10,6 +10,7 @@ package activation import ( context "context" + url "net/url" reflect "reflect" time "time" @@ -1551,6 +1552,68 @@ func (c *PoetClientSubmitCall) DoAndReturn(f func(context.Context, time.Time, [] return c } +// MockcertifierClient is a mock of certifierClient interface. +type MockcertifierClient struct { + ctrl *gomock.Controller + recorder *MockcertifierClientMockRecorder +} + +// MockcertifierClientMockRecorder is the mock recorder for MockcertifierClient. +type MockcertifierClientMockRecorder struct { + mock *MockcertifierClient +} + +// NewMockcertifierClient creates a new mock instance. +func NewMockcertifierClient(ctrl *gomock.Controller) *MockcertifierClient { + mock := &MockcertifierClient{ctrl: ctrl} + mock.recorder = &MockcertifierClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockcertifierClient) EXPECT() *MockcertifierClientMockRecorder { + return m.recorder +} + +// Certify mocks base method. +func (m *MockcertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Certify", ctx, url, pubkey) + ret0, _ := ret[0].(*PoetCert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Certify indicates an expected call of Certify. +func (mr *MockcertifierClientMockRecorder) Certify(ctx, url, pubkey any) *certifierClientCertifyCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certify", reflect.TypeOf((*MockcertifierClient)(nil).Certify), ctx, url, pubkey) + return &certifierClientCertifyCall{Call: call} +} + +// certifierClientCertifyCall wrap *gomock.Call +type certifierClientCertifyCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *certifierClientCertifyCall) Return(arg0 *PoetCert, arg1 error) *certifierClientCertifyCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *certifierClientCertifyCall) Do(f func(context.Context, *url.URL, []byte) (*PoetCert, error)) *certifierClientCertifyCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *certifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.URL, []byte) (*PoetCert, error)) *certifierClientCertifyCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockcertifierService is a mock of certifierService interface. type MockcertifierService struct { ctrl *gomock.Controller diff --git a/activation/nipost.go b/activation/nipost.go index ffba879e04..62e3b06ca8 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -76,8 +76,7 @@ type NIPostBuilder struct { type NIPostBuilderOption func(*NIPostBuilder) -// withPoetClients allows to pass in clients directly (for testing purposes). -func withPoetClients(clients []PoetClient) NIPostBuilderOption { +func WithPoetClients(clients ...PoetClient) NIPostBuilderOption { return func(nb *NIPostBuilder) { nb.poetProvers = make(map[string]PoetClient, len(clients)) for _, client := range clients { @@ -90,7 +89,6 @@ func withPoetClients(clients []PoetClient) NIPostBuilderOption { func NewNIPostBuilder( poetDB poetDbAPI, postService postService, - poetServers []string, dataDir string, lg *zap.Logger, signer *signing.EdSigner, @@ -98,17 +96,7 @@ func NewNIPostBuilder( layerClock layerClock, opts ...NIPostBuilderOption, ) (*NIPostBuilder, error) { - poetClients := make(map[string]PoetClient, len(poetServers)) - for _, address := range poetServers { - client, err := NewHTTPPoetClient(address, poetCfg, WithLogger(lg.Named("poet"))) - if err != nil { - return nil, fmt.Errorf("cannot create poet client: %w", err) - } - poetClients[client.Address()] = client - } - b := &NIPostBuilder{ - poetProvers: poetClients, poetDB: poetDB, postService: postService, state: &types.NIPostBuilderState{NIPost: &types.NIPost{}}, @@ -157,7 +145,11 @@ func (nb *NIPostBuilder) UpdatePoETProvers(poetProvers []PoetClient) { // BuildNIPost uses the given challenge to build a NIPost. // The process can take considerable time, because it includes waiting for the poet service to // publish a proof - a process that takes about an epoch. -func (nb *NIPostBuilder) BuildNIPost(ctx context.Context, challenge *types.NIPostChallenge, certifier certifierService) (*types.NIPost, error) { +func (nb *NIPostBuilder) BuildNIPost( + ctx context.Context, + challenge *types.NIPostChallenge, + certifier certifierService, +) (*types.NIPost, error) { logger := nb.log.With(log.ZContext(ctx)) // Note: to avoid missing next PoET round, we need to publish the ATX before the next PoET round starts. // We can still publish an ATX late (i.e. within publish epoch) and receive rewards, but we will miss one diff --git a/activation/nipost_test.go b/activation/nipost_test.go index f0dc67427a..741d368cd3 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -23,7 +23,10 @@ func defaultPoetServiceMock(ctrl *gomock.Controller, id []byte, address string) Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). AnyTimes(). Return(&types.PoetRound{}, nil) - poetClient.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: id}, nil) + poetClient.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().DoAndReturn( + func(ctx context.Context) (types.PoetServiceID, error) { + return types.PoetServiceID{ServiceID: id}, ctx.Err() + }) poetClient.EXPECT().PowParams(gomock.Any()).AnyTimes().Return(&PoetPowParams{}, nil) poetClient.EXPECT().Address().AnyTimes().Return(address).AnyTimes() return poetClient @@ -70,18 +73,19 @@ func TestNIPostBuilderWithMocks(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProvider}), + WithPoetClients(poetProvider), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProvider}).Return(nil) + certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&PoetCert{ + Signature: []byte("cert"), + }) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) @@ -112,13 +116,12 @@ func TestPostSetup(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), postProvider.signer, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProvider}), + WithPoetClients(poetProvider), ) require.NoError(t, err) @@ -127,7 +130,9 @@ func TestPostSetup(t *testing.T) { t.Cleanup(func() { assert.NoError(t, postProvider.Reset()) }) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProvider}).Return(nil) + certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&PoetCert{ + Signature: []byte("cert"), + }) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) @@ -177,18 +182,19 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, dir, zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) + certifier.EXPECT().GetCertificate(poetProver.Address()).AnyTimes().Return(&PoetCert{ + Signature: []byte("cert"), + }) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -200,20 +206,18 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { nb, err = NewNIPostBuilder( poetDb, postService, - []string{}, dir, zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("error")).Times(1) // check that proof ref is not called again - certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) nipost, err = nb.BuildNIPost(context.Background(), &challenge2, certifier) require.Nil(t, nipost) require.Error(t, err) @@ -224,13 +228,12 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { nb, err = NewNIPostBuilder( poetDb, postService, - []string{}, dir, zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{}, &types.PostInfo{}, nil).Times(1) @@ -243,7 +246,6 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { // test state not loading if other challenge provided poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{}, &types.PostInfo{}, nil).Times(1) - certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) nipost, err = nb.BuildNIPost(context.Background(), &challenge3, certifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -283,7 +285,6 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. <-ctx.Done() return nil, ctx.Err() }) - poet.EXPECT().PowParams(gomock.Any()).Return(&PoetPowParams{}, nil) poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") poets = append(poets, poet) } @@ -299,7 +300,6 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. poet.EXPECT(). Proof(gomock.Any(), gomock.Any()). Return(proof, []types.Member{types.Member(challenge.Hash())}, nil) - poet.EXPECT().PowParams(gomock.Any()).Return(&PoetPowParams{}, nil) poet.EXPECT().Address().AnyTimes().Return("http://localhost:9998") poets = append(poets, poet) } @@ -318,18 +318,22 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients(poets), + WithPoetClients(poets...), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), poets).Return(nil) + certifier.EXPECT().GetCertificate(poets[0].Address()).Return(&PoetCert{ + Signature: []byte("cert"), + }) + certifier.EXPECT().GetCertificate(poets[1].Address()).Return(&PoetCert{ + Signature: []byte("cert"), + }) // Act nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) @@ -366,7 +370,12 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { poets := make([]PoetClient, 0, 2) { - poet := defaultPoetServiceMock(ctrl, []byte("poet0"), "http://localhost:9999") + poet := NewMockPoetClient(ctrl) + poet.EXPECT(). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(&types.PoetRound{}, nil) + poet.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte("poet0")}, nil) + poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999").AnyTimes() poet.EXPECT().Proof(gomock.Any(), "").Return(proofWorse, []types.Member{types.Member(challenge.Hash())}, nil) poets = append(poets, poet) } @@ -387,18 +396,19 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients(poets), + WithPoetClients(poets...), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), poets).Return(nil) + certifier.EXPECT().GetCertificate(poets[0].Address()).Return(&PoetCert{Signature: []byte("cert")}) + // No certs - fallback to PoW + certifier.EXPECT().GetCertificate(poets[1].Address()).Return(nil) // Act nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) @@ -417,7 +427,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetCfg := PoetConfig{ PhaseShift: layerDuration, } - + cert := &PoetCert{Signature: []byte("cert")} sig, err := signing.NewEdSigner() require.NoError(t, err) @@ -427,26 +437,23 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{}, errors.New("test")) + poetProver.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{}, errors.New("test")) poetProver.EXPECT().Address().Return("http://localhost:9999") postService := NewMockpostService(ctrl) nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) - nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) require.Nil(t, nipst) @@ -457,29 +464,27 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte{}}, nil) + poetProver.EXPECT().PoetServiceID(gomock.Any()).Return(types.PoetServiceID{}, nil) poetProver.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), PoetAuth{PoetCert: cert}). Return(nil, errors.New("test")) - poetProver.EXPECT().PowParams(gomock.Any()).Return(&PoetPowParams{}, nil) poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) @@ -491,9 +496,9 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte{}}, nil) + poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{}, nil) poetProver.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), PoetAuth{PoetCert: cert}). DoAndReturn(func( ctx context.Context, _ time.Time, @@ -505,25 +510,23 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { <-ctx.Done() return nil, ctx.Err() }) - poetProver.EXPECT().PowParams(gomock.Any()).Return(&PoetPowParams{}, nil) poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) @@ -541,18 +544,17 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetProofNotReceived) @@ -573,18 +575,17 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poetProver}).Return(nil) + certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrPoetProofNotReceived) @@ -592,6 +593,79 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { }) } +func TestNIPostBuilder_RecertifyPoet(t *testing.T) { + t.Parallel() + sig, err := signing.NewEdSigner() + require.NoError(t, err) + challenge := types.NIPostChallenge{ + PublishEpoch: postGenesisEpoch + 1, + } + + ctrl := gomock.NewController(t) + poetDb := NewMockpoetDbAPI(ctrl) + poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) + mclock := defaultLayerClockMock(ctrl) + poetProver := NewMockPoetClient(ctrl) + poetProver.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{}, nil) + poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") + postClient := NewMockPostClient(ctrl) + postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{}, &types.PostInfo{}, nil) + postService := NewMockpostService(ctrl) + postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) + + nb, err := NewNIPostBuilder( + poetDb, + postService, + t.TempDir(), + zaptest.NewLogger(t), + sig, + PoetConfig{PhaseShift: layerDuration}, + mclock, + WithPoetClients(poetProver), + ) + require.NoError(t, err) + + certifier := NewMockcertifierService(ctrl) + + invalid := &PoetCert{Signature: []byte("certInvalid")} + getInvalid := certifier.EXPECT().GetCertificate("http://localhost:9999").Return(invalid) + submitFailed := poetProver.EXPECT(). + Submit( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + sig.NodeID(), + PoetAuth{PoetCert: invalid}, + ). + After(getInvalid.Call). + Return(nil, ErrUnathorized) + + valid := &PoetCert{Signature: []byte("certInvalid")} + recertify := certifier.EXPECT().Recertify(gomock.Any(), poetProver).After(submitFailed).Return(valid, nil) + submitOK := poetProver.EXPECT(). + Submit( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + sig.NodeID(), + PoetAuth{PoetCert: valid}, + ). + After(recertify). + Return(&types.PoetRound{}, nil) + + poetProver.EXPECT(). + Proof(gomock.Any(), ""). + After(submitOK). + Return(&types.PoetProofMessage{}, []types.Member{types.Member(challenge.Hash())}, nil) + + _, err = nb.BuildNIPost(context.Background(), &challenge, certifier) + require.NoError(t, err) +} + // TestNIPoSTBuilder_StaleChallenge checks if // it properly detects that the challenge is stale and the poet round has already started. func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { @@ -620,18 +694,16 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - challenge := types.NIPostChallenge{PublishEpoch: currLayer.GetEpoch()} nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.ErrorIs(t, err, ErrATXChallengeExpired) @@ -654,13 +726,12 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, dir, zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) challenge := types.NIPostChallenge{PublishEpoch: currLayer.GetEpoch() - 1} @@ -693,13 +764,12 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, dir, zaptest.NewLogger(t), sig, PoetConfig{}, mclock, - withPoetClients([]PoetClient{poetProver}), + WithPoetClients(poetProver), ) require.NoError(t, err) challenge := types.NIPostChallenge{PublishEpoch: currLayer.GetEpoch() - 1} @@ -729,12 +799,14 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { challenge := types.NIPostChallenge{ PublishEpoch: postGenesisEpoch + 1, } - + sig, err := signing.NewEdSigner() + require.NoError(t, err) proof := &types.PoetProofMessage{ PoetProof: types.PoetProof{ LeafCount: 777, }, } + cert := &PoetCert{Signature: []byte("cert")} ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) @@ -746,7 +818,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { poet := NewMockPoetClient(ctrl) poet.EXPECT().PoetServiceID(gomock.Any()).AnyTimes().Return(types.PoetServiceID{ServiceID: []byte("poet0")}, nil) poet.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), PoetAuth{PoetCert: cert}). DoAndReturn(func( _ context.Context, _ time.Time, @@ -758,7 +830,6 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { cancel() return &types.PoetRound{}, context.Canceled }) - poet.EXPECT().PowParams(gomock.Any()).Times(2).Return(&PoetPowParams{}, nil) poet.EXPECT().Proof(gomock.Any(), "").Return(proof, []types.Member{types.Member(challenge.Hash())}, nil) poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") @@ -766,9 +837,6 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { PhaseShift: layerDuration * layersPerEpoch / 2, } - sig, err := signing.NewEdSigner() - require.NoError(t, err) - postClient := NewMockPostClient(ctrl) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{}, &types.PostInfo{}, nil) postService := NewMockpostService(ctrl) @@ -777,18 +845,17 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients([]PoetClient{poet}), + WithPoetClients(poet), ) require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), []PoetClient{poet}).Return(nil).Times(2) + certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) // Act nipost, err := nb.BuildNIPost(buildCtx, &challenge, certifier) @@ -800,6 +867,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&types.PoetRound{}, nil) + certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) nipost, err = nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) @@ -941,13 +1009,12 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { nb, err := NewNIPostBuilder( poetDb, postService, - []string{}, t.TempDir(), zaptest.NewLogger(t), sig, poetCfg, mclock, - withPoetClients(poets), + WithPoetClients(poets...), ) require.NoError(t, err) @@ -1008,3 +1075,41 @@ func TestCalculatingGetProofWaitTime(t *testing.T) { ) }) } + +func TestNIPostBuilder_Close(t *testing.T) { + ctrl := gomock.NewController(t) + + mclock := 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)) + }, + ) + + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + nb, err := NewNIPostBuilder( + NewMockpoetDbAPI(ctrl), + NewMockpostService(ctrl), + t.TempDir(), + zaptest.NewLogger(t), + sig, + PoetConfig{}, + mclock, + WithPoetClients(defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999")), + ) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + challenge := types.NIPostChallenge{ + PublishEpoch: postGenesisEpoch + 2, + } + + nipost, err := nb.BuildNIPost(ctx, &challenge, NewMockcertifierService(ctrl)) + require.ErrorIs(t, err, context.Canceled) + require.Nil(t, nipost) +} diff --git a/go.mod b/go.mod index 92a7344f8a..9fe63f9c0d 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/spacemeshos/go-spacemesh go 1.21.3 -replace github.com/spacemeshos/poet => ../poet - require ( cloud.google.com/go/storage v1.34.1 github.com/ALTree/bigfloat v0.2.0 @@ -41,7 +39,7 @@ require ( github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.1.12 github.com/spacemeshos/merkle-tree v0.2.3 - github.com/spacemeshos/poet v0.9.7 + github.com/spacemeshos/poet v0.10.0-rc2 github.com/spacemeshos/post v0.10.1 github.com/spf13/afero v1.10.0 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index f44b6d35ea..6ce7165a1d 100644 --- a/go.sum +++ b/go.sum @@ -647,6 +647,8 @@ github.com/spacemeshos/go-scale v1.1.12 h1:O66yfIBaXSCqbxJYlDP6QSI2s9Lz8rvZjPe3q github.com/spacemeshos/go-scale v1.1.12/go.mod h1:loK9wrj9IHxATTrVqIyR2o9SB+E9/SAsiDDXuUfvbA8= github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas= github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww= +github.com/spacemeshos/poet v0.10.0-rc2 h1:qas9awz8c0XaV0nDM/zK977NqJGN0hLOBu3+olLhxDw= +github.com/spacemeshos/poet v0.10.0-rc2/go.mod h1:IfOF+rRxTP7dWXwojf/TIx7HYIZVxst2ZQ6UUtgYpRA= github.com/spacemeshos/post v0.10.1 h1:dFWB1xHa4Z+mDqmZjlQxBVn0e5gBs4QS/j50WXfHoac= github.com/spacemeshos/post v0.10.1/go.mod h1:FPa130ioHforcqca0vqJrYjgUsYzTBwivy6lyLy7sKA= github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= diff --git a/node/node.go b/node/node.go index 3bc4089b04..85815b4b47 100644 --- a/node/node.go +++ b/node/node.go @@ -939,15 +939,27 @@ func (app *App) initServices(ctx context.Context) error { app.grpcPostService = grpcserver.NewPostService(app.addLogger(PostServiceLogger, lg).Zap()) + poetClients := make([]activation.PoetClient, 0, len(app.Config.PoETServers)) + for _, address := range app.Config.PoETServers { + client, err := activation.NewHTTPPoetClient( + address, + app.Config.POET, + activation.WithLogger(lg.Zap().Named("poet")), + ) + if err != nil { + app.log.Panic("failed to create poet client: %v", err) + } + poetClients = append(poetClients, client) + } nipostBuilder, err := activation.NewNIPostBuilder( poetDb, app.grpcPostService, - app.Config.PoETServers, app.Config.SMESHING.Opts.DataDir, app.addLogger(NipostBuilderLogger, lg).Zap(), app.edSgn, app.Config.POET, app.clock, + activation.WithPoetClients(poetClients...), ) if err != nil { app.log.Panic("failed to create nipost builder: %v", err) @@ -989,6 +1001,7 @@ func (app *App) initServices(ctx context.Context) error { activation.WithPoetConfig(app.Config.POET), activation.WithPoetRetryInterval(app.Config.HARE.WakeupDelta), activation.WithValidator(app.validator), + activation.WithPoets(poetClients...), ) if err := atxBuilder.MigrateDiskToLocalDB(); err != nil { app.log.Panic("failed to migrate state of atx builder: %v", err) diff --git a/systest/Makefile b/systest/Makefile index 517b7baa5a..877f37087a 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,7 +5,8 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -poet_image ?= spacemeshos/poet:v0.9.7 +certifier_image ?= spacemeshos/certifier-service:58d4d17566b4353580a6f70f182523f06538ce01 +poet_image ?= spacemeshos/poet:v0.10.0-rc2 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) test_id ?= systest-$(version_info) @@ -55,6 +56,7 @@ template: @echo node-selector=$(node_selector) >> $(tmpfile) @echo image=$(smesher_image) >> $(tmpfile) @echo bs-image=$(bs_image) >> $(tmpfile) + @echo certifier-image=$(certifier_image) >> $(tmpfile) @echo poet-image=$(poet_image) >> $(tmpfile) @echo poet-size=$(poet_size) >> $(tmpfile) @echo keep=$(keep) >> $(tmpfile) diff --git a/systest/cluster/cluster.go b/systest/cluster/cluster.go index 428ffab58b..b19ea91473 100644 --- a/systest/cluster/cluster.go +++ b/systest/cluster/cluster.go @@ -3,6 +3,7 @@ package cluster import ( "context" "crypto/ed25519" + "encoding/base64" "encoding/hex" "errors" "fmt" @@ -30,11 +31,13 @@ var errNotInitialized = errors.New("cluster: not initialized") const ( initBalance = 100000000000000000 defaultExtraData = "systest" + certifierApp = "certifier" poetApp = "poet" bootnodeApp = "boot" smesherApp = "smesher" bootstrapperApp = "bootstrapper" bootstrapperPort = 80 + certifierPort = 80 poetPort = 80 poetFlags = "poetflags" @@ -144,6 +147,16 @@ func Default(cctx *testcontext.Context, opts ...Opt) (*Cluster, error) { if err := cl.AddBootstrappers(cctx); err != nil { return nil, err } + pubkey, privkey, err := ed25519.GenerateKey(nil) + if err != nil { + return nil, fmt.Errorf("generating keys for certifier: %w", err) + } + if err := cl.AddCertifier(cctx, base64.StdEncoding.EncodeToString(privkey.Seed())); err != nil { + return nil, err + } + cl.addPoetFlag(PoetCertifierURL("http://certifier-0")) + cl.addPoetFlag(PoetCertifierPubkey(base64.StdEncoding.EncodeToString(pubkey))) + if err := cl.AddPoets(cctx); err != nil { return nil, err } @@ -190,6 +203,7 @@ type Cluster struct { bootnodes int smeshers int clients []*NodeClient + certifiers []*NodeClient poets []*NodeClient bootstrappers []*NodeClient @@ -243,6 +257,16 @@ func (c *Cluster) persistConfigs(ctx *testcontext.Context) error { if err != nil { return fmt.Errorf("apply cfgmap %v/%v: %w", ctx.Namespace, spacemeshConfigMapName, err) } + _, err = ctx.Client.CoreV1().ConfigMaps(ctx.Namespace).Apply( + ctx, + corev1.ConfigMap(certifierConfigMapName, ctx.Namespace).WithData(map[string]string{ + attachedCertifierConfig: certifierConfig.Get(ctx.Parameters), + }), + apimetav1.ApplyOptions{FieldManager: "test"}, + ) + if err != nil { + return fmt.Errorf("apply cfgmap %v/%v: %w", ctx.Namespace, poetConfigMapName, err) + } _, err = ctx.Client.CoreV1().ConfigMaps(ctx.Namespace).Apply( ctx, corev1.ConfigMap(poetConfigMapName, ctx.Namespace).WithData(map[string]string{ @@ -402,6 +426,22 @@ func (c *Cluster) firstFreePoetId() int { } } +// AddCertifier spawns a single certifier with the first available id. +// Id is of form "certifier-N", where N ∈ [0, ∞). +func (c *Cluster) AddCertifier(cctx *testcontext.Context, privkey string) error { + if err := c.persist(cctx); err != nil { + return err + } + id := fmt.Sprintf("certifier-%d", len(c.certifiers)) + cctx.Log.Debugw("deploying poet", "id", id) + pod, err := deployCertifier(cctx, id, privkey) + if err != nil { + return err + } + c.certifiers = append(c.certifiers, pod) + return nil +} + // AddPoet spawns a single poet with the first available id. // Id is of form "poet-N", where N ∈ [0, ∞). func (c *Cluster) AddPoet(cctx *testcontext.Context) error { diff --git a/systest/cluster/nodes.go b/systest/cluster/nodes.go index 166bc457dd..b9c33c2533 100644 --- a/systest/cluster/nodes.go +++ b/systest/cluster/nodes.go @@ -31,6 +31,11 @@ import ( ) var ( + certifierConfig = parameters.String( + "certifier", + "configuration for certifier service", + fastnet.CertifierConfig, + ) poetConfig = parameters.String( "poet", "configuration for poet service", @@ -100,9 +105,11 @@ func toResources(value string) (*apiv1.ResourceRequirements, error) { const ( configDir = "/etc/config/" - attachedPoetConfig = "poet.conf" - attachedSmesherConfig = "smesher.json" + attachedCertifierConfig = "certifier.yaml" + attachedPoetConfig = "poet.conf" + attachedSmesherConfig = "smesher.json" + certifierConfigMapName = "certifier" poetConfigMapName = "poet" spacemeshConfigMapName = "spacemesh" @@ -213,6 +220,60 @@ func (n *NodeClient) NewStream( return stream, err } +func deployCertifierD(ctx *testcontext.Context, id, privkey string) (*NodeClient, error) { + args := []string{ + "-c" + configDir + attachedCertifierConfig, + } + + ctx.Log.Debugw("deploying certifier service pod", "id", id, "args", args, "image", ctx.CertifierImage) + + labels := nodeLabels(certifierApp, id) + + deployment := appsv1.Deployment(id, ctx.Namespace). + WithLabels(labels). + WithSpec(appsv1.DeploymentSpec(). + WithSelector(metav1.LabelSelector().WithMatchLabels(labels)). + WithReplicas(1). + WithTemplate(corev1.PodTemplateSpec(). + WithLabels(labels). + WithSpec(corev1.PodSpec(). + WithNodeSelector(ctx.NodeSelector). + WithVolumes(corev1.Volume(). + WithName("config"). + WithConfigMap(corev1.ConfigMapVolumeSource().WithName(certifierConfigMapName)), + ). + WithContainers(corev1.Container(). + WithName("certifier"). + WithImage(ctx.CertifierImage). + WithArgs(args...). + WithEnv(corev1.EnvVar().WithName("CERTIFIER_SIGNING_KEY").WithValue(privkey)). + WithPorts( + corev1.ContainerPort().WithName("rest").WithProtocol("TCP").WithContainerPort(certifierPort), + ). + WithVolumeMounts( + corev1.VolumeMount().WithName("config").WithMountPath(configDir), + ). + WithResources(corev1.ResourceRequirements(). + WithRequests(poetResources.Get(ctx.Parameters).Requests). + WithLimits(poetResources.Get(ctx.Parameters).Limits), + ), + ), + ))) + + _, err := ctx.Client.AppsV1(). + Deployments(ctx.Namespace). + Apply(ctx, deployment, apimetav1.ApplyOptions{FieldManager: "test"}) + if err != nil { + return nil, fmt.Errorf("creating certifier: %w", err) + } + return &NodeClient{ + session: ctx, + Node: Node{ + Name: id, + }, + }, nil +} + func deployPoetD(ctx *testcontext.Context, id string, flags ...DeploymentFlag) (*NodeClient, error) { args := []string{ "-c=" + configDir + attachedPoetConfig, @@ -287,6 +348,21 @@ func deployBootnodeSvc(ctx *testcontext.Context, id string) error { return nil } +func deployCertifierSvc(ctx *testcontext.Context, id string) (*apiv1.Service, error) { + ctx.Log.Debugw("deploying certifier service", "id", id) + labels := nodeLabels(certifierApp, id) + svc := corev1.Service(id, ctx.Namespace). + WithLabels(labels). + WithSpec(corev1.ServiceSpec(). + WithSelector(labels). + WithPorts( + corev1.ServicePort().WithName("rest").WithPort(certifierPort).WithProtocol("TCP"), + ), + ) + + return ctx.Client.CoreV1().Services(ctx.Namespace).Apply(ctx, svc, apimetav1.ApplyOptions{FieldManager: "test"}) +} + func deployPoetSvc(ctx *testcontext.Context, id string) (*apiv1.Service, error) { ctx.Log.Debugw("deploying poet service", "id", id) labels := nodeLabels(poetApp, id) @@ -322,6 +398,21 @@ func decodePoetIdentifier(id string) int { return ord } +// deployCertifier creates a certifier Deployment and exposes it via a Service. +// The key is passed to the certifier Pod. +func deployCertifier(ctx *testcontext.Context, id, privkey string) (*NodeClient, error) { + if _, err := deployCertifierSvc(ctx, id); err != nil { + return nil, fmt.Errorf("deploying certifier service: %w", err) + } + + node, err := deployCertifierD(ctx, id, privkey) + if err != nil { + return nil, err + } + + return node, nil +} + // deployPoet creates a poet Pod and exposes it via a Service. // Flags are passed to the poet Pod as arguments. func deployPoet(ctx *testcontext.Context, id string, flags ...DeploymentFlag) (*NodeClient, error) { @@ -742,6 +833,14 @@ func DurationFlag(flag string, d time.Duration) DeploymentFlag { return DeploymentFlag{Name: flag, Value: d.String()} } +func PoetCertifierURL(url string) DeploymentFlag { + return DeploymentFlag{Name: "--certifier-url", Value: url} +} + +func PoetCertifierPubkey(key string) DeploymentFlag { + return DeploymentFlag{Name: "--certifier-pubkey", Value: key} +} + // PoetRestListen socket pair with http api. func PoetRestListen(port int) DeploymentFlag { return DeploymentFlag{Name: "--restlisten", Value: fmt.Sprintf("0.0.0.0:%d", port)} diff --git a/systest/parameters/bignet/certifier.yaml b/systest/parameters/bignet/certifier.yaml new file mode 100644 index 0000000000..51d47896d6 --- /dev/null +++ b/systest/parameters/bignet/certifier.yaml @@ -0,0 +1,17 @@ +listen: "0.0.0.0:80" +# Same as fastnet +post_cfg: + k1: 12 + k2: 4 + k3: 4 + pow_difficulty: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +init_cfg: + min_num_units: 2 + max_num_units: 4 + labels_per_unit: 128 + scrypt: + n: 2 + r: 1 + p: 1 + +metrics: true \ No newline at end of file diff --git a/systest/parameters/fastnet/certifier.yaml b/systest/parameters/fastnet/certifier.yaml new file mode 100644 index 0000000000..51d47896d6 --- /dev/null +++ b/systest/parameters/fastnet/certifier.yaml @@ -0,0 +1,17 @@ +listen: "0.0.0.0:80" +# Same as fastnet +post_cfg: + k1: 12 + k2: 4 + k3: 4 + pow_difficulty: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +init_cfg: + min_num_units: 2 + max_num_units: 4 + labels_per_unit: 128 + scrypt: + n: 2 + r: 1 + p: 1 + +metrics: true \ No newline at end of file diff --git a/systest/parameters/fastnet/embed.go b/systest/parameters/fastnet/embed.go index dc8a450bb3..37cbc60ab8 100644 --- a/systest/parameters/fastnet/embed.go +++ b/systest/parameters/fastnet/embed.go @@ -9,3 +9,6 @@ var SmesherConfig string //go:embed "poet.conf" var PoetConfig string + +//go:embed "certifier.yaml" +var CertifierConfig string diff --git a/systest/parameters/fastnet/poet.conf b/systest/parameters/fastnet/poet.conf index 558fd5a254..ce3cc9c6f4 100644 --- a/systest/parameters/fastnet/poet.conf +++ b/systest/parameters/fastnet/poet.conf @@ -6,3 +6,5 @@ jsonlog="true" debuglog="true" pow-difficulty=4 + +metrics-port=8081 \ No newline at end of file diff --git a/systest/parameters/longfast/certifier.yaml b/systest/parameters/longfast/certifier.yaml new file mode 100644 index 0000000000..51d47896d6 --- /dev/null +++ b/systest/parameters/longfast/certifier.yaml @@ -0,0 +1,17 @@ +listen: "0.0.0.0:80" +# Same as fastnet +post_cfg: + k1: 12 + k2: 4 + k3: 4 + pow_difficulty: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +init_cfg: + min_num_units: 2 + max_num_units: 4 + labels_per_unit: 128 + scrypt: + n: 2 + r: 1 + p: 1 + +metrics: true \ No newline at end of file diff --git a/systest/testcontext/context.go b/systest/testcontext/context.go index e470f4e739..aaefe8e10d 100644 --- a/systest/testcontext/context.go +++ b/systest/testcontext/context.go @@ -73,6 +73,11 @@ var ( "bootstrapper image", "spacemeshos/spacemesh-dev-bs:2beaf443f", ) + certifierImage = parameters.String( + "certifier-image", + "certifier service image", + "spacemeshos/certifier-service:latest", + ) poetImage = parameters.String( "poet-image", "poet server image", @@ -157,6 +162,7 @@ type Context struct { Namespace string Image string BootstrapperImage string + CertifierImage string PoetImage string Storage struct { Size string @@ -335,6 +341,7 @@ func New(t *testing.T, opts ...Opt) *Context { BootstrapperSize: bsSize.Get(p), Image: imageFlag.Get(p), BootstrapperImage: bsImage.Get(p), + CertifierImage: certifierImage.Get(p), PoetImage: poetImage.Get(p), NodeSelector: nodeSelector.Get(p), Log: zaptest.NewLogger(t, zaptest.Level(logLevel)).Sugar().Named(t.Name()), From d68a5629e9927a507201ddc6144ba46ef84b923f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 6 Nov 2023 12:39:29 +0100 Subject: [PATCH 03/55] Initial certification using existing ATX or initial POST --- activation/activation.go | 69 +++++++++++++++++++++++++++++++++-- activation/activation_test.go | 56 +++++++++++++++++++++++++++- activation/e2e/poet_test.go | 13 ++++++- sql/atxs/atxs.go | 8 ++-- 4 files changed, 135 insertions(+), 11 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 7ef2676c8f..15f5d9876e 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -385,15 +385,76 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { return nipost.AddInitialPost(b.localDB, b.signer.NodeID(), initialPost) } +// Obtain certificates for the poets. +// We want to certify immediately after the startup or creating the initial POST +// to avoid all nodes spamming the certifier at the same time when +// submitting to the poets. +// +// New nodes should call it after the initial POST is created. +func (b *Builder) certifyPost(ctx context.Context) { + post, meta, err := b.obtainPostForCertification() + if err != nil { + b.log.With().Error("failed to obtain post for certification", zap.Error(err)) + } + + client := NewCertifierClient(b.log, post, meta) + b.certifier = NewCertifier(b.nipostBuilder.DataDir(), b.log, client) + b.certifier.CertifyAll(ctx, b.poets) +} + +func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, error) { + var ( + post *types.Post + meta *types.PostInfo + ) + + post, _, err := nipost.InitialPost(b.localDB, b.signer.NodeID()) + switch { + case err == nil: + b.log.Info("certifying using the initial post") + // TODO fix metadata + return post, nil, nil + case errors.Is(err, sql.ErrNotFound): + // no initial post + default: + return nil, nil, fmt.Errorf("loading initial post from db: %w", err) + } + + b.log.Info("certifying using an existing ATX") + atxid, err := atxs.GetFirstIDByNodeID(b.cdb, b.SmesherID()) + if err != nil { + return nil, nil, fmt.Errorf("cannot certify - no existing ATX found: %w", err) + } + atx, err := b.cdb.GetFullAtx(atxid) + if err != nil { + return nil, nil, fmt.Errorf("cannot certify - failed to retrieve ATX: %w", err) + } + var commitmentAtx *types.ATXID + if commitmentAtx = atx.CommitmentATX; commitmentAtx == nil { + atx, err := atxs.CommitmentATX(b.cdb, b.SmesherID()) + if err != nil { + return nil, nil, fmt.Errorf("cannot determine own commitment ATX: %w", err) + } + commitmentAtx = &atx + } + post = atx.NIPost.Post + meta = &types.PostInfo{ + NodeID: b.SmesherID(), + CommitmentATX: *commitmentAtx, + Nonce: atx.VRFNonce, + NumUnits: atx.NumUnits, + LabelsPerUnit: atx.NIPost.PostMetadata.LabelsPerUnit, + } + + return post, meta, nil +} + func (b *Builder) run(ctx context.Context) { defer b.log.Info("atx builder stopped") for { err := b.buildInitialPost(ctx) if err == nil { - client := NewCertifierClient(b.log.Zap(), b.initialPost, b.initialPostInfo) - b.certifier = NewCertifier(b.nipostBuilder.DataDir(), b.log.Zap(), client) - b.certifier.CertifyAll(ctx, b.poets) break } b.log.Error("failed to generate initial proof:", zap.Error(err)) @@ -405,6 +466,8 @@ func (b *Builder) run(ctx context.Context) { } } + b.certifyPost(ctx) + for { err := b.PublishActivationTx(ctx) if err == nil { diff --git a/activation/activation_test.go b/activation/activation_test.go index 0108ea4cfe..0c035c476a 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -769,8 +769,8 @@ func TestBuilder_PublishActivationTx_NoPrevATX_PublishFails_InitialPost_preserve genesis := time.Now().Add(-time.Duration(currLayer) * layerDuration) return genesis.Add(layerDuration * time.Duration(got)) }).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ context.Context, challenge *types.NIPostChallenge) (*types.NIPost, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *types.NIPostChallenge, _ certifierService) (*types.NIPost, error) { return nil, ErrATXChallengeExpired }, ) @@ -999,6 +999,8 @@ func TestBuilder_PublishActivationTx_TargetsEpochBasedOnPosAtx(t *testing.T) { nipost.Post{Indices: make([]byte, 10)}, )) + tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge).Return(&types.Post{}, &types.PostInfo{}, nil) + r.NoError(tab.buildInitialPost(context.Background())) r.NoError(tab.PublishActivationTx(context.Background())) // state is cleaned up @@ -1250,6 +1252,56 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { require.NoError(t, tab.buildInitialPost(context.Background())) } +func TestBuilder_ObtainPostForCertification(t *testing.T) { + t.Run("no POST or ATX - fail", func(t *testing.T) { + tab := newTestBuilder(t) + _, _, err := tab.obtainPostForCertification() + require.Error(t, err) + }) + t.Run("initial POST available", func(t *testing.T) { + tab := newTestBuilder(t) + tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge).Return(&types.Post{}, &types.PostInfo{}, nil) + require.NoError(t, tab.buildInitialPost(context.Background())) + + _, _, err := tab.obtainPostForCertification() + require.NoError(t, err) + }) + t.Run("initial POST unavailable but ATX exists", func(t *testing.T) { + tab := newTestBuilder(t) + commitmentAtxId := types.RandomATXID() + challenge := types.NIPostChallenge{ + PublishEpoch: 2, + Sequence: 0, + PrevATXID: types.EmptyATXID, + PositioningATX: types.RandomATXID(), + CommitmentATX: &commitmentAtxId, + InitialPost: &types.Post{}, + } + nipost := newNIPostWithChallenge(t, types.HexToHash32("55555"), []byte("66666")) + nipost.Post = &types.Post{ + Nonce: 5, + Indices: []byte{1, 2, 3, 4}, + Pow: 7, + } + nipost.PostMetadata.LabelsPerUnit = 777 + atx := types.NewActivationTx(challenge, types.Address{}, nipost, 2, nil) + atx.SetEffectiveNumUnits(2) + atx.SetReceived(time.Now()) + SignAndFinalizeAtx(tab.sig, atx) + vAtx, err := atx.Verify(0, 1) + require.NoError(t, err) + require.NoError(t, atxs.Add(tab.cdb, vAtx)) + + post, meta, err := tab.obtainPostForCertification() + require.NoError(t, err) + require.Equal(t, commitmentAtxId, meta.CommitmentATX) + require.Equal(t, uint32(2), meta.NumUnits) + require.Equal(t, tab.sig.NodeID(), meta.NodeID) + require.Equal(t, uint64(777), meta.LabelsPerUnit) + require.Equal(t, nipost.Post, post) + }) +} + func TestBuilder_InitialPostIsPersisted(t *testing.T) { tab := newTestBuilder(t, WithPoetConfig(PoetConfig{PhaseShift: layerDuration * 4})) tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge). diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index 3ec4776b28..bfff8c8a59 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -3,6 +3,7 @@ package activation_test import ( "bytes" "context" + "crypto/ed25519" "errors" "net/url" "testing" @@ -97,7 +98,13 @@ func TestHTTPPoet(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - c, err := NewHTTPPoetTestHarness(ctx, poetDir) + + certPubKey, certPrivKey, err := ed25519.GenerateKey(nil) + r.NoError(err) + + c, err := NewHTTPPoetTestHarness(ctx, poetDir, WithCertifier(®istration.CertifierConfig{ + PubKey: registration.Base64Enc(certPubKey), + })) r.NoError(err) r.NotNil(c) @@ -127,7 +134,9 @@ func TestHTTPPoet(t *testing.T) { ch.Bytes(), signature, signer.NodeID(), - activation.PoetAuth{}, + activation.PoetAuth{ + PoetCert: &activation.PoetCert{Signature: ed25519.Sign(certPrivKey, signer.NodeID().Bytes())}, + }, ) r.NoError(err) r.NotNil(poetRound) diff --git a/sql/atxs/atxs.go b/sql/atxs/atxs.go index 2e4d548989..a873e60adf 100644 --- a/sql/atxs/atxs.go +++ b/sql/atxs/atxs.go @@ -127,7 +127,7 @@ func CommitmentATX(db sql.Executor, nodeID types.NodeID) (id types.ATXID, err er } if rows, err := db.Exec(` - select commitment_atx from atxs + select commitment_atx from atxs where pubkey = ?1 and commitment_atx is not null order by epoch desc limit 1;`, enc, dec); err != nil { @@ -150,7 +150,7 @@ func GetFirstIDByNodeID(db sql.Executor, nodeID types.NodeID) (id types.ATXID, e } if rows, err := db.Exec(` - select id from atxs + select id from atxs where pubkey = ?1 order by epoch asc limit 1;`, enc, dec); err != nil { @@ -173,7 +173,7 @@ func GetLastIDByNodeID(db sql.Executor, nodeID types.NodeID) (id types.ATXID, er } if rows, err := db.Exec(` - select id from atxs + select id from atxs where pubkey = ?1 order by epoch desc, received desc limit 1;`, enc, dec); err != nil { @@ -386,7 +386,7 @@ func LatestN(db sql.Executor, n int) ([]CheckpointAtx, error) { } if rows, err := db.Exec(` - select id, epoch, effective_num_units, base_tick_height, tick_count, pubkey, sequence, coinbase + select id, epoch, effective_num_units, base_tick_height, tick_count, pubkey, sequence, coinbase from ( select row_number() over (partition by pubkey order by epoch desc) RowNum, id, epoch, effective_num_units, base_tick_height, tick_count, pubkey, sequence, coinbase from atxs From 535dfcf64fb798a38e2df29f1fc66b1c387a5b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 6 Nov 2023 13:55:35 +0100 Subject: [PATCH 04/55] Improve poet HTTP client coverage --- activation/e2e/poet_test.go | 40 +++++++++++++++++++++++++------------ activation/poet.go | 6 ------ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index bfff8c8a59..2923f609af 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -127,19 +127,33 @@ func TestHTTPPoet(t *testing.T) { signature := signer.Sign(signing.POET, ch.Bytes()) prefix := bytes.Join([][]byte{signer.Prefix(), {byte(signing.POET)}}, nil) - poetRound, err := client.Submit( - context.Background(), - time.Time{}, - prefix, - ch.Bytes(), - signature, - signer.NodeID(), - activation.PoetAuth{ - PoetCert: &activation.PoetCert{Signature: ed25519.Sign(certPrivKey, signer.NodeID().Bytes())}, - }, - ) - r.NoError(err) - r.NotNil(poetRound) + t.Run("submit with cert", func(t *testing.T) { + poetRound, err := client.Submit( + context.Background(), + time.Time{}, + prefix, + ch.Bytes(), + signature, + signer.NodeID(), + activation.PoetAuth{ + PoetCert: &activation.PoetCert{Signature: ed25519.Sign(certPrivKey, signer.NodeID().Bytes())}, + }, + ) + require.NoError(t, err) + require.NotNil(t, poetRound) + }) + t.Run("return proper error code on rejected cert", func(t *testing.T) { + _, err := client.Submit( + context.Background(), + time.Time{}, + prefix, + ch.Bytes(), + signature, + signer.NodeID(), + activation.PoetAuth{PoetCert: &activation.PoetCert{Signature: []byte("oops")}}, + ) + require.ErrorIs(t, err, activation.ErrUnathorized) + }) } func TestSubmitTooLate(t *testing.T) { diff --git a/activation/poet.go b/activation/poet.go index 4b3f8366ce..2af9817a71 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -21,8 +21,6 @@ import ( ) var ( - ErrNotFound = errors.New("not found") - ErrUnavailable = errors.New("unavailable") ErrInvalidRequest = errors.New("invalid request") ErrUnathorized = errors.New("unauthorized") ) @@ -300,10 +298,6 @@ func (c *HTTPPoetClient) req(ctx context.Context, method, path string, reqBody, switch res.StatusCode { case http.StatusOK: - case http.StatusNotFound: - return fmt.Errorf("%w: response status code: %s, body: %s", ErrNotFound, res.Status, string(data)) - case http.StatusServiceUnavailable: - return fmt.Errorf("%w: response status code: %s, body: %s", ErrUnavailable, res.Status, string(data)) case http.StatusBadRequest: return fmt.Errorf("%w: response status code: %s, body: %s", ErrInvalidRequest, res.Status, string(data)) case http.StatusUnauthorized: From 513c29a1ab039160147c894465a0df62b090c100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 6 Nov 2023 16:19:07 +0100 Subject: [PATCH 05/55] Bump certifier --- systest/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systest/Makefile b/systest/Makefile index 877f37087a..c04cfc1995 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,7 +5,7 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:58d4d17566b4353580a6f70f182523f06538ce01 +certifier_image ?= spacemeshos/certifier-service:4d14816c95920e0d459f2b04400bc949fb78c1de poet_image ?= spacemeshos/poet:v0.10.0-rc2 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) From 2bf4807f44fe9ededdb9e0c496171ff0d00cee6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 8 Nov 2023 19:36:31 +0700 Subject: [PATCH 06/55] Use the right post challenge to certify --- activation/activation.go | 18 +++++++++--------- activation/activation_test.go | 24 +++++++++++++++--------- activation/certifier.go | 12 +++++++++--- activation/e2e/certifier_client_test.go | 2 +- activation/e2e/nipost_test.go | 2 +- activation/e2e/validation_test.go | 2 +- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 15f5d9876e..2b47706948 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -392,17 +392,17 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { // // New nodes should call it after the initial POST is created. func (b *Builder) certifyPost(ctx context.Context) { - post, meta, err := b.obtainPostForCertification() + post, meta, ch, err := b.obtainPostForCertification() if err != nil { b.log.With().Error("failed to obtain post for certification", zap.Error(err)) } - client := NewCertifierClient(b.log, post, meta) + client := NewCertifierClient(b.log, post, meta, ch) b.certifier = NewCertifier(b.nipostBuilder.DataDir(), b.log, client) b.certifier.CertifyAll(ctx, b.poets) } -func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, error) { +func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, []byte, error) { var ( post *types.Post meta *types.PostInfo @@ -413,27 +413,27 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, er case err == nil: b.log.Info("certifying using the initial post") // TODO fix metadata - return post, nil, nil + return post, nil, shared.ZeroChallenge, nil case errors.Is(err, sql.ErrNotFound): // no initial post default: - return nil, nil, fmt.Errorf("loading initial post from db: %w", err) + return nil, nil, nil, fmt.Errorf("loading initial post from db: %w", err) } b.log.Info("certifying using an existing ATX") atxid, err := atxs.GetFirstIDByNodeID(b.cdb, b.SmesherID()) if err != nil { - return nil, nil, fmt.Errorf("cannot certify - no existing ATX found: %w", err) + return nil, nil, nil, fmt.Errorf("cannot certify - no existing ATX found: %w", err) } atx, err := b.cdb.GetFullAtx(atxid) if err != nil { - return nil, nil, fmt.Errorf("cannot certify - failed to retrieve ATX: %w", err) + return nil, nil, nil, fmt.Errorf("cannot certify - failed to retrieve ATX: %w", err) } var commitmentAtx *types.ATXID if commitmentAtx = atx.CommitmentATX; commitmentAtx == nil { atx, err := atxs.CommitmentATX(b.cdb, b.SmesherID()) if err != nil { - return nil, nil, fmt.Errorf("cannot determine own commitment ATX: %w", err) + return nil, nil, nil, fmt.Errorf("cannot determine own commitment ATX: %w", err) } commitmentAtx = &atx } @@ -446,7 +446,7 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, er LabelsPerUnit: atx.NIPost.PostMetadata.LabelsPerUnit, } - return post, meta, nil + return post, meta, atx.NIPost.PostMetadata.Challenge, nil } func (b *Builder) run(ctx context.Context) { diff --git a/activation/activation_test.go b/activation/activation_test.go index 0c035c476a..b3a8cef0ad 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1255,7 +1255,7 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { func TestBuilder_ObtainPostForCertification(t *testing.T) { t.Run("no POST or ATX - fail", func(t *testing.T) { tab := newTestBuilder(t) - _, _, err := tab.obtainPostForCertification() + _, _, _, err := tab.obtainPostForCertification() require.Error(t, err) }) t.Run("initial POST available", func(t *testing.T) { @@ -1263,8 +1263,9 @@ func TestBuilder_ObtainPostForCertification(t *testing.T) { tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge).Return(&types.Post{}, &types.PostInfo{}, nil) require.NoError(t, tab.buildInitialPost(context.Background())) - _, _, err := tab.obtainPostForCertification() + _, _, ch, err := tab.obtainPostForCertification() require.NoError(t, err) + require.EqualValues(t, shared.ZeroChallenge, ch) }) t.Run("initial POST unavailable but ATX exists", func(t *testing.T) { tab := newTestBuilder(t) @@ -1277,13 +1278,17 @@ func TestBuilder_ObtainPostForCertification(t *testing.T) { CommitmentATX: &commitmentAtxId, InitialPost: &types.Post{}, } - nipost := newNIPostWithChallenge(t, types.HexToHash32("55555"), []byte("66666")) - nipost.Post = &types.Post{ - Nonce: 5, - Indices: []byte{1, 2, 3, 4}, - Pow: 7, + nipost := &types.NIPost{ + Post: &types.Post{ + Nonce: 5, + Indices: []byte{1, 2, 3, 4}, + Pow: 7, + }, + PostMetadata: &types.PostMetadata{ + Challenge: []byte("66666"), + LabelsPerUnit: 777, + }, } - nipost.PostMetadata.LabelsPerUnit = 777 atx := types.NewActivationTx(challenge, types.Address{}, nipost, 2, nil) atx.SetEffectiveNumUnits(2) atx.SetReceived(time.Now()) @@ -1292,12 +1297,13 @@ func TestBuilder_ObtainPostForCertification(t *testing.T) { require.NoError(t, err) require.NoError(t, atxs.Add(tab.cdb, vAtx)) - post, meta, err := tab.obtainPostForCertification() + post, meta, ch, err := tab.obtainPostForCertification() require.NoError(t, err) require.Equal(t, commitmentAtxId, meta.CommitmentATX) require.Equal(t, uint32(2), meta.NumUnits) require.Equal(t, tab.sig.NodeID(), meta.NodeID) require.Equal(t, uint64(777), meta.LabelsPerUnit) + require.Equal(t, nipost.PostMetadata.Challenge, ch) require.Equal(t, nipost.Post, post) }) } diff --git a/activation/certifier.go b/activation/certifier.go index 1014c9f1f8..a17719ec39 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/go-retryablehttp" "github.com/natefinch/atomic" "github.com/sourcegraph/conc/iter" - "github.com/spacemeshos/post/shared" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -178,15 +177,22 @@ type CertifierClient struct { client *retryablehttp.Client post *types.Post postInfo *types.PostInfo + postCh []byte logger *zap.Logger } -func NewCertifierClient(logger *zap.Logger, post *types.Post, postInfo *types.PostInfo) *CertifierClient { +func NewCertifierClient( + logger *zap.Logger, + post *types.Post, + postInfo *types.PostInfo, + postCh []byte, +) *CertifierClient { c := &CertifierClient{ client: retryablehttp.NewClient(), logger: logger, post: post, postInfo: postInfo, + postCh: postCh, } c.client.Logger = &retryableHttpLogger{logger} c.client.ResponseLogHook = func(logger retryablehttp.Logger, resp *http.Response) { @@ -208,7 +214,7 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by CommitmentAtxId: c.postInfo.CommitmentATX[:], LabelsPerUnit: c.postInfo.LabelsPerUnit, NumUnits: c.postInfo.NumUnits, - Challenge: shared.ZeroChallenge, + Challenge: c.postCh, }, } diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index ca3872b01f..284d896c12 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -92,7 +92,7 @@ func TestCertification(t *testing.T) { require.NoError(t, err) poets = append(poets, client) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) certifier := activation.NewCertifier(t.TempDir(), zaptest.NewLogger(t), certifierClient) certs := certifier.CertifyAll(context.Background(), poets) require.Len(t, certs, 3) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index a98784bf83..064857808f 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -200,7 +200,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { ) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) certifier := activation.NewCertifier(t.TempDir(), logger, certifierClient) certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index 65dc5271bd..9ce58261fd 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -109,7 +109,7 @@ func TestValidator_Validate(t *testing.T) { } challengeHash := challenge.Hash() - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) certifier := activation.NewCertifier(t.TempDir(), logger, certifierClient) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) From 54f915bc8cfafe719e13bf8d6ddf2cecc1d8f9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 9 Nov 2023 14:00:55 +0700 Subject: [PATCH 07/55] Fixed POST metadata sent to certifier --- activation/activation.go | 26 ++++++++++++++----------- activation/activation_test.go | 6 +++--- activation/certifier.go | 6 ++---- activation/e2e/certifier_client_test.go | 4 +++- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 2b47706948..56f41d24d7 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -403,17 +403,22 @@ func (b *Builder) certifyPost(ctx context.Context) { } func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, []byte, error) { - var ( - post *types.Post - meta *types.PostInfo - ) - - post, _, err := nipost.InitialPost(b.localDB, b.signer.NodeID()) + post, err := nipost.InitialPost(b.localDB, b.signer.NodeID()) switch { case err == nil: b.log.Info("certifying using the initial post") - // TODO fix metadata - return post, nil, shared.ZeroChallenge, nil + meta := &types.PostInfo{ + NodeID: b.SmesherID(), + CommitmentATX: post.CommitmentATX, + Nonce: &post.VRFNonce, + NumUnits: post.NumUnits, + } + post := &types.Post{ + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + } + return post, meta, shared.ZeroChallenge, nil case errors.Is(err, sql.ErrNotFound): // no initial post default: @@ -437,8 +442,7 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, [] } commitmentAtx = &atx } - post = atx.NIPost.Post - meta = &types.PostInfo{ + meta := &types.PostInfo{ NodeID: b.SmesherID(), CommitmentATX: *commitmentAtx, Nonce: atx.VRFNonce, @@ -446,7 +450,7 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, [] LabelsPerUnit: atx.NIPost.PostMetadata.LabelsPerUnit, } - return post, meta, atx.NIPost.PostMetadata.Challenge, nil + return atx.NIPost.Post, meta, atx.NIPost.PostMetadata.Challenge, nil } func (b *Builder) run(ctx context.Context) { diff --git a/activation/activation_test.go b/activation/activation_test.go index b3a8cef0ad..2dbe1e13a1 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -999,8 +999,6 @@ func TestBuilder_PublishActivationTx_TargetsEpochBasedOnPosAtx(t *testing.T) { nipost.Post{Indices: make([]byte, 10)}, )) - tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge).Return(&types.Post{}, &types.PostInfo{}, nil) - r.NoError(tab.buildInitialPost(context.Background())) r.NoError(tab.PublishActivationTx(context.Background())) // state is cleaned up @@ -1260,7 +1258,9 @@ func TestBuilder_ObtainPostForCertification(t *testing.T) { }) t.Run("initial POST available", func(t *testing.T) { tab := newTestBuilder(t) - tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge).Return(&types.Post{}, &types.PostInfo{}, nil) + tab.mpostClient.EXPECT(). + Proof(gomock.Any(), shared.ZeroChallenge). + Return(&types.Post{Indices: []byte{1, 2, 3}}, &types.PostInfo{Nonce: new(types.VRFPostIndex)}, nil) require.NoError(t, tab.buildInitialPost(context.Background())) _, _, ch, err := tab.obtainPostForCertification() diff --git a/activation/certifier.go b/activation/certifier.go index a17719ec39..e89ef59d6c 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -33,9 +33,8 @@ type ProoToCertifyfMetadata struct { NodeId []byte `json:"node_id"` CommitmentAtxId []byte `json:"commitment_atx_id"` - Challenge []byte `json:"challenge"` - NumUnits uint32 `json:"num_units"` - LabelsPerUnit uint64 `json:"labels_per_unit"` + Challenge []byte `json:"challenge"` + NumUnits uint32 `json:"num_units"` } type CertifyRequest struct { @@ -212,7 +211,6 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by Metadata: ProoToCertifyfMetadata{ NodeId: c.postInfo.NodeID[:], CommitmentAtxId: c.postInfo.CommitmentATX[:], - LabelsPerUnit: c.postInfo.LabelsPerUnit, NumUnits: c.postInfo.NumUnits, Challenge: c.postCh, }, diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 284d896c12..d0e71edefb 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -107,6 +107,7 @@ type testCertifier struct { privKey ed25519.PrivateKey postVerifier activation.PostVerifier opts []verifying.OptionFunc + cfg activation.PostConfig } func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { @@ -127,7 +128,7 @@ func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { CommitmentAtxId: req.Metadata.CommitmentAtxId, Challenge: req.Metadata.Challenge, NumUnits: req.Metadata.NumUnits, - LabelsPerUnit: req.Metadata.LabelsPerUnit, + LabelsPerUnit: c.cfg.LabelsPerUnit, } if err := c.postVerifier.Verify(context.Background(), proof, metadata, c.opts...); err != nil { http.Error(w, fmt.Sprintf("verifying POST: %v", err), http.StatusBadRequest) @@ -169,6 +170,7 @@ func spawnTestCertifier( privKey: private, postVerifier: postVerifier, opts: opts, + cfg: cfg, } mux := http.NewServeMux() From 6cc3971bcde13f7f2a655e659eb7b9f84516efad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 9 Nov 2023 14:38:58 +0700 Subject: [PATCH 08/55] Bump certifier image --- systest/Makefile | 2 +- systest/parameters/bignet/certifier.yaml | 3 +-- systest/parameters/fastnet/certifier.yaml | 3 +-- systest/parameters/longfast/certifier.yaml | 3 +-- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/systest/Makefile b/systest/Makefile index c04cfc1995..2b9df3df15 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,7 +5,7 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:4d14816c95920e0d459f2b04400bc949fb78c1de +certifier_image ?= spacemeshos/certifier-service:bdbae9e09190a91d130fa6739493e391e0403a98 poet_image ?= spacemeshos/poet:v0.10.0-rc2 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) diff --git a/systest/parameters/bignet/certifier.yaml b/systest/parameters/bignet/certifier.yaml index 51d47896d6..2190dbd3c8 100644 --- a/systest/parameters/bignet/certifier.yaml +++ b/systest/parameters/bignet/certifier.yaml @@ -1,4 +1,5 @@ listen: "0.0.0.0:80" +metrics: "0.0.0.0:2112" # Same as fastnet post_cfg: k1: 12 @@ -13,5 +14,3 @@ init_cfg: n: 2 r: 1 p: 1 - -metrics: true \ No newline at end of file diff --git a/systest/parameters/fastnet/certifier.yaml b/systest/parameters/fastnet/certifier.yaml index 51d47896d6..2190dbd3c8 100644 --- a/systest/parameters/fastnet/certifier.yaml +++ b/systest/parameters/fastnet/certifier.yaml @@ -1,4 +1,5 @@ listen: "0.0.0.0:80" +metrics: "0.0.0.0:2112" # Same as fastnet post_cfg: k1: 12 @@ -13,5 +14,3 @@ init_cfg: n: 2 r: 1 p: 1 - -metrics: true \ No newline at end of file diff --git a/systest/parameters/longfast/certifier.yaml b/systest/parameters/longfast/certifier.yaml index 51d47896d6..2190dbd3c8 100644 --- a/systest/parameters/longfast/certifier.yaml +++ b/systest/parameters/longfast/certifier.yaml @@ -1,4 +1,5 @@ listen: "0.0.0.0:80" +metrics: "0.0.0.0:2112" # Same as fastnet post_cfg: k1: 12 @@ -13,5 +14,3 @@ init_cfg: n: 2 r: 1 p: 1 - -metrics: true \ No newline at end of file From f79232cdd32989b319b67289647c2ef8ac8b9d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 9 Nov 2023 18:00:42 +0700 Subject: [PATCH 09/55] Migrate certifier store to localdb --- activation/activation.go | 2 +- activation/certifier.go | 100 ++++++------------------ activation/certifier_test.go | 10 ++- activation/e2e/certifier_client_test.go | 3 +- activation/e2e/nipost_test.go | 3 +- activation/e2e/validation_test.go | 3 +- activation/interface.go | 7 ++ activation/mocks.go | 38 +++++++++ sql/localsql/certifier/db.go | 47 +++++++++++ sql/migrations/local/0002_next.sql | 7 ++ 10 files changed, 135 insertions(+), 85 deletions(-) create mode 100644 sql/localsql/certifier/db.go diff --git a/activation/activation.go b/activation/activation.go index 56f41d24d7..607059798b 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -398,7 +398,7 @@ func (b *Builder) certifyPost(ctx context.Context) { } client := NewCertifierClient(b.log, post, meta, ch) - b.certifier = NewCertifier(b.nipostBuilder.DataDir(), b.log, client) + b.certifier = NewCertifier(b.localDB, b.log, client) b.certifier.CertifyAll(ctx, b.poets) } diff --git a/activation/certifier.go b/activation/certifier.go index e89ef59d6c..01b6348c80 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -10,17 +10,15 @@ import ( "io" "net/http" "net/url" - "os" - "path/filepath" - "sync" "github.com/hashicorp/go-retryablehttp" - "github.com/natefinch/atomic" "github.com/sourcegraph/conc/iter" "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/localsql" + certifier_db "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) type ProofToCertify struct { @@ -49,21 +47,25 @@ type CertifyResponse struct { type Certifier struct { logger *zap.Logger - store *certificateStore + db *localsql.Database client certifierClient } -func NewCertifier(datadir string, logger *zap.Logger, client certifierClient) *Certifier { +func NewCertifier(db *localsql.Database, logger *zap.Logger, client certifierClient) *Certifier { return &Certifier{ client: client, logger: logger, - store: openCertificateStore(datadir, logger), + db: db, } } func (c *Certifier) GetCertificate(poet string) *PoetCert { - if cert, ok := c.store.get(poet); ok { - return &cert + cert, err := certifier_db.Certificate(c.db, c.client.Id(), poet) + switch { + case err == nil: + return &PoetCert{Signature: cert} + case !errors.Is(err, sql.ErrNotFound): + c.logger.Warn("failed to get certificate", zap.Error(err)) } return nil } @@ -77,9 +79,9 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, if err != nil { return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), info.URL, err) } - c.store.put(poet.Address(), *cert) - if err := c.store.persist(); err != nil { - c.logger.Warn("failed to persist poet certs", zap.Error(err)) + + if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Signature, poet.Address()); err != nil { + c.logger.Warn("failed to persist poet cert", zap.Error(err)) } return cert, nil @@ -145,12 +147,7 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri c.logger.Info( "certifying for poets", zap.Stringer("certifier", svc.URL), - zap.Array("poets", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { - for _, poet := range svc.poets { - enc.AppendString(poet) - } - return nil - })), + zap.Strings("poets", svc.poets), ) cert, err := c.client.Certify(ctx, svc.URL, svc.PubKey) @@ -164,11 +161,12 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri zap.Binary("cert", cert.Signature), ) for _, poet := range svc.poets { - c.store.put(poet, *cert) + if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Signature, poet); err != nil { + c.logger.Warn("failed to persist poet cert", zap.Error(err)) + } certs[poet] = *cert } } - c.store.persist() return certs } @@ -201,6 +199,10 @@ func NewCertifierClient( return c } +func (c *CertifierClient) Id() types.NodeID { + return c.postInfo.NodeID +} + func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { request := CertifyRequest{ Proof: ProofToCertify{ @@ -253,59 +255,3 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by } return &PoetCert{Signature: certRespose.Signature}, nil } - -type certificateStore struct { - // protects certs map - mu sync.RWMutex - // map of certifier public key to PoET certificate - certs map[string]PoetCert - dir string -} - -func openCertificateStore(dir string, logger *zap.Logger) *certificateStore { - store := &certificateStore{ - dir: dir, - certs: map[string]PoetCert{}, - } - - file, err := os.Open(filepath.Join(dir, "poet_certs.json")) - switch { - case errors.Is(err, os.ErrNotExist): - return store - case err != nil: - logger.Warn("failed to open poet certs file", zap.Error(err)) - return store - } - certs := make(map[string]PoetCert) - if err := json.NewDecoder(file).Decode(&certs); err != nil { - logger.Warn("failed to decode poet certs", zap.Error(err)) - return store - } - return &certificateStore{ - certs: certs, - dir: dir, - } -} - -func (s *certificateStore) get(poet string) (PoetCert, bool) { - s.mu.RLock() - defer s.mu.RUnlock() - cert, ok := s.certs[poet] - return cert, ok -} - -func (s *certificateStore) put(poet string, cert PoetCert) { - s.mu.Lock() - defer s.mu.Unlock() - s.certs[poet] = cert -} - -func (s *certificateStore) persist() error { - s.mu.RLock() - defer s.mu.RUnlock() - buf := &bytes.Buffer{} - if err := json.NewEncoder(buf).Encode(s.certs); err != nil { - return err - } - return atomic.WriteFile(filepath.Join(s.dir, "poet_certs.json"), buf) -} diff --git a/activation/certifier_test.go b/activation/certifier_test.go index e0ca0920a8..73f93ebc3b 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -10,14 +10,16 @@ import ( "go.uber.org/zap/zaptest" "github.com/spacemeshos/go-spacemesh/activation" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql/localsql" ) func TestPersistsCerts(t *testing.T) { client := activation.NewMockcertifierClient(gomock.NewController(t)) - datadir := t.TempDir() - + client.EXPECT().Id().AnyTimes().Return(types.RandomNodeID()) + db := localsql.InMemory() { - certifier := activation.NewCertifier(datadir, zaptest.NewLogger(t), client) + certifier := activation.NewCertifier(db, zaptest.NewLogger(t), client) poetMock := activation.NewMockPoetClient(gomock.NewController(t)) poetMock.EXPECT().Address().Return("http://poet") @@ -41,7 +43,7 @@ func TestPersistsCerts(t *testing.T) { } { // Create new certifier and check that it loads the certs back. - certifier := activation.NewCertifier(datadir, zaptest.NewLogger(t), client) + certifier := activation.NewCertifier(db, zaptest.NewLogger(t), client) cert := certifier.GetCertificate("http://poet") require.Equal(t, []byte("cert"), cert.Signature) } diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index d0e71edefb..80e999b4b4 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -25,6 +25,7 @@ import ( "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/localsql" ) func TestCertification(t *testing.T) { @@ -93,7 +94,7 @@ func TestCertification(t *testing.T) { poets = append(poets, client) certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) - certifier := activation.NewCertifier(t.TempDir(), zaptest.NewLogger(t), certifierClient) + certifier := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), certifierClient) certs := certifier.CertifyAll(context.Background(), poets) require.Len(t, certs, 3) require.Contains(t, certs, poets[0].Address()) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 064857808f..d0652c8bb6 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -27,6 +27,7 @@ import ( "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/localsql" ) const ( @@ -201,7 +202,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { require.NoError(t, err) certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) - certifier := activation.NewCertifier(t.TempDir(), logger, certifierClient) + certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) nb, err := activation.NewNIPostBuilder( diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index 9ce58261fd..cf998bf830 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -20,6 +20,7 @@ import ( "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/localsql" ) func TestValidator_Validate(t *testing.T) { @@ -110,7 +111,7 @@ func TestValidator_Validate(t *testing.T) { challengeHash := challenge.Hash() certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) - certifier := activation.NewCertifier(t.TempDir(), logger, certifierClient) + certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) diff --git a/activation/interface.go b/activation/interface.go index 8e36acf937..f7b869e1a1 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -136,11 +136,18 @@ type CertifierInfo struct { PubKey []byte } +// A certifier client that the certifierService uses to obtain certificates +// The implementation can use any method to obtain the certificate, +// for example, POST verification. type certifierClient interface { + // The ID for which this client certifies. + Id() types.NodeID + // Certify the ID in the given certifier. Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) } // certifierService is used to certify nodeID for registerting in the poet. +// It holds the certificates and can recertify if needed. type certifierService interface { // Acquire a certificate for the given poet. GetCertificate(poet string) *PoetCert diff --git a/activation/mocks.go b/activation/mocks.go index bf8ce850f9..f9ea5b4c77 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1614,6 +1614,44 @@ func (c *certifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.UR return c } +// Id mocks base method. +func (m *MockcertifierClient) Id() types.NodeID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Id") + ret0, _ := ret[0].(types.NodeID) + return ret0 +} + +// Id indicates an expected call of Id. +func (mr *MockcertifierClientMockRecorder) Id() *certifierClientIdCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockcertifierClient)(nil).Id)) + return &certifierClientIdCall{Call: call} +} + +// certifierClientIdCall wrap *gomock.Call +type certifierClientIdCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *certifierClientIdCall) Return(arg0 types.NodeID) *certifierClientIdCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *certifierClientIdCall) Do(f func() types.NodeID) *certifierClientIdCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *certifierClientIdCall) DoAndReturn(f func() types.NodeID) *certifierClientIdCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // MockcertifierService is a mock of certifierService interface. type MockcertifierService struct { ctrl *gomock.Controller diff --git a/sql/localsql/certifier/db.go b/sql/localsql/certifier/db.go new file mode 100644 index 0000000000..ced108ad2c --- /dev/null +++ b/sql/localsql/certifier/db.go @@ -0,0 +1,47 @@ +package certifier + +import ( + "fmt" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql" +) + +func AddCertificate(db sql.Executor, nodeID types.NodeID, cert []byte, poetUrl string) error { + enc := func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + stmt.BindBytes(2, []byte(poetUrl)) + stmt.BindBytes(3, cert) + } + if _, err := db.Exec(` + insert into poet_certificates (node_id, poet_url, certificate) + values (?1, ?2, ?3);`, enc, nil, + ); err != nil { + return fmt.Errorf("storing poet certificate for (%s; %s): %w", nodeID.ShortString(), poetUrl, err) + } + return nil +} + +func Certificate(db sql.Executor, nodeID types.NodeID, poetUrl string) ([]byte, error) { + enc := func(stmt *sql.Statement) { + stmt.BindBytes(1, nodeID.Bytes()) + stmt.BindBytes(2, []byte(poetUrl)) + } + var cert []byte + dec := func(stmt *sql.Statement) bool { + cert = make([]byte, stmt.ColumnLen(0)) + stmt.ColumnBytes(0, cert) + return true + } + rows, err := db.Exec(` + select certificate + from poet_certificates where node_id = ?1 and poet_url = ?2 limit 1;`, enc, dec, + ) + switch { + case err != nil: + return nil, fmt.Errorf("getting poet certificate for (%s; %s): %w", nodeID.ShortString(), poetUrl, err) + case rows == 0: + return nil, sql.ErrNotFound + } + return cert, nil +} diff --git a/sql/migrations/local/0002_next.sql b/sql/migrations/local/0002_next.sql index 00469ce7cd..b3feed4e05 100644 --- a/sql/migrations/local/0002_next.sql +++ b/sql/migrations/local/0002_next.sql @@ -7,3 +7,10 @@ ALTER TABLE initial_post RENAME COLUMN commit_atx_new TO commit_atx; ALTER TABLE initial_post ADD COLUMN num_units UNSIGNED INT NOT NULL; ALTER TABLE initial_post ADD COLUMN vrf_nonce UNSIGNED LONG INT NOT NULL; + +CREATE TABLE poet_certificates +( + node_id CHAR(32), + poet_url VARCHAR NOT NULL, + certificate VARCHAR NOT NULL +); \ No newline at end of file From a2893dcf6995b7632574c6dbc925a2c6638f4f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 9 Nov 2023 18:03:35 +0700 Subject: [PATCH 10/55] Remove CertifierInfo struct --- activation/certifier.go | 40 ++++++++++++++++++++---------------- activation/certifier_test.go | 7 +++---- activation/e2e/poet_test.go | 8 ++++---- activation/interface.go | 7 +------ activation/mocks.go | 17 +++++++-------- activation/poet.go | 13 +++++------- 6 files changed, 44 insertions(+), 48 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index 01b6348c80..905eae5b6f 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -71,13 +71,13 @@ func (c *Certifier) GetCertificate(poet string) *PoetCert { } func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) { - info, err := poet.CertifierInfo(ctx) + url, pubkey, err := poet.CertifierInfo(ctx) if err != nil { return nil, fmt.Errorf("querying certifier info: %w", err) } - cert, err := c.client.Certify(ctx, info.URL, info.PubKey) + cert, err := c.client.Certify(ctx, url, pubkey) if err != nil { - return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), info.URL, err) + return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), url, err) } if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Signature, poet.Address()); err != nil { @@ -106,26 +106,29 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri } type certInfo struct { - CertifierInfo - poet string + url *url.URL + pubkey []byte + poet string } certifierInfos := iter.Map(poetsToCertify, func(p *PoetClient) *certInfo { poet := *p - info, err := poet.CertifierInfo(ctx) + url, pubkey, err := poet.CertifierInfo(ctx) if err != nil { c.logger.Warn("failed to query for certifier info", zap.Error(err), zap.String("poet", poet.Address())) return nil } return &certInfo{ - CertifierInfo: *info, - poet: poet.Address(), + url: url, + pubkey: pubkey, + poet: poet.Address(), } }) type certService struct { - CertifierInfo - poets []string + url *url.URL + pubkey []byte + poets []string } certSvcs := make(map[string]*certService) for _, info := range certifierInfos { @@ -133,10 +136,11 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri continue } - if svc, ok := certSvcs[string(info.PubKey)]; !ok { - certSvcs[string(info.PubKey)] = &certService{ - CertifierInfo: info.CertifierInfo, - poets: []string{info.poet}, + if svc, ok := certSvcs[string(info.pubkey)]; !ok { + certSvcs[string(info.pubkey)] = &certService{ + url: info.url, + pubkey: info.pubkey, + poets: []string{info.poet}, } } else { svc.poets = append(svc.poets, info.poet) @@ -146,18 +150,18 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri for _, svc := range certSvcs { c.logger.Info( "certifying for poets", - zap.Stringer("certifier", svc.URL), + zap.Stringer("certifier", svc.url), zap.Strings("poets", svc.poets), ) - cert, err := c.client.Certify(ctx, svc.URL, svc.PubKey) + cert, err := c.client.Certify(ctx, svc.url, svc.pubkey) if err != nil { - c.logger.Warn("failed to certify", zap.Error(err), zap.Stringer("certifier", svc.URL)) + c.logger.Warn("failed to certify", zap.Error(err), zap.Stringer("certifier", svc.url)) continue } c.logger.Info( "successfully obtained certificate", - zap.Stringer("certifier", svc.URL), + zap.Stringer("certifier", svc.url), zap.Binary("cert", cert.Signature), ) for _, poet := range svc.poets { diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 73f93ebc3b..cef97a7f5f 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -23,10 +23,9 @@ func TestPersistsCerts(t *testing.T) { poetMock := activation.NewMockPoetClient(gomock.NewController(t)) poetMock.EXPECT().Address().Return("http://poet") - poetMock.EXPECT().CertifierInfo(gomock.Any()).Return(&activation.CertifierInfo{ - URL: &url.URL{Scheme: "http", Host: "certifier.org"}, - PubKey: []byte("pubkey"), - }, nil) + poetMock.EXPECT(). + CertifierInfo(gomock.Any()). + Return(&url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey"), nil) client.EXPECT(). Certify(gomock.Any(), &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index 2923f609af..88e99c5ac1 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -230,10 +230,10 @@ func TestCertifierInfo(t *testing.T) { ) require.NoError(t, err) - info, err := client.CertifierInfo(context.Background()) + url, pubkey, err := client.CertifierInfo(context.Background()) r.NoError(err) - r.Equal("http://localhost:8080", info.URL.String()) - r.Equal([]byte("pubkey"), info.PubKey) + r.Equal("http://localhost:8080", url.String()) + r.Equal([]byte("pubkey"), pubkey) } func TestNoCertifierInfo(t *testing.T) { @@ -262,6 +262,6 @@ func TestNoCertifierInfo(t *testing.T) { ) require.NoError(t, err) - _, err = client.CertifierInfo(context.Background()) + _, _, err = client.CertifierInfo(context.Background()) r.ErrorContains(err, "poet doesn't support certifier") } diff --git a/activation/interface.go b/activation/interface.go index f7b869e1a1..d2c70bdc70 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -125,17 +125,12 @@ type PoetClient interface { // PoetServiceID returns the public key of the PoET proving service. PoetServiceID(context.Context) (types.PoetServiceID, error) - CertifierInfo(context.Context) (*CertifierInfo, error) + CertifierInfo(context.Context) (*url.URL, []byte, error) // Proof returns the proof for the given round ID. Proof(ctx context.Context, roundID string) (*types.PoetProofMessage, []types.Member, error) } -type CertifierInfo struct { - URL *url.URL - PubKey []byte -} - // A certifier client that the certifierService uses to obtain certificates // The implementation can use any method to obtain the certificate, // for example, POST verification. diff --git a/activation/mocks.go b/activation/mocks.go index f9ea5b4c77..c97d6f5505 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1357,12 +1357,13 @@ func (c *PoetClientAddressCall) DoAndReturn(f func() string) *PoetClientAddressC } // CertifierInfo mocks base method. -func (m *MockPoetClient) CertifierInfo(arg0 context.Context) (*CertifierInfo, error) { +func (m *MockPoetClient) CertifierInfo(arg0 context.Context) (*url.URL, []byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CertifierInfo", arg0) - ret0, _ := ret[0].(*CertifierInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret0, _ := ret[0].(*url.URL) + ret1, _ := ret[1].([]byte) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // CertifierInfo indicates an expected call of CertifierInfo. @@ -1378,19 +1379,19 @@ type PoetClientCertifierInfoCall struct { } // Return rewrite *gomock.Call.Return -func (c *PoetClientCertifierInfoCall) Return(arg0 *CertifierInfo, arg1 error) *PoetClientCertifierInfoCall { - c.Call = c.Call.Return(arg0, arg1) +func (c *PoetClientCertifierInfoCall) Return(arg0 *url.URL, arg1 []byte, arg2 error) *PoetClientCertifierInfoCall { + c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *PoetClientCertifierInfoCall) Do(f func(context.Context) (*CertifierInfo, error)) *PoetClientCertifierInfoCall { +func (c *PoetClientCertifierInfoCall) Do(f func(context.Context) (*url.URL, []byte, error)) *PoetClientCertifierInfoCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *PoetClientCertifierInfoCall) DoAndReturn(f func(context.Context) (*CertifierInfo, error)) *PoetClientCertifierInfoCall { +func (c *PoetClientCertifierInfoCall) DoAndReturn(f func(context.Context) (*url.URL, []byte, error)) *PoetClientCertifierInfoCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/poet.go b/activation/poet.go index 2af9817a71..0cbd933e73 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -148,23 +148,20 @@ func (c *HTTPPoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) }, nil } -func (c *HTTPPoetClient) CertifierInfo(ctx context.Context) (*CertifierInfo, error) { +func (c *HTTPPoetClient) CertifierInfo(ctx context.Context) (*url.URL, []byte, error) { info, err := c.info(ctx) if err != nil { - return nil, err + return nil, nil, err } certifierInfo := info.GetCertifier() if certifierInfo == nil { - return nil, errors.New("poet doesn't support certifier") + return nil, nil, errors.New("poet doesn't support certifier") } url, err := url.Parse(certifierInfo.Url) if err != nil { - return nil, fmt.Errorf("parsing certifier address: %w", err) + return nil, nil, fmt.Errorf("parsing certifier address: %w", err) } - return &CertifierInfo{ - PubKey: certifierInfo.Pubkey, - URL: url, - }, nil + return url, certifierInfo.Pubkey, nil } // Submit registers a challenge in the proving service current open round. From ffd77d0b3e2ea8893218ee33ce81399fff8196a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 10 Nov 2023 15:00:10 +0700 Subject: [PATCH 11/55] Add systest for poet registrations with PoW and cert --- systest/cluster/cluster.go | 13 +++++- systest/cluster/nodes.go | 9 ++++ systest/parameters/fastnet/poet.conf | 4 +- systest/tests/metrics.go | 65 +++++++++++++++++++++++++++ systest/tests/poets_test.go | 67 ++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 systest/tests/metrics.go diff --git a/systest/cluster/cluster.go b/systest/cluster/cluster.go index b19ea91473..c490d8f952 100644 --- a/systest/cluster/cluster.go +++ b/systest/cluster/cluster.go @@ -58,6 +58,10 @@ func MakePoetEndpoint(ith int) string { return fmt.Sprintf("http://%s:%d", createPoetIdentifier(ith), poetPort) } +func MakePoetMetricsEndpoint(testNamespace string, ith int) string { + return fmt.Sprintf("http://%s.%s:%d/metrics", createPoetIdentifier(ith), testNamespace, prometheusScrapePort) +} + func BootstrapperEndpoint(ith int) string { return fmt.Sprintf("http://%s:%d", createBootstrapperIdentifier(ith), bootstrapperPort) } @@ -444,11 +448,11 @@ func (c *Cluster) AddCertifier(cctx *testcontext.Context, privkey string) error // AddPoet spawns a single poet with the first available id. // Id is of form "poet-N", where N ∈ [0, ∞). -func (c *Cluster) AddPoet(cctx *testcontext.Context) error { +func (c *Cluster) AddPoet(cctx *testcontext.Context, flags ...DeploymentFlag) error { if err := c.persist(cctx); err != nil { return err } - flags := maps.Values(c.poetFlags) + flags = append(maps.Values(c.poetFlags), flags...) id := createPoetIdentifier(c.firstFreePoetId()) cctx.Log.Debugw("deploying poet", "id", id) @@ -626,6 +630,11 @@ func (c *Cluster) Bootnodes() int { return c.bootnodes } +// Smeshers returns number of the smeshers in the cluster. +func (c *Cluster) Smeshers() int { + return c.smeshers +} + // Total returns total number of clients. func (c *Cluster) Total() int { return len(c.clients) diff --git a/systest/cluster/nodes.go b/systest/cluster/nodes.go index b9c33c2533..de0775ebb4 100644 --- a/systest/cluster/nodes.go +++ b/systest/cluster/nodes.go @@ -277,6 +277,7 @@ func deployCertifierD(ctx *testcontext.Context, id, privkey string) (*NodeClient func deployPoetD(ctx *testcontext.Context, id string, flags ...DeploymentFlag) (*NodeClient, error) { args := []string{ "-c=" + configDir + attachedPoetConfig, + "--metrics-port=" + strconv.Itoa(prometheusScrapePort), } for _, flag := range flags { args = append(args, flag.Flag()) @@ -292,6 +293,12 @@ func deployPoetD(ctx *testcontext.Context, id string, flags ...DeploymentFlag) ( WithReplicas(1). WithTemplate(corev1.PodTemplateSpec(). WithLabels(labels). + WithAnnotations( + map[string]string{ + "prometheus.io/port": strconv.Itoa(prometheusScrapePort), + "prometheus.io/scrape": "true", + }, + ). WithSpec(corev1.PodSpec(). WithNodeSelector(ctx.NodeSelector). WithVolumes(corev1.Volume(). @@ -304,6 +311,7 @@ func deployPoetD(ctx *testcontext.Context, id string, flags ...DeploymentFlag) ( WithArgs(args...). WithPorts( corev1.ContainerPort().WithName("rest").WithProtocol("TCP").WithContainerPort(poetPort), + corev1.ContainerPort().WithName("prometheus").WithContainerPort(prometheusScrapePort), ). WithVolumeMounts( corev1.VolumeMount().WithName("config").WithMountPath(configDir), @@ -372,6 +380,7 @@ func deployPoetSvc(ctx *testcontext.Context, id string) (*apiv1.Service, error) WithSelector(labels). WithPorts( corev1.ServicePort().WithName("rest").WithPort(poetPort).WithProtocol("TCP"), + corev1.ServicePort().WithName("prometheus").WithPort(prometheusScrapePort), ), ) diff --git a/systest/parameters/fastnet/poet.conf b/systest/parameters/fastnet/poet.conf index ce3cc9c6f4..27adb95f75 100644 --- a/systest/parameters/fastnet/poet.conf +++ b/systest/parameters/fastnet/poet.conf @@ -5,6 +5,4 @@ cycle-gap="30s" jsonlog="true" debuglog="true" -pow-difficulty=4 - -metrics-port=8081 \ No newline at end of file +pow-difficulty=4 \ No newline at end of file diff --git a/systest/tests/metrics.go b/systest/tests/metrics.go new file mode 100644 index 0000000000..2d1ed131d1 --- /dev/null +++ b/systest/tests/metrics.go @@ -0,0 +1,65 @@ +package tests + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/prometheus/common/expfmt" +) + +var errMetricNotFound = errors.New("metric not found") + +func fetchCounterMetric( + ctx context.Context, + url string, + metricName string, + labelFilters map[string]string, +) (float64, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return 0, fmt.Errorf("failed to create HTTP request: %v", err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return 0, fmt.Errorf("failed to fetch metrics: %v", err) + } + defer resp.Body.Close() + + parser := expfmt.TextParser{} + metricFamilies, err := parser.TextToMetricFamilies(resp.Body) + if err != nil { + return 0, fmt.Errorf("failed to parse metric families: %v", err) + } + + metricFamily, ok := metricFamilies[metricName] + if !ok { + return 0, fmt.Errorf("metric not found: %s", metricName) + } + + for _, metric := range metricFamily.Metric { + if metric.Counter != nil { + // Check if the metric has the specified labels + labels := make(map[string]string) + for _, lp := range metric.Label { + labels[lp.GetName()] = lp.GetValue() + } + + matches := true + for key, value := range labelFilters { + if labels[key] != value { + matches = false + break + } + } + + if matches { + return metric.GetCounter().GetValue(), nil + } + } + } + + return 0, errMetricNotFound +} diff --git a/systest/tests/poets_test.go b/systest/tests/poets_test.go index 3968851d7a..0890d479e1 100644 --- a/systest/tests/poets_test.go +++ b/systest/tests/poets_test.go @@ -1,6 +1,8 @@ package tests import ( + "crypto/ed25519" + "encoding/base64" "encoding/hex" "fmt" "math" @@ -203,3 +205,68 @@ func TestNodesUsingDifferentPoets(t *testing.T) { ) } } + +// Test verifying that nodes can register in both poets +// - supporting PoW only +// - supporting certificates +// TODO: When PoW support is removed, convert this test to verify only the cert path. +func TestRegisteringInPoetWithPowAndCert(t *testing.T) { + tctx := testcontext.New(t, testcontext.Labels("sanity")) + tctx.PoetSize = 2 + + cl := cluster.New(tctx, cluster.WithKeys(10)) + require.NoError(t, cl.AddBootnodes(tctx, 2)) + require.NoError(t, cl.AddBootstrappers(tctx)) + + pubkey, privkey, err := ed25519.GenerateKey(nil) + require.NoError(t, err) + require.NoError(t, cl.AddCertifier(tctx, base64.StdEncoding.EncodeToString(privkey.Seed()))) + // First poet supports PoW only (legacy) + require.NoError(t, cl.AddPoet(tctx)) + // Second poet supports certs + require.NoError( + t, + cl.AddPoet( + tctx, + cluster.PoetCertifierURL("http://certifier-0"), + cluster.PoetCertifierPubkey(base64.StdEncoding.EncodeToString(pubkey)), + ), + ) + require.NoError(t, cl.AddSmeshers(tctx, tctx.ClusterSize-2)) + require.NoError(t, cl.WaitAll(tctx)) + + epoch := 2 + layersPerEpoch := testcontext.LayersPerEpoch.Get(tctx.Parameters) + last := uint32(layersPerEpoch * epoch) + tctx.Log.Debugw("waiting for epoch", "epoch", epoch, "layer", last) + + eg, ctx := errgroup.WithContext(tctx) + for i := 0; i < cl.Total(); i++ { + client := cl.Client(i) + tctx.Log.Debugw("watching", "client", client.Name) + watchProposals(ctx, eg, client, func(proposal *pb.Proposal) (bool, error) { + return proposal.Layer.Number < last, nil + }) + } + + require.NoError(t, eg.Wait()) + + // Check that smeshers are registered in both poets + valid := map[string]string{"result": "valid"} + invalid := map[string]string{"result": "invalid"} + + metricsEndpoint := cluster.MakePoetMetricsEndpoint(tctx.Namespace, 0) + powRegs, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_pow_total", valid) + require.NoError(t, err) + require.GreaterOrEqual(t, float64(cl.Smeshers()*epoch), powRegs) + powRegsInvalid, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_pow_total", invalid) + require.ErrorIs(t, err, errMetricNotFound, "metric for invalid PoW registrations value: %v", powRegsInvalid) + + metricsEndpoint = cluster.MakePoetMetricsEndpoint(tctx.Namespace, 1) + certRegs, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_cert_total", valid) + require.NoError(t, err) + require.GreaterOrEqual(t, float64(cl.Smeshers()*epoch), certRegs) + + certRegsInvalid, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_cert_total", invalid) + require.ErrorIs(t, err, errMetricNotFound, "metric for invalid cert registrations value: %v", certRegsInvalid) +} From 326bde01da6f547bec2fcf9653cb07369b5134db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 13 Nov 2023 10:02:27 +0700 Subject: [PATCH 12/55] Configurable certifier retry options --- activation/activation.go | 10 +++++- activation/certifier.go | 37 +++++++++++++++++++++ config/config.go | 44 +++++++++++++------------ config/presets/testnet.go | 5 +-- node/node.go | 1 + systest/parameters/bignet/smesher.json | 3 ++ systest/parameters/fastnet/smesher.json | 3 ++ 7 files changed, 79 insertions(+), 24 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 607059798b..0966888422 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -83,6 +83,7 @@ type Builder struct { nipostBuilder nipostBuilder validator nipostValidator certifier certifierService + certifierConfig CertifierClientConfig // smeshingMutex protects `StartSmeshing` and `StopSmeshing` from concurrent access smeshingMutex sync.Mutex @@ -136,6 +137,12 @@ func WithValidator(v nipostValidator) BuilderOption { } } +func WithCertifierConfig(c CertifierClientConfig) BuilderOption { + return func(b *Builder) { + b.certifierConfig = c + } +} + // NewBuilder returns an atx builder that will start a routine that will attempt to create an atx upon each new layer. func NewBuilder( conf Config, @@ -165,6 +172,7 @@ func NewBuilder( syncer: syncer, log: log, poetRetryInterval: defaultPoetRetryInterval, + certifierConfig: DefaultCertifierClientConfig(), } for _, opt := range opts { opt(b) @@ -397,7 +405,7 @@ func (b *Builder) certifyPost(ctx context.Context) { b.log.With().Error("failed to obtain post for certification", zap.Error(err)) } - client := NewCertifierClient(b.log, post, meta, ch) + client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig)) b.certifier = NewCertifier(b.localDB, b.log, client) b.certifier.CertifyAll(ctx, b.poets) } diff --git a/activation/certifier.go b/activation/certifier.go index 905eae5b6f..ea9f9b9db3 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -10,6 +10,7 @@ import ( "io" "net/http" "net/url" + "time" "github.com/hashicorp/go-retryablehttp" "github.com/sourcegraph/conc/iter" @@ -21,6 +22,23 @@ import ( certifier_db "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) +type CertifierClientConfig struct { + // Base delay between retries, scaled with the number of retries. + RetryDelay time.Duration `mapstructure:"retry-delay"` + // Maximum time to wait between retries + MaxRetryDelay time.Duration `mapstructure:"max-retry-delay"` + // Maximum number of retries + MaxRetries int `mapstructure:"max-retries"` +} + +func DefaultCertifierClientConfig() CertifierClientConfig { + return CertifierClientConfig{ + RetryDelay: 1 * time.Second, + MaxRetryDelay: 30 * time.Second, + MaxRetries: 5, + } +} + type ProofToCertify struct { Nonce uint32 `json:"nonce"` Indices []byte `json:"indices"` @@ -182,11 +200,22 @@ type CertifierClient struct { logger *zap.Logger } +type certifierClientOpts func(*CertifierClient) + +func WithCertifierClientConfig(cfg CertifierClientConfig) certifierClientOpts { + return func(c *CertifierClient) { + c.client.RetryMax = cfg.MaxRetries + c.client.RetryWaitMin = cfg.RetryDelay + c.client.RetryWaitMax = cfg.MaxRetryDelay + } +} + func NewCertifierClient( logger *zap.Logger, post *types.Post, postInfo *types.PostInfo, postCh []byte, + opts ...certifierClientOpts, ) *CertifierClient { c := &CertifierClient{ client: retryablehttp.NewClient(), @@ -195,11 +224,19 @@ func NewCertifierClient( postInfo: postInfo, postCh: postCh, } + config := DefaultCertifierClientConfig() + c.client.RetryMax = config.MaxRetries + c.client.RetryWaitMin = config.RetryDelay + c.client.RetryWaitMax = config.MaxRetryDelay c.client.Logger = &retryableHttpLogger{logger} c.client.ResponseLogHook = func(logger retryablehttp.Logger, resp *http.Response) { c.logger.Info("response received", zap.Stringer("url", resp.Request.URL), zap.Int("status", resp.StatusCode)) } + for _, opt := range opts { + opt(c) + } + return c } diff --git a/config/config.go b/config/config.go index 60352fdacd..201081e12f 100644 --- a/config/config.go +++ b/config/config.go @@ -46,27 +46,28 @@ func init() { // Config defines the top level configuration for a spacemesh node. type Config struct { BaseConfig `mapstructure:"main"` - Genesis *GenesisConfig `mapstructure:"genesis"` - PublicMetrics PublicMetrics `mapstructure:"public-metrics"` - Tortoise tortoise.Config `mapstructure:"tortoise"` - P2P p2p.Config `mapstructure:"p2p"` - API grpcserver.Config `mapstructure:"api"` - HARE hareConfig.Config `mapstructure:"hare"` - HARE3 hare3.Config `mapstructure:"hare3"` - HareEligibility eligConfig.Config `mapstructure:"hare-eligibility"` - Beacon beacon.Config `mapstructure:"beacon"` - TIME timeConfig.TimeConfig `mapstructure:"time"` - VM vm.Config `mapstructure:"vm"` - POST activation.PostConfig `mapstructure:"post"` - POSTService activation.PostSupervisorConfig `mapstructure:"post-service"` - POET activation.PoetConfig `mapstructure:"poet"` - SMESHING SmeshingConfig `mapstructure:"smeshing"` - LOGGING LoggerConfig `mapstructure:"logging"` - FETCH fetch.Config `mapstructure:"fetch"` - Bootstrap bootstrap.Config `mapstructure:"bootstrap"` - Sync syncer.Config `mapstructure:"syncer"` - Recovery checkpoint.Config `mapstructure:"recovery"` - Cache datastore.Config `mapstructure:"cache"` + Genesis *GenesisConfig `mapstructure:"genesis"` + PublicMetrics PublicMetrics `mapstructure:"public-metrics"` + Tortoise tortoise.Config `mapstructure:"tortoise"` + P2P p2p.Config `mapstructure:"p2p"` + API grpcserver.Config `mapstructure:"api"` + HARE hareConfig.Config `mapstructure:"hare"` + HARE3 hare3.Config `mapstructure:"hare3"` + HareEligibility eligConfig.Config `mapstructure:"hare-eligibility"` + Beacon beacon.Config `mapstructure:"beacon"` + TIME timeConfig.TimeConfig `mapstructure:"time"` + VM vm.Config `mapstructure:"vm"` + POST activation.PostConfig `mapstructure:"post"` + POSTService activation.PostSupervisorConfig `mapstructure:"post-service"` + POET activation.PoetConfig `mapstructure:"poet"` + SMESHING SmeshingConfig `mapstructure:"smeshing"` + LOGGING LoggerConfig `mapstructure:"logging"` + FETCH fetch.Config `mapstructure:"fetch"` + Bootstrap bootstrap.Config `mapstructure:"bootstrap"` + Sync syncer.Config `mapstructure:"syncer"` + Recovery checkpoint.Config `mapstructure:"recovery"` + Cache datastore.Config `mapstructure:"cache"` + Certifier activation.CertifierClientConfig `mapstructure:"certifier"` } // DataDir returns the absolute path to use for the node's data. This is the tilde-expanded path given in the config @@ -167,6 +168,7 @@ func DefaultConfig() Config { Sync: syncer.DefaultConfig(), Recovery: checkpoint.DefaultConfig(), Cache: datastore.DefaultConfig(), + Certifier: activation.DefaultCertifierClientConfig(), } } diff --git a/config/presets/testnet.go b/config/presets/testnet.go index 3d5383f975..10c312c6c4 100644 --- a/config/presets/testnet.go +++ b/config/presets/testnet.go @@ -150,7 +150,8 @@ func testnet() config.Config { GossipDuration: 50 * time.Second, OutOfSyncThresholdLayers: 10, }, - Recovery: checkpoint.DefaultConfig(), - Cache: datastore.DefaultConfig(), + Recovery: checkpoint.DefaultConfig(), + Cache: datastore.DefaultConfig(), + Certifier: activation.DefaultCertifierClientConfig(), } } diff --git a/node/node.go b/node/node.go index 85815b4b47..aae2051282 100644 --- a/node/node.go +++ b/node/node.go @@ -1002,6 +1002,7 @@ func (app *App) initServices(ctx context.Context) error { activation.WithPoetRetryInterval(app.Config.HARE.WakeupDelta), activation.WithValidator(app.validator), activation.WithPoets(poetClients...), + activation.WithCertifierConfig(app.Config.Certifier), ) if err := atxBuilder.MigrateDiskToLocalDB(); err != nil { app.log.Panic("failed to migrate state of atx builder: %v", err) diff --git a/systest/parameters/bignet/smesher.json b/systest/parameters/bignet/smesher.json index 1f724bb099..723fbc6502 100644 --- a/systest/parameters/bignet/smesher.json +++ b/systest/parameters/bignet/smesher.json @@ -37,5 +37,8 @@ "post-k1": 26, "post-k2": 37, "post-k3": 37 + }, + "certifier": { + "max-retries": 10 } } \ No newline at end of file diff --git a/systest/parameters/fastnet/smesher.json b/systest/parameters/fastnet/smesher.json index 23e9a12d14..2aae66f950 100644 --- a/systest/parameters/fastnet/smesher.json +++ b/systest/parameters/fastnet/smesher.json @@ -28,5 +28,8 @@ }, "fetch": { "servers-metrics": true + }, + "certifier": { + "max-retries": 10 } } \ No newline at end of file From e0a6f89510990a6d475eed606fcc2d93076d711b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 13 Nov 2023 11:36:06 +0700 Subject: [PATCH 13/55] Increase systest CI job timeout --- .github/workflows/systest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/systest.yml b/.github/workflows/systest.yml index 042b4f81e2..a3100bfb2a 100644 --- a/.github/workflows/systest.yml +++ b/.github/workflows/systest.yml @@ -50,7 +50,7 @@ jobs: if: ${{ needs.filter-changes.outputs.nondocchanges == 'true' }} needs: - filter-changes - timeout-minutes: 70 + timeout-minutes: 90 steps: - uses: actions/checkout@v4 @@ -108,7 +108,7 @@ jobs: go-version-file: "go.mod" - name: Run tests - timeout-minutes: 60 + timeout-minutes: 80 env: test_id: systest-${{ steps.vars.outputs.sha_short }} label: sanity From 0054df7125777f4356e671eba138986039774ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 14 Nov 2023 14:40:03 +0700 Subject: [PATCH 14/55] Keep POST proof in localDB for certification --- activation/activation.go | 52 ++++++++++++++------------- activation/activation_test.go | 44 ++++++++--------------- activation/e2e/nipost_test.go | 1 + activation/e2e/validation_test.go | 1 + activation/nipost.go | 20 +++++++++++ activation/nipost_test.go | 20 +++++++++++ checkpoint/recovery.go | 4 +++ node/node.go | 2 ++ sql/localsql/certifier/db.go | 4 +-- sql/localsql/certifier/db_test.go | 45 +++++++++++++++++++++++ sql/localsql/nipost/nipost.go | 58 ++++++++++++++---------------- sql/localsql/nipost/nipost_test.go | 47 ++++++++++++------------ sql/migrations/local/0002_next.sql | 9 +++-- 13 files changed, 193 insertions(+), 114 deletions(-) create mode 100644 sql/localsql/certifier/db_test.go diff --git a/activation/activation.go b/activation/activation.go index 0966888422..f0ff838bae 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -309,19 +309,17 @@ func (b *Builder) movePostToDb() error { commitmentAtxId := types.BytesToATXID(meta.CommitmentAtxId) return b.localDB.WithTx(context.Background(), func(tx *sql.Tx) error { - if err := nipost.RemoveInitialPost(tx, b.signer.NodeID()); err != nil { - return fmt.Errorf("removing existing challenge from db: %w", err) - } initialPost := nipost.Post{ - Nonce: post.Nonce, - Indices: post.Indices, - Pow: post.Pow, + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: shared.ZeroChallenge, NumUnits: meta.NumUnits, CommitmentATX: commitmentAtxId, VRFNonce: types.VRFPostIndex(*meta.Nonce), } - if err := nipost.AddInitialPost(tx, b.signer.NodeID(), initialPost); err != nil { + if err := nipost.AddPost(tx, b.signer.NodeID(), initialPost); err != nil { return fmt.Errorf("adding post to db: %w", err) } return discardPost(b.nipostBuilder.DataDir()) @@ -360,7 +358,7 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { return nil } // ...and if we haven't stored an initial post yet. - _, err := nipost.InitialPost(b.localDB, b.signer.NodeID()) + _, err := nipost.GetPost(b.localDB, b.signer.NodeID()) switch { case err == nil: b.log.Info("load initial post from db") @@ -382,15 +380,16 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { b.log.Info("created the initial post") initialPost := nipost.Post{ - Nonce: post.Nonce, - Indices: post.Indices, - Pow: post.Pow, + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: shared.ZeroChallenge, NumUnits: postInfo.NumUnits, CommitmentATX: postInfo.CommitmentATX, VRFNonce: *postInfo.Nonce, } - return nipost.AddInitialPost(b.localDB, b.signer.NodeID(), initialPost) + return nipost.AddPost(b.localDB, b.signer.NodeID(), initialPost) } // Obtain certificates for the poets. @@ -411,36 +410,37 @@ func (b *Builder) certifyPost(ctx context.Context) { } func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, []byte, error) { - post, err := nipost.InitialPost(b.localDB, b.signer.NodeID()) + post, err := nipost.GetPost(b.localDB, b.signer.NodeID()) switch { case err == nil: - b.log.Info("certifying using the initial post") + b.log.Info("certifying using the post from local DB") meta := &types.PostInfo{ NodeID: b.SmesherID(), CommitmentATX: post.CommitmentATX, Nonce: &post.VRFNonce, NumUnits: post.NumUnits, } + challenge := post.Challenge post := &types.Post{ Nonce: post.Nonce, Indices: post.Indices, Pow: post.Pow, } - return post, meta, shared.ZeroChallenge, nil + return post, meta, challenge, nil case errors.Is(err, sql.ErrNotFound): - // no initial post + // no post found default: return nil, nil, nil, fmt.Errorf("loading initial post from db: %w", err) } - b.log.Info("certifying using an existing ATX") - atxid, err := atxs.GetFirstIDByNodeID(b.cdb, b.SmesherID()) + b.log.Debug("trying to obtain POST from an existing ATX") + atxid, err := atxs.GetLastIDByNodeID(b.cdb, b.SmesherID()) if err != nil { - return nil, nil, nil, fmt.Errorf("cannot certify - no existing ATX found: %w", err) + return nil, nil, nil, fmt.Errorf("no existing ATX found: %w", err) } atx, err := b.cdb.GetFullAtx(atxid) if err != nil { - return nil, nil, nil, fmt.Errorf("cannot certify - failed to retrieve ATX: %w", err) + return nil, nil, nil, fmt.Errorf("failed to retrieve ATX: %w", err) } var commitmentAtx *types.ATXID if commitmentAtx = atx.CommitmentATX; commitmentAtx == nil { @@ -450,6 +450,12 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, [] } commitmentAtx = &atx } + if atx.NIPost == nil { + return nil, nil, nil, errors.New("ATX does not contain a NIPost") + } + + b.log.Info("certifying using an existing ATX", zap.Any("atx", atx)) + meta := &types.PostInfo{ NodeID: b.SmesherID(), CommitmentATX: *commitmentAtx, @@ -587,7 +593,7 @@ func (b *Builder) buildNIPostChallenge(ctx context.Context) (*types.NIPostChalle switch { case errors.Is(err, sql.ErrNotFound): // initial ATX challenge - post, err := nipost.InitialPost(b.localDB, b.signer.NodeID()) + post, err := nipost.GetPost(b.localDB, b.signer.NodeID()) if err != nil { return nil, fmt.Errorf("get initial post: %w", err) } @@ -673,9 +679,7 @@ func (b *Builder) PublishActivationTx(ctx context.Context) error { if err := nipost.RemoveChallenge(b.localDB, b.signer.NodeID()); err != nil { return fmt.Errorf("discarding challenge after published ATX: %w", err) } - if err := nipost.RemoveInitialPost(b.localDB, b.signer.NodeID()); err != nil { - return fmt.Errorf("discarding initial post after published ATX: %w", err) - } + events.EmitAtxPublished( atx.PublishEpoch, atx.TargetEpoch(), atx.ID(), diff --git a/activation/activation_test.go b/activation/activation_test.go index 2dbe1e13a1..218e1256b5 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -716,10 +716,10 @@ func TestBuilder_PublishActivationTx_NoPrevATX(t *testing.T) { require.NoError(t, atxs.Add(tab.cdb, vPosAtx)) // generate and store initial post in state - require.NoError(t, nipost.AddInitialPost( + require.NoError(t, nipost.AddPost( tab.localDb, tab.sig.NodeID(), - nipost.Post{Indices: make([]byte, 10)}, + nipost.Post{Indices: make([]byte, 10), Challenge: shared.ZeroChallenge}, )) // create and publish ATX @@ -728,10 +728,6 @@ func TestBuilder_PublishActivationTx_NoPrevATX(t *testing.T) { require.NoError(t, err) require.NotNil(t, atx) - // state is cleaned up - _, err = nipost.InitialPost(tab.localDB, tab.sig.NodeID()) - require.ErrorIs(t, err, sql.ErrNotFound) - _, err = nipost.Challenge(tab.localDB, tab.sig.NodeID()) require.ErrorIs(t, err, sql.ErrNotFound) } @@ -754,8 +750,9 @@ func TestBuilder_PublishActivationTx_NoPrevATX_PublishFails_InitialPost_preserve refPost := nipost.Post{ Indices: make([]byte, 10), CommitmentATX: types.RandomATXID(), + Challenge: shared.ZeroChallenge, } - require.NoError(t, nipost.AddInitialPost( + require.NoError(t, nipost.AddPost( tab.localDb, tab.sig.NodeID(), refPost, @@ -798,7 +795,7 @@ func TestBuilder_PublishActivationTx_NoPrevATX_PublishFails_InitialPost_preserve } // initial post is preserved - post, err := nipost.InitialPost(tab.localDB, tab.sig.NodeID()) + post, err := nipost.GetPost(tab.localDB, tab.sig.NodeID()) require.NoError(t, err) require.NotNil(t, post) require.Equal(t, refPost, *post) @@ -993,10 +990,10 @@ func TestBuilder_PublishActivationTx_TargetsEpochBasedOnPosAtx(t *testing.T) { return nil }) - require.NoError(t, nipost.AddInitialPost( + require.NoError(t, nipost.AddPost( tab.localDb, tab.sig.NodeID(), - nipost.Post{Indices: make([]byte, 10)}, + nipost.Post{Indices: make([]byte, 10), Challenge: shared.ZeroChallenge}, )) r.NoError(tab.PublishActivationTx(context.Background())) @@ -1205,7 +1202,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { } // state is cleaned up - _, err = nipost.InitialPost(tab.localDB, tab.sig.NodeID()) + _, err = nipost.GetPost(tab.localDB, tab.sig.NodeID()) require.ErrorIs(t, err, sql.ErrNotFound) _, err = nipost.Challenge(tab.localDB, tab.sig.NodeID()) @@ -1327,19 +1324,6 @@ func TestBuilder_InitialPostIsPersisted(t *testing.T) { // postClient.Proof() should not be called again require.NoError(t, tab.buildInitialPost(context.Background())) - - // Remove the persisted post file and try again - require.NoError(t, nipost.RemoveInitialPost(tab.localDb, tab.signer.NodeID())) - tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge). - Return( - &types.Post{Indices: make([]byte, 10)}, - &types.PostInfo{ - CommitmentATX: types.RandomATXID(), - Nonce: new(types.VRFPostIndex), - }, - nil, - ) - require.NoError(t, tab.buildInitialPost(context.Background())) } func TestWaitPositioningAtx(t *testing.T) { @@ -1381,10 +1365,10 @@ func TestWaitPositioningAtx(t *testing.T) { return nil }) - require.NoError(t, nipost.AddInitialPost( + require.NoError(t, nipost.AddPost( tab.localDb, tab.sig.NodeID(), - nipost.Post{Indices: make([]byte, 10)}, + nipost.Post{Indices: make([]byte, 10), Challenge: shared.ZeroChallenge}, )) err := tab.PublishActivationTx(context.Background()) @@ -1475,7 +1459,7 @@ func TestBuilder_MovePostToDb(t *testing.T) { }) require.NoError(t, tab.movePostToDb()) - post, err := nipost.InitialPost(tab.localDb, tab.sig.NodeID()) + post, err := nipost.GetPost(tab.localDb, tab.sig.NodeID()) require.NoError(t, err) require.NotNil(t, post) require.Equal(t, refPost.Nonce, post.Nonce) @@ -1486,7 +1470,7 @@ func TestBuilder_MovePostToDb(t *testing.T) { require.NoFileExists(t, filepath.Join(tab.nipostBuilder.DataDir(), postFilename)) require.NoError(t, tab.movePostToDb()) // should not fail if post is already in db - post2, err := nipost.InitialPost(tab.localDb, tab.sig.NodeID()) + post2, err := nipost.GetPost(tab.localDb, tab.sig.NodeID()) require.NoError(t, err) require.NotNil(t, post2) // state is unchanged require.Equal(t, refPost.Nonce, post2.Nonce) @@ -1555,7 +1539,7 @@ func TestBuilder_MigrateDiskToLocalDB(t *testing.T) { require.NoError(t, tab.MigrateDiskToLocalDB()) - post, err := nipost.InitialPost(tab.localDb, tab.sig.NodeID()) + post, err := nipost.GetPost(tab.localDb, tab.sig.NodeID()) require.NoError(t, err) require.NotNil(t, post) require.Equal(t, refPost.Nonce, post.Nonce) @@ -1573,7 +1557,7 @@ func TestBuilder_MigrateDiskToLocalDB(t *testing.T) { require.NoError(t, tab.MigrateDiskToLocalDB()) // should not fail if challenge and post are already in db - post2, err := nipost.InitialPost(tab.localDb, tab.sig.NodeID()) + post2, err := nipost.GetPost(tab.localDb, tab.sig.NodeID()) require.NoError(t, err) require.NotNil(t, post2) // state is unchanged require.NotNil(t, post) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index d0652c8bb6..f8bcf8b197 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -213,6 +213,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), activation.WithPoetClients(client), ) require.NoError(t, err) diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index cf998bf830..20af91269a 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -101,6 +101,7 @@ func TestValidator_Validate(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), activation.WithPoetClients(client), ) require.NoError(t, err) diff --git a/activation/nipost.go b/activation/nipost.go index 62e3b06ca8..5d2f55ab2e 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -21,6 +21,8 @@ import ( "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/metrics/public" "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) const ( @@ -72,6 +74,7 @@ type NIPostBuilder struct { signer *signing.EdSigner layerClock layerClock poetCfg PoetConfig + localDB *localsql.Database } type NIPostBuilderOption func(*NIPostBuilder) @@ -94,6 +97,7 @@ func NewNIPostBuilder( signer *signing.EdSigner, poetCfg PoetConfig, layerClock layerClock, + localDB *localsql.Database, opts ...NIPostBuilderOption, ) (*NIPostBuilder, error) { b := &NIPostBuilder{ @@ -105,6 +109,7 @@ func NewNIPostBuilder( signer: signer, poetCfg: poetCfg, layerClock: layerClock, + localDB: localDB, } for _, opt := range opts { @@ -284,6 +289,21 @@ func (nb *NIPostBuilder) BuildNIPost( } nb.persistState() + + post := nipost.Post{ + Nonce: proof.Nonce, + Indices: proof.Indices, + Pow: proof.Pow, + Challenge: nb.state.PoetProofRef[:], + NumUnits: postInfo.NumUnits, + CommitmentATX: postInfo.CommitmentATX, + } + if postInfo.Nonce != nil { + post.VRFNonce = *postInfo.Nonce + } + if err := nipost.AddPost(nb.localDB, nb.signer.NodeID(), post); err != nil { + nb.log.Warn("failed to persist post", zap.Error(err)) + } } nb.log.Info("finished nipost construction") diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 741d368cd3..e2349ee3ea 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -15,6 +15,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql/localsql" ) func defaultPoetServiceMock(ctrl *gomock.Controller, id []byte, address string) *MockPoetClient { @@ -78,6 +79,7 @@ func TestNIPostBuilderWithMocks(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProvider), ) require.NoError(t, err) @@ -121,6 +123,7 @@ func TestPostSetup(t *testing.T) { postProvider.signer, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProvider), ) require.NoError(t, err) @@ -187,6 +190,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -211,6 +215,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -233,6 +238,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -323,6 +329,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poets...), ) require.NoError(t, err) @@ -401,6 +408,7 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poets...), ) require.NoError(t, err) @@ -449,6 +457,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -479,6 +488,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -521,6 +531,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -549,6 +560,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -580,6 +592,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -621,6 +634,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { sig, PoetConfig{PhaseShift: layerDuration}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -699,6 +713,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -731,6 +746,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -769,6 +785,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -850,6 +867,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poet), ) require.NoError(t, err) @@ -1014,6 +1032,7 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { sig, poetCfg, mclock, + localsql.InMemory(), WithPoetClients(poets...), ) require.NoError(t, err) @@ -1099,6 +1118,7 @@ func TestNIPostBuilder_Close(t *testing.T) { sig, PoetConfig{}, mclock, + localsql.InMemory(), WithPoetClients(defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999")), ) require.NoError(t, err) diff --git a/checkpoint/recovery.go b/checkpoint/recovery.go index 3d4ee5e13d..72e7cd8c2e 100644 --- a/checkpoint/recovery.go +++ b/checkpoint/recovery.go @@ -185,6 +185,8 @@ func recoverFromLocalFile( log.Context(ctx), log.Int("own atx deps", len(deps)), ) + } else { + logger.WithContext(ctx).Info("there are no own atx deps") } if err := db.Close(); err != nil { return nil, fmt.Errorf("close old db: %w", err) @@ -333,6 +335,7 @@ func collectOwnAtxDeps( nipostCh, _ := nipost.Challenge(localDB, cfg.NodeID) if ref == types.EmptyATXID { if nipostCh == nil { + logger.Debug("there is no own atx and none is being built") return nil, nil, nil } if nipostCh.CommitmentATX != nil { @@ -371,6 +374,7 @@ func collectOwnAtxDeps( deps = append(deps, deps2...) proofs = append(proofs, proofs2...) } + logger.With().Debug("collected atx deps", log.Any("deps", deps)) return deps, proofs, nil } diff --git a/node/node.go b/node/node.go index aae2051282..5150b95002 100644 --- a/node/node.go +++ b/node/node.go @@ -959,6 +959,7 @@ func (app *App) initServices(ctx context.Context) error { app.edSgn, app.Config.POET, app.clock, + app.localDB, activation.WithPoetClients(poetClients...), ) if err != nil { @@ -1812,6 +1813,7 @@ func (app *App) startSynchronous(ctx context.Context) (err error) { func (app *App) preserveAfterRecovery(ctx context.Context) { if app.preserve == nil { + app.log.Info("no need to preserve data after recovery") return } for i, poetProof := range app.preserve.Proofs { diff --git a/sql/localsql/certifier/db.go b/sql/localsql/certifier/db.go index ced108ad2c..ca30a0a359 100644 --- a/sql/localsql/certifier/db.go +++ b/sql/localsql/certifier/db.go @@ -14,8 +14,8 @@ func AddCertificate(db sql.Executor, nodeID types.NodeID, cert []byte, poetUrl s stmt.BindBytes(3, cert) } if _, err := db.Exec(` - insert into poet_certificates (node_id, poet_url, certificate) - values (?1, ?2, ?3);`, enc, nil, + REPLACE INTO poet_certificates (node_id, poet_url, certificate) + VALUES (?1, ?2, ?3);`, enc, nil, ); err != nil { return fmt.Errorf("storing poet certificate for (%s; %s): %w", nodeID.ShortString(), poetUrl, err) } diff --git a/sql/localsql/certifier/db_test.go b/sql/localsql/certifier/db_test.go new file mode 100644 index 0000000000..8910b8bb37 --- /dev/null +++ b/sql/localsql/certifier/db_test.go @@ -0,0 +1,45 @@ +package certifier_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" +) + +func TestAddingCertificates(t *testing.T) { + db := localsql.InMemory() + nodeId := types.RandomNodeID() + + require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert"), "poet-0")) + cert, err := certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, err) + require.Equal(t, []byte("cert"), cert) + + require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert2"), "poet-1")) + + cert, err = certifier.Certificate(db, nodeId, "poet-1") + require.NoError(t, err) + require.Equal(t, []byte("cert2"), cert) + cert, err = certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, err) + require.Equal(t, []byte("cert"), cert) +} + +func TestOverwritingCertificates(t *testing.T) { + db := localsql.InMemory() + nodeId := types.RandomNodeID() + + require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert"), "poet-0")) + cert, err := certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, err) + require.Equal(t, []byte("cert"), cert) + + require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert2"), "poet-0")) + cert, err = certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, err) + require.Equal(t, []byte("cert2"), cert) +} diff --git a/sql/localsql/nipost/nipost.go b/sql/localsql/nipost/nipost.go index e989c77352..f46e23e975 100644 --- a/sql/localsql/nipost/nipost.go +++ b/sql/localsql/nipost/nipost.go @@ -8,71 +8,65 @@ import ( ) type Post struct { - Nonce uint32 - Indices []byte - Pow uint64 + Nonce uint32 + Indices []byte + Pow uint64 + Challenge []byte NumUnits uint32 CommitmentATX types.ATXID VRFNonce types.VRFPostIndex } -func AddInitialPost(db sql.Executor, nodeID types.NodeID, post Post) error { +func AddPost(db sql.Executor, nodeID types.NodeID, post Post) error { enc := func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) stmt.BindInt64(2, int64(post.Nonce)) stmt.BindBytes(3, post.Indices) stmt.BindInt64(4, int64(post.Pow)) - stmt.BindInt64(5, int64(post.NumUnits)) - stmt.BindBytes(6, post.CommitmentATX.Bytes()) - stmt.BindInt64(7, int64(post.VRFNonce)) + stmt.BindBytes(5, post.Challenge) + stmt.BindInt64(6, int64(post.NumUnits)) + stmt.BindBytes(7, post.CommitmentATX.Bytes()) + stmt.BindInt64(8, int64(post.VRFNonce)) } if _, err := db.Exec(` - insert into initial_post ( - id, post_nonce, post_indices, post_pow, num_units, commit_atx, vrf_nonce - ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);`, enc, nil, + REPLACE into post ( + id, post_nonce, post_indices, post_pow, challenge, num_units, commit_atx, vrf_nonce + ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);`, enc, nil, ); err != nil { - return fmt.Errorf("insert initial post for %s: %w", nodeID.ShortString(), err) + return fmt.Errorf("inserting post for %s: %w", nodeID.ShortString(), err) } return nil } -func RemoveInitialPost(db sql.Executor, nodeID types.NodeID) error { - enc := func(stmt *sql.Statement) { - stmt.BindBytes(1, nodeID.Bytes()) - } - if _, err := db.Exec(`delete from initial_post where id = ?1;`, enc, nil); err != nil { - return fmt.Errorf("remove initial post for %s: %w", nodeID, err) - } - return nil -} - -func InitialPost(db sql.Executor, nodeID types.NodeID) (*Post, error) { +func GetPost(db sql.Executor, nodeID types.NodeID) (*Post, error) { var post *Post enc := func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) } dec := func(stmt *sql.Statement) bool { post = &Post{ - Nonce: uint32(stmt.ColumnInt64(0)), - Indices: make([]byte, stmt.ColumnLen(1)), - Pow: uint64(stmt.ColumnInt64(2)), + Nonce: uint32(stmt.ColumnInt64(0)), + Indices: make([]byte, stmt.ColumnLen(1)), + Pow: uint64(stmt.ColumnInt64(2)), + Challenge: make([]byte, stmt.ColumnLen(3)), - NumUnits: uint32(stmt.ColumnInt64(3)), - VRFNonce: types.VRFPostIndex(stmt.ColumnInt64(5)), + NumUnits: uint32(stmt.ColumnInt64(4)), + VRFNonce: types.VRFPostIndex(stmt.ColumnInt64(6)), } stmt.ColumnBytes(1, post.Indices) - stmt.ColumnBytes(4, post.CommitmentATX[:]) + stmt.ColumnBytes(3, post.Challenge) + stmt.ColumnBytes(5, post.CommitmentATX[:]) return true } if _, err := db.Exec(` - select post_nonce, post_indices, post_pow, num_units, commit_atx, vrf_nonce - from initial_post where id = ?1 limit 1;`, enc, dec, + select post_nonce, post_indices, post_pow, challenge, num_units, commit_atx, vrf_nonce + from post where id = ?1 limit 1;`, enc, dec, ); err != nil { - return nil, fmt.Errorf("get initial post from node id %s: %w", nodeID.ShortString(), err) + return nil, fmt.Errorf("getting post for node id %s: %w", nodeID.ShortString(), err) } if post == nil { - return nil, fmt.Errorf("get initial post from node id %s: %w", nodeID.ShortString(), sql.ErrNotFound) + return nil, fmt.Errorf("getting post for node id %s: %w", nodeID.ShortString(), sql.ErrNotFound) } return post, nil } diff --git a/sql/localsql/nipost/nipost_test.go b/sql/localsql/nipost/nipost_test.go index eb242a4b2b..0241b1a17b 100644 --- a/sql/localsql/nipost/nipost_test.go +++ b/sql/localsql/nipost/nipost_test.go @@ -23,54 +23,53 @@ func Test_AddInitialPost(t *testing.T) { CommitmentATX: types.RandomATXID(), VRFNonce: 3, } - err := AddInitialPost(db, nodeID, post) + err := AddPost(db, nodeID, post) require.NoError(t, err) - got, err := InitialPost(db, nodeID) + got, err := GetPost(db, nodeID) require.NoError(t, err) require.NotNil(t, got) require.Equal(t, post, *got) - - err = RemoveInitialPost(db, nodeID) - require.NoError(t, err) - - got, err = InitialPost(db, nodeID) - require.ErrorIs(t, err, sql.ErrNotFound) - require.Nil(t, got) } -func Test_AddInitialPost_NoDuplicates(t *testing.T) { +func Test_OverwritePost(t *testing.T) { db := localsql.InMemory() nodeID := types.RandomNodeID() post := Post{ - Nonce: 1, - Indices: []byte{1, 2, 3}, - Pow: 1, + Nonce: 1, + Indices: []byte{1, 2, 3}, + Pow: 1, + Challenge: shared.ZeroChallenge, NumUnits: 2, CommitmentATX: types.RandomATXID(), VRFNonce: 3, } - err := AddInitialPost(db, nodeID, post) + require.NoError(t, AddPost(db, nodeID, post)) + + got, err := GetPost(db, nodeID) require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, post, *got) - // fail to add new initial post for same node + // Overwrite post2 := Post{ - Nonce: 2, - Indices: []byte{1, 2, 3}, - Pow: 1, + Nonce: 11, + Indices: []byte{4, 5, 6}, + Pow: 11, + Challenge: []byte("challenge"), - NumUnits: 4, + NumUnits: 22, CommitmentATX: types.RandomATXID(), - VRFNonce: 5, + VRFNonce: 33, } - err = AddInitialPost(db, nodeID, post2) - require.Error(t, err) + require.NoError(t, AddPost(db, nodeID, post2)) - // succeed to add initial post for different node - err = AddInitialPost(db, types.RandomNodeID(), post2) + got, err = GetPost(db, nodeID) require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, post2, *got) } func Test_AddChallenge(t *testing.T) { diff --git a/sql/migrations/local/0002_next.sql b/sql/migrations/local/0002_next.sql index b3feed4e05..46aabdb502 100644 --- a/sql/migrations/local/0002_next.sql +++ b/sql/migrations/local/0002_next.sql @@ -7,10 +7,15 @@ ALTER TABLE initial_post RENAME COLUMN commit_atx_new TO commit_atx; ALTER TABLE initial_post ADD COLUMN num_units UNSIGNED INT NOT NULL; ALTER TABLE initial_post ADD COLUMN vrf_nonce UNSIGNED LONG INT NOT NULL; +ALTER TABLE initial_post ADD COLUMN challenge VARCHAR NOT NULL; + +ALTER TABLE initial_post RENAME TO post; CREATE TABLE poet_certificates ( - node_id CHAR(32), + node_id CHAR(32) NOT NULL, poet_url VARCHAR NOT NULL, certificate VARCHAR NOT NULL -); \ No newline at end of file +); + +CREATE UNIQUE INDEX idx_poet_certificates ON poet_certificates (node_id, poet_url); From 50fa30fa7f2254d0097a5ae9e7da3b05881deff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 14 Nov 2023 16:33:47 +0700 Subject: [PATCH 15/55] Take optional poet certificates in config --- activation/activation.go | 10 ++-- activation/certifier.go | 92 ++++++++++++++++++++++++++---- activation/certifier_test.go | 27 +++++++-- activation/e2e/poet_test.go | 4 +- activation/interface.go | 14 +++-- activation/mocks.go | 30 +++++----- activation/nipost.go | 4 +- activation/nipost_test.go | 30 ++++------ activation/poet.go | 2 +- config/config.go | 46 +++++++-------- config/presets/testnet.go | 2 +- sql/localsql/nipost/nipost_test.go | 8 ++- 12 files changed, 175 insertions(+), 94 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index f0ff838bae..caa7bbae18 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -83,7 +83,7 @@ type Builder struct { nipostBuilder nipostBuilder validator nipostValidator certifier certifierService - certifierConfig CertifierClientConfig + certifierConfig CertifierConfig // smeshingMutex protects `StartSmeshing` and `StopSmeshing` from concurrent access smeshingMutex sync.Mutex @@ -137,7 +137,7 @@ func WithValidator(v nipostValidator) BuilderOption { } } -func WithCertifierConfig(c CertifierClientConfig) BuilderOption { +func WithCertifierConfig(c CertifierConfig) BuilderOption { return func(b *Builder) { b.certifierConfig = c } @@ -172,7 +172,7 @@ func NewBuilder( syncer: syncer, log: log, poetRetryInterval: defaultPoetRetryInterval, - certifierConfig: DefaultCertifierClientConfig(), + certifierConfig: DefaultCertifierConfig(), } for _, opt := range opts { opt(b) @@ -404,8 +404,8 @@ func (b *Builder) certifyPost(ctx context.Context) { b.log.With().Error("failed to obtain post for certification", zap.Error(err)) } - client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig)) - b.certifier = NewCertifier(b.localDB, b.log, client) + client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig.Client)) + b.certifier = NewCertifier(b.localDB, b.log, client, WithCertificates(b.certifierConfig.Certificates)) b.certifier.CertifyAll(ctx, b.poets) } diff --git a/activation/certifier.go b/activation/certifier.go index ea9f9b9db3..8c7837fd6f 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/ed25519" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -31,6 +32,43 @@ type CertifierClientConfig struct { MaxRetries int `mapstructure:"max-retries"` } +type Base64Enc struct { + Inner []byte +} + +func (e Base64Enc) String() string { + return base64.RawStdEncoding.EncodeToString(e.Inner) +} + +// Set implements pflag.Value.Set. +func (e *Base64Enc) Set(value string) error { + return e.UnmarshalText([]byte(value)) +} + +// Type implements pflag.Value.Type. +func (Base64Enc) Type() string { + return "Base64Enc" +} + +func (e *Base64Enc) UnmarshalText(text []byte) error { + b, err := base64.StdEncoding.DecodeString(string(text)) + if err != nil { + return err + } + e.Inner = b + return nil +} + +type Certificate struct { + Poet string `mapstructure:"poet"` + Certificate Base64Enc `mapstructure:"certificate"` +} + +type CertifierConfig struct { + Client CertifierClientConfig `mapstructure:"client"` + Certificates []Certificate `mapstructure:"certificates"` +} + func DefaultCertifierClientConfig() CertifierClientConfig { return CertifierClientConfig{ RetryDelay: 1 * time.Second, @@ -39,6 +77,12 @@ func DefaultCertifierClientConfig() CertifierClientConfig { } } +func DefaultCertifierConfig() CertifierConfig { + return CertifierConfig{ + Client: DefaultCertifierClientConfig(), + } +} + type ProofToCertify struct { Nonce uint32 `json:"nonce"` Indices []byte `json:"indices"` @@ -69,26 +113,50 @@ type Certifier struct { client certifierClient } -func NewCertifier(db *localsql.Database, logger *zap.Logger, client certifierClient) *Certifier { - return &Certifier{ +type NewCertifierOption func(*Certifier) + +func WithCertificates(certs []Certificate) NewCertifierOption { + return func(c *Certifier) { + c.logger.Info("adding certificates", zap.Int("num", len(certs)), zap.Any("certs", certs)) + for _, cert := range certs { + if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Certificate.Inner, cert.Poet); err != nil { + c.logger.Warn("failed to persist poet cert", zap.Error(err)) + } + } + } +} + +func NewCertifier( + db *localsql.Database, + logger *zap.Logger, + client certifierClient, + opts ...NewCertifierOption, +) *Certifier { + c := &Certifier{ client: client, logger: logger, db: db, } + + for _, opt := range opts { + opt(c) + } + + return c } -func (c *Certifier) GetCertificate(poet string) *PoetCert { +func (c *Certifier) GetCertificate(poet string) PoetCert { cert, err := certifier_db.Certificate(c.db, c.client.Id(), poet) switch { case err == nil: - return &PoetCert{Signature: cert} + return cert case !errors.Is(err, sql.ErrNotFound): c.logger.Warn("failed to get certificate", zap.Error(err)) } return nil } -func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) { +func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (PoetCert, error) { url, pubkey, err := poet.CertifierInfo(ctx) if err != nil { return nil, fmt.Errorf("querying certifier info: %w", err) @@ -98,7 +166,7 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), url, err) } - if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Signature, poet.Address()); err != nil { + if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Bytes(), poet.Address()); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } @@ -114,7 +182,7 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri poetsToCertify := []PoetClient{} for _, poet := range poets { if cert := c.GetCertificate(poet.Address()); cert != nil { - certs[poet.Address()] = *cert + certs[poet.Address()] = cert } else { poetsToCertify = append(poetsToCertify, poet) } @@ -180,13 +248,13 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri c.logger.Info( "successfully obtained certificate", zap.Stringer("certifier", svc.url), - zap.Binary("cert", cert.Signature), + zap.Binary("cert", cert.Bytes()), ) for _, poet := range svc.poets { - if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Signature, poet); err != nil { + if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Bytes(), poet); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } - certs[poet] = *cert + certs[poet] = cert } } return certs @@ -244,7 +312,7 @@ func (c *CertifierClient) Id() types.NodeID { return c.postInfo.NodeID } -func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { +func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (PoetCert, error) { request := CertifyRequest{ Proof: ProofToCertify{ Pow: c.post.Pow, @@ -294,5 +362,5 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by if !ed25519.Verify(pubkey, c.postInfo.NodeID[:], certRespose.Signature) { return nil, errors.New("signature is invalid") } - return &PoetCert{Signature: certRespose.Signature}, nil + return certRespose.Signature, nil } diff --git a/activation/certifier_test.go b/activation/certifier_test.go index cef97a7f5f..8240b5b76a 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -29,21 +29,40 @@ func TestPersistsCerts(t *testing.T) { client.EXPECT(). Certify(gomock.Any(), &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). - Return(&activation.PoetCert{Signature: []byte("cert")}, nil) + Return(activation.PoetCert("cert"), nil) require.Nil(t, certifier.GetCertificate("http://poet")) certs, err := certifier.Recertify(context.Background(), poetMock) require.NoError(t, err) - require.Equal(t, []byte("cert"), certs.Signature) + require.Equal(t, activation.PoetCert("cert"), certs) cert := certifier.GetCertificate("http://poet") - require.Equal(t, []byte("cert"), cert.Signature) + require.Equal(t, activation.PoetCert("cert"), cert) require.Nil(t, certifier.GetCertificate("http://other-poet")) } { // Create new certifier and check that it loads the certs back. certifier := activation.NewCertifier(db, zaptest.NewLogger(t), client) cert := certifier.GetCertificate("http://poet") - require.Equal(t, []byte("cert"), cert.Signature) + require.Equal(t, activation.PoetCert("cert"), cert) } } + +func TestSeedWithCerts(t *testing.T) { + client := activation.NewMockcertifierClient(gomock.NewController(t)) + client.EXPECT().Id().AnyTimes().Return(types.RandomNodeID()) + + certs := []activation.Certificate{ + { + Poet: "poet1", + Certificate: activation.Base64Enc{Inner: []byte("cert1")}, + }, + { + Poet: "poet2", + Certificate: activation.Base64Enc{Inner: []byte("cert2")}, + }, + } + c := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), client, activation.WithCertificates(certs)) + require.Equal(t, activation.PoetCert("cert1"), c.GetCertificate("poet1")) + require.Equal(t, activation.PoetCert("cert2"), c.GetCertificate("poet2")) +} diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index 88e99c5ac1..d7f1bb469a 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -136,7 +136,7 @@ func TestHTTPPoet(t *testing.T) { signature, signer.NodeID(), activation.PoetAuth{ - PoetCert: &activation.PoetCert{Signature: ed25519.Sign(certPrivKey, signer.NodeID().Bytes())}, + PoetCert: ed25519.Sign(certPrivKey, signer.NodeID().Bytes()), }, ) require.NoError(t, err) @@ -150,7 +150,7 @@ func TestHTTPPoet(t *testing.T) { ch.Bytes(), signature, signer.NodeID(), - activation.PoetAuth{PoetCert: &activation.PoetCert{Signature: []byte("oops")}}, + activation.PoetAuth{PoetCert: activation.PoetCert("oops")}, ) require.ErrorIs(t, err, activation.ErrUnathorized) }) diff --git a/activation/interface.go b/activation/interface.go index d2c70bdc70..f1c8929c26 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -94,13 +94,15 @@ type SmeshingProvider interface { SetCoinbase(coinbase types.Address) } -type PoetCert struct { - Signature []byte +type PoetCert []byte + +func (c PoetCert) Bytes() []byte { + return c } type PoetAuth struct { *PoetPoW - *PoetCert + PoetCert } // PoetClient servers as an interface to communicate with a PoET server. @@ -138,17 +140,17 @@ type certifierClient interface { // The ID for which this client certifies. Id() types.NodeID // Certify the ID in the given certifier. - Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) + Certify(ctx context.Context, url *url.URL, pubkey []byte) (PoetCert, error) } // certifierService is used to certify nodeID for registerting in the poet. // It holds the certificates and can recertify if needed. type certifierService interface { // Acquire a certificate for the given poet. - GetCertificate(poet string) *PoetCert + GetCertificate(poet string) PoetCert // Recertify the nodeID and return a certificate confirming that // it is verified. The certificate can be later used to submit in poet. - Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) + Recertify(ctx context.Context, poet PoetClient) (PoetCert, error) // Certify the nodeID for all given poets. // It won't recertify poets that already have a certificate. diff --git a/activation/mocks.go b/activation/mocks.go index c97d6f5505..8ebb7674a9 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1577,10 +1577,10 @@ func (m *MockcertifierClient) EXPECT() *MockcertifierClientMockRecorder { } // Certify mocks base method. -func (m *MockcertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*PoetCert, error) { +func (m *MockcertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (PoetCert, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Certify", ctx, url, pubkey) - ret0, _ := ret[0].(*PoetCert) + ret0, _ := ret[0].(PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1598,19 +1598,19 @@ type certifierClientCertifyCall struct { } // Return rewrite *gomock.Call.Return -func (c *certifierClientCertifyCall) Return(arg0 *PoetCert, arg1 error) *certifierClientCertifyCall { +func (c *certifierClientCertifyCall) Return(arg0 PoetCert, arg1 error) *certifierClientCertifyCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *certifierClientCertifyCall) Do(f func(context.Context, *url.URL, []byte) (*PoetCert, error)) *certifierClientCertifyCall { +func (c *certifierClientCertifyCall) Do(f func(context.Context, *url.URL, []byte) (PoetCert, error)) *certifierClientCertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *certifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.URL, []byte) (*PoetCert, error)) *certifierClientCertifyCall { +func (c *certifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.URL, []byte) (PoetCert, error)) *certifierClientCertifyCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1715,10 +1715,10 @@ func (c *certifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []P } // GetCertificate mocks base method. -func (m *MockcertifierService) GetCertificate(poet string) *PoetCert { +func (m *MockcertifierService) GetCertificate(poet string) PoetCert { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCertificate", poet) - ret0, _ := ret[0].(*PoetCert) + ret0, _ := ret[0].(PoetCert) return ret0 } @@ -1735,28 +1735,28 @@ type certifierServiceGetCertificateCall struct { } // Return rewrite *gomock.Call.Return -func (c *certifierServiceGetCertificateCall) Return(arg0 *PoetCert) *certifierServiceGetCertificateCall { +func (c *certifierServiceGetCertificateCall) Return(arg0 PoetCert) *certifierServiceGetCertificateCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *certifierServiceGetCertificateCall) Do(f func(string) *PoetCert) *certifierServiceGetCertificateCall { +func (c *certifierServiceGetCertificateCall) Do(f func(string) PoetCert) *certifierServiceGetCertificateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *certifierServiceGetCertificateCall) DoAndReturn(f func(string) *PoetCert) *certifierServiceGetCertificateCall { +func (c *certifierServiceGetCertificateCall) DoAndReturn(f func(string) PoetCert) *certifierServiceGetCertificateCall { c.Call = c.Call.DoAndReturn(f) return c } // Recertify mocks base method. -func (m *MockcertifierService) Recertify(ctx context.Context, poet PoetClient) (*PoetCert, error) { +func (m *MockcertifierService) Recertify(ctx context.Context, poet PoetClient) (PoetCert, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Recertify", ctx, poet) - ret0, _ := ret[0].(*PoetCert) + ret0, _ := ret[0].(PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1774,19 +1774,19 @@ type certifierServiceRecertifyCall struct { } // Return rewrite *gomock.Call.Return -func (c *certifierServiceRecertifyCall) Return(arg0 *PoetCert, arg1 error) *certifierServiceRecertifyCall { +func (c *certifierServiceRecertifyCall) Return(arg0 PoetCert, arg1 error) *certifierServiceRecertifyCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *certifierServiceRecertifyCall) Do(f func(context.Context, PoetClient) (*PoetCert, error)) *certifierServiceRecertifyCall { +func (c *certifierServiceRecertifyCall) Do(f func(context.Context, PoetClient) (PoetCert, error)) *certifierServiceRecertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *certifierServiceRecertifyCall) DoAndReturn(f func(context.Context, PoetClient) (*PoetCert, error)) *certifierServiceRecertifyCall { +func (c *certifierServiceRecertifyCall) DoAndReturn(f func(context.Context, PoetClient) (PoetCert, error)) *certifierServiceRecertifyCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index 5d2f55ab2e..e84c12264f 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -338,7 +338,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( auth := PoetAuth{ PoetCert: certifier.GetCertificate(client.Address()), } - if auth.PoetCert == nil || auth.PoetCert.Signature == nil { + if auth.PoetCert == nil { logger.Info("missing poet cert - falling back to PoW") powCtx, cancel := withConditionalTimeout(ctx, nb.poetCfg.RequestTimeout) defer cancel() @@ -364,7 +364,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( Params: *powParams, } } else { - logger.Info("registering with a certificate", zap.Binary("cert", auth.PoetCert.Signature)) + logger.Info("registering with a certificate", zap.Binary("cert", auth.PoetCert)) } logger.Debug("submitting challenge to poet proving service") diff --git a/activation/nipost_test.go b/activation/nipost_test.go index e2349ee3ea..1f0fe1058c 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -85,9 +85,7 @@ func TestNIPostBuilderWithMocks(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&PoetCert{ - Signature: []byte("cert"), - }) + certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(PoetCert("cert")) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) @@ -133,9 +131,7 @@ func TestPostSetup(t *testing.T) { t.Cleanup(func() { assert.NoError(t, postProvider.Reset()) }) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&PoetCert{ - Signature: []byte("cert"), - }) + certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(PoetCert("cert")) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) @@ -196,9 +192,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poetProver.Address()).AnyTimes().Return(&PoetCert{ - Signature: []byte("cert"), - }) + certifier.EXPECT().GetCertificate(poetProver.Address()).AnyTimes().Return(PoetCert("cert")) nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -335,12 +329,8 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poets[0].Address()).Return(&PoetCert{ - Signature: []byte("cert"), - }) - certifier.EXPECT().GetCertificate(poets[1].Address()).Return(&PoetCert{ - Signature: []byte("cert"), - }) + certifier.EXPECT().GetCertificate(poets[0].Address()).Return(PoetCert("cert")) + certifier.EXPECT().GetCertificate(poets[1].Address()).Return(PoetCert("cert")) // Act nipost, err := nb.BuildNIPost(context.Background(), &challenge, certifier) @@ -414,7 +404,7 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poets[0].Address()).Return(&PoetCert{Signature: []byte("cert")}) + certifier.EXPECT().GetCertificate(poets[0].Address()).Return(PoetCert("cert")) // No certs - fallback to PoW certifier.EXPECT().GetCertificate(poets[1].Address()).Return(nil) @@ -435,7 +425,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetCfg := PoetConfig{ PhaseShift: layerDuration, } - cert := &PoetCert{Signature: []byte("cert")} + cert := PoetCert("cert") sig, err := signing.NewEdSigner() require.NoError(t, err) @@ -641,7 +631,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { certifier := NewMockcertifierService(ctrl) - invalid := &PoetCert{Signature: []byte("certInvalid")} + invalid := PoetCert("certInvalid") getInvalid := certifier.EXPECT().GetCertificate("http://localhost:9999").Return(invalid) submitFailed := poetProver.EXPECT(). Submit( @@ -656,7 +646,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { After(getInvalid.Call). Return(nil, ErrUnathorized) - valid := &PoetCert{Signature: []byte("certInvalid")} + valid := PoetCert("certInvalid") recertify := certifier.EXPECT().Recertify(gomock.Any(), poetProver).After(submitFailed).Return(valid, nil) submitOK := poetProver.EXPECT(). Submit( @@ -823,7 +813,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { LeafCount: 777, }, } - cert := &PoetCert{Signature: []byte("cert")} + cert := PoetCert("cert") ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) diff --git a/activation/poet.go b/activation/poet.go index 0cbd933e73..bb35e387b6 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -189,7 +189,7 @@ func (c *HTTPPoetClient) Submit( } if auth.PoetCert != nil { request.Certificate = &rpcapi.SubmitRequest_Certificate{ - Signature: auth.PoetCert.Signature, + Signature: auth.PoetCert, } } diff --git a/config/config.go b/config/config.go index 201081e12f..f5d0b8ce51 100644 --- a/config/config.go +++ b/config/config.go @@ -46,28 +46,28 @@ func init() { // Config defines the top level configuration for a spacemesh node. type Config struct { BaseConfig `mapstructure:"main"` - Genesis *GenesisConfig `mapstructure:"genesis"` - PublicMetrics PublicMetrics `mapstructure:"public-metrics"` - Tortoise tortoise.Config `mapstructure:"tortoise"` - P2P p2p.Config `mapstructure:"p2p"` - API grpcserver.Config `mapstructure:"api"` - HARE hareConfig.Config `mapstructure:"hare"` - HARE3 hare3.Config `mapstructure:"hare3"` - HareEligibility eligConfig.Config `mapstructure:"hare-eligibility"` - Beacon beacon.Config `mapstructure:"beacon"` - TIME timeConfig.TimeConfig `mapstructure:"time"` - VM vm.Config `mapstructure:"vm"` - POST activation.PostConfig `mapstructure:"post"` - POSTService activation.PostSupervisorConfig `mapstructure:"post-service"` - POET activation.PoetConfig `mapstructure:"poet"` - SMESHING SmeshingConfig `mapstructure:"smeshing"` - LOGGING LoggerConfig `mapstructure:"logging"` - FETCH fetch.Config `mapstructure:"fetch"` - Bootstrap bootstrap.Config `mapstructure:"bootstrap"` - Sync syncer.Config `mapstructure:"syncer"` - Recovery checkpoint.Config `mapstructure:"recovery"` - Cache datastore.Config `mapstructure:"cache"` - Certifier activation.CertifierClientConfig `mapstructure:"certifier"` + Genesis *GenesisConfig `mapstructure:"genesis"` + PublicMetrics PublicMetrics `mapstructure:"public-metrics"` + Tortoise tortoise.Config `mapstructure:"tortoise"` + P2P p2p.Config `mapstructure:"p2p"` + API grpcserver.Config `mapstructure:"api"` + HARE hareConfig.Config `mapstructure:"hare"` + HARE3 hare3.Config `mapstructure:"hare3"` + HareEligibility eligConfig.Config `mapstructure:"hare-eligibility"` + Beacon beacon.Config `mapstructure:"beacon"` + TIME timeConfig.TimeConfig `mapstructure:"time"` + VM vm.Config `mapstructure:"vm"` + POST activation.PostConfig `mapstructure:"post"` + POSTService activation.PostSupervisorConfig `mapstructure:"post-service"` + POET activation.PoetConfig `mapstructure:"poet"` + SMESHING SmeshingConfig `mapstructure:"smeshing"` + LOGGING LoggerConfig `mapstructure:"logging"` + FETCH fetch.Config `mapstructure:"fetch"` + Bootstrap bootstrap.Config `mapstructure:"bootstrap"` + Sync syncer.Config `mapstructure:"syncer"` + Recovery checkpoint.Config `mapstructure:"recovery"` + Cache datastore.Config `mapstructure:"cache"` + Certifier activation.CertifierConfig `mapstructure:"certifier"` } // DataDir returns the absolute path to use for the node's data. This is the tilde-expanded path given in the config @@ -168,7 +168,7 @@ func DefaultConfig() Config { Sync: syncer.DefaultConfig(), Recovery: checkpoint.DefaultConfig(), Cache: datastore.DefaultConfig(), - Certifier: activation.DefaultCertifierClientConfig(), + Certifier: activation.DefaultCertifierConfig(), } } diff --git a/config/presets/testnet.go b/config/presets/testnet.go index 10c312c6c4..d3b37179db 100644 --- a/config/presets/testnet.go +++ b/config/presets/testnet.go @@ -152,6 +152,6 @@ func testnet() config.Config { }, Recovery: checkpoint.DefaultConfig(), Cache: datastore.DefaultConfig(), - Certifier: activation.DefaultCertifierClientConfig(), + Certifier: activation.DefaultCertifierConfig(), } } diff --git a/sql/localsql/nipost/nipost_test.go b/sql/localsql/nipost/nipost_test.go index 0241b1a17b..6d8566c6d8 100644 --- a/sql/localsql/nipost/nipost_test.go +++ b/sql/localsql/nipost/nipost_test.go @@ -3,6 +3,7 @@ package nipost import ( "testing" + "github.com/spacemeshos/post/shared" "github.com/stretchr/testify/require" "github.com/spacemeshos/go-spacemesh/common/types" @@ -15,9 +16,10 @@ func Test_AddInitialPost(t *testing.T) { nodeID := types.RandomNodeID() post := Post{ - Nonce: 1, - Indices: []byte{1, 2, 3}, - Pow: 1, + Nonce: 1, + Indices: []byte{1, 2, 3}, + Pow: 1, + Challenge: shared.ZeroChallenge, NumUnits: 2, CommitmentATX: types.RandomATXID(), From 2a9210e8d9633f892af8de0448f15d888900675c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 15 Nov 2023 08:36:15 +0700 Subject: [PATCH 16/55] Bump poet to v0.10.0 --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index a17307be88..21ecffa809 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spacemeshos/go-spacemesh -go 1.21.3 +go 1.21.4 require ( cloud.google.com/go/storage v1.35.1 @@ -39,7 +39,7 @@ require ( github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.1.12 github.com/spacemeshos/merkle-tree v0.2.3 - github.com/spacemeshos/poet v0.10.0-rc2 + github.com/spacemeshos/poet v0.10.0 github.com/spacemeshos/post v0.10.1 github.com/spf13/afero v1.10.0 github.com/spf13/cobra v1.8.0 @@ -202,7 +202,7 @@ require ( golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.14.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect diff --git a/go.sum b/go.sum index d9d8b42d5b..966fe34a9b 100644 --- a/go.sum +++ b/go.sum @@ -647,8 +647,8 @@ github.com/spacemeshos/go-scale v1.1.12 h1:O66yfIBaXSCqbxJYlDP6QSI2s9Lz8rvZjPe3q github.com/spacemeshos/go-scale v1.1.12/go.mod h1:loK9wrj9IHxATTrVqIyR2o9SB+E9/SAsiDDXuUfvbA8= github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas= github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww= -github.com/spacemeshos/poet v0.10.0-rc2 h1:qas9awz8c0XaV0nDM/zK977NqJGN0hLOBu3+olLhxDw= -github.com/spacemeshos/poet v0.10.0-rc2/go.mod h1:IfOF+rRxTP7dWXwojf/TIx7HYIZVxst2ZQ6UUtgYpRA= +github.com/spacemeshos/poet v0.10.0 h1:9mY0gwQaXQs8yToIpnR/VK+sbtb1Ur4zHGoWg2lB/1Q= +github.com/spacemeshos/poet v0.10.0/go.mod h1:mdVFGznCU03oA3TXW5OkDchKxFFw4s7+PUVzixjPFuI= github.com/spacemeshos/post v0.10.1 h1:dFWB1xHa4Z+mDqmZjlQxBVn0e5gBs4QS/j50WXfHoac= github.com/spacemeshos/post v0.10.1/go.mod h1:FPa130ioHforcqca0vqJrYjgUsYzTBwivy6lyLy7sKA= github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= @@ -933,8 +933,8 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= From 111576b966a3e9073bd0d6d27beba090d3c87fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 20 Nov 2023 10:08:02 +0700 Subject: [PATCH 17/55] Apply suggestions from code review Co-authored-by: Matthias Fasching <5011972+fasmat@users.noreply.github.com> --- activation/activation.go | 2 +- activation/certifier.go | 2 +- activation/e2e/certifier_client_test.go | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index caa7bbae18..98a61802e6 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -401,7 +401,7 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { func (b *Builder) certifyPost(ctx context.Context) { post, meta, ch, err := b.obtainPostForCertification() if err != nil { - b.log.With().Error("failed to obtain post for certification", zap.Error(err)) + b.log.Error("failed to obtain post for certification", zap.Error(err)) } client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig.Client)) diff --git a/activation/certifier.go b/activation/certifier.go index 8c7837fd6f..7eeec71a90 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -89,7 +89,7 @@ type ProofToCertify struct { Pow uint64 `json:"pow"` } -type ProoToCertifyfMetadata struct { +type ProofToCertifyfMetadata struct { NodeId []byte `json:"node_id"` CommitmentAtxId []byte `json:"commitment_atx_id"` diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 80e999b4b4..60260e017e 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -50,13 +50,12 @@ func TestCertification(t *testing.T) { t.Cleanup(cleanup) t.Cleanup(launchPostSupervisor(t, logger, mgr, grpcCfg, opts)) + var postClient activation.PostClient require.Eventually(t, func() bool { - _, err := svc.Client(sig.NodeID()) + var err error + postClient, err = svc.Client(sig.NodeID()) return err == nil }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") - - postClient, err := svc.Client(sig.NodeID()) - require.NoError(t, err) post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) From 678e2c67f8d26a45ab010302a1bb8c98a93629d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 20 Nov 2023 10:15:44 +0700 Subject: [PATCH 18/55] update nipost_test.go --- activation/e2e/nipost_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index f8bcf8b197..15f4d80d3d 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -184,13 +184,13 @@ func TestNIPostBuilderWithClients(t *testing.T) { t.Cleanup(launchPostSupervisor(t, logger, mgr, grpcCfg, opts)) + var postClient activation.PostClient require.Eventually(t, func() bool { - _, err := svc.Client(sig.NodeID()) + var err error + postClient, err = svc.Client(sig.NodeID()) return err == nil }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") - postClient, err := svc.Client(sig.NodeID()) - require.NoError(t, err) post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) From ca0fcf014580445e66b2596d7e2d3484d10b71de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 20 Nov 2023 10:43:57 +0700 Subject: [PATCH 19/55] fixup ProofToCertifyMetadata struct name --- activation/certifier.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index 7eeec71a90..ed1a525780 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -89,7 +89,7 @@ type ProofToCertify struct { Pow uint64 `json:"pow"` } -type ProofToCertifyfMetadata struct { +type ProofToCertifyMetadata struct { NodeId []byte `json:"node_id"` CommitmentAtxId []byte `json:"commitment_atx_id"` @@ -99,7 +99,7 @@ type ProofToCertifyfMetadata struct { type CertifyRequest struct { Proof ProofToCertify `json:"proof"` - Metadata ProoToCertifyfMetadata `json:"metadata"` + Metadata ProofToCertifyMetadata `json:"metadata"` } type CertifyResponse struct { @@ -319,7 +319,7 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by Nonce: c.post.Nonce, Indices: c.post.Indices, }, - Metadata: ProoToCertifyfMetadata{ + Metadata: ProofToCertifyMetadata{ NodeId: c.postInfo.NodeID[:], CommitmentAtxId: c.postInfo.CommitmentATX[:], NumUnits: c.postInfo.NumUnits, From f80eb4ac2426e8f6dd0f1b6cb793c95d82a651b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 20 Nov 2023 11:09:23 +0700 Subject: [PATCH 20/55] Run poet systests in parallel --- .github/workflows/systest.yml | 4 ++-- systest/Makefile | 2 +- systest/tests/poets_test.go | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/systest.yml b/.github/workflows/systest.yml index a3100bfb2a..042b4f81e2 100644 --- a/.github/workflows/systest.yml +++ b/.github/workflows/systest.yml @@ -50,7 +50,7 @@ jobs: if: ${{ needs.filter-changes.outputs.nondocchanges == 'true' }} needs: - filter-changes - timeout-minutes: 90 + timeout-minutes: 70 steps: - uses: actions/checkout@v4 @@ -108,7 +108,7 @@ jobs: go-version-file: "go.mod" - name: Run tests - timeout-minutes: 80 + timeout-minutes: 60 env: test_id: systest-${{ steps.vars.outputs.sha_short }} label: sanity diff --git a/systest/Makefile b/systest/Makefile index 2b9df3df15..a84dfd5a86 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -6,7 +6,7 @@ test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) certifier_image ?= spacemeshos/certifier-service:bdbae9e09190a91d130fa6739493e391e0403a98 -poet_image ?= spacemeshos/poet:v0.10.0-rc2 +poet_image ?= spacemeshos/poet:v0.10.0 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) test_id ?= systest-$(version_info) diff --git a/systest/tests/poets_test.go b/systest/tests/poets_test.go index 0890d479e1..a4af0bcaf2 100644 --- a/systest/tests/poets_test.go +++ b/systest/tests/poets_test.go @@ -124,6 +124,7 @@ func testPoetDies(t *testing.T, tctx *testcontext.Context, cl *cluster.Cluster) } func TestNodesUsingDifferentPoets(t *testing.T) { + t.Parallel() tctx := testcontext.New(t, testcontext.Labels("sanity")) if tctx.PoetSize < 2 { t.Skip("Skipping test for using different poets - test configured with less then 2 poets") @@ -211,6 +212,7 @@ func TestNodesUsingDifferentPoets(t *testing.T) { // - supporting certificates // TODO: When PoW support is removed, convert this test to verify only the cert path. func TestRegisteringInPoetWithPowAndCert(t *testing.T) { + t.Parallel() tctx := testcontext.New(t, testcontext.Labels("sanity")) tctx.PoetSize = 2 From 4302d657b368b5c6369d858989bc9a7484dc0bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 20 Nov 2023 11:42:11 +0700 Subject: [PATCH 21/55] Add NodeID to certs passed in config --- activation/certifier.go | 7 ++++--- activation/certifier_test.go | 11 ++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index ed1a525780..ac4a992fca 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -60,8 +60,9 @@ func (e *Base64Enc) UnmarshalText(text []byte) error { } type Certificate struct { - Poet string `mapstructure:"poet"` - Certificate Base64Enc `mapstructure:"certificate"` + NodeID types.NodeID `mapstructure:"node_id"` + Poet string `mapstructure:"poet"` + Certificate Base64Enc `mapstructure:"certificate"` } type CertifierConfig struct { @@ -119,7 +120,7 @@ func WithCertificates(certs []Certificate) NewCertifierOption { return func(c *Certifier) { c.logger.Info("adding certificates", zap.Int("num", len(certs)), zap.Any("certs", certs)) for _, cert := range certs { - if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Certificate.Inner, cert.Poet); err != nil { + if err := certifier_db.AddCertificate(c.db, cert.NodeID, cert.Certificate.Inner, cert.Poet); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } } diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 8240b5b76a..25e29bc842 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -50,19 +50,28 @@ func TestPersistsCerts(t *testing.T) { func TestSeedWithCerts(t *testing.T) { client := activation.NewMockcertifierClient(gomock.NewController(t)) - client.EXPECT().Id().AnyTimes().Return(types.RandomNodeID()) + nodeID := types.RandomNodeID() + client.EXPECT().Id().AnyTimes().Return(nodeID) certs := []activation.Certificate{ { + NodeID: nodeID, Poet: "poet1", Certificate: activation.Base64Enc{Inner: []byte("cert1")}, }, { + NodeID: nodeID, Poet: "poet2", Certificate: activation.Base64Enc{Inner: []byte("cert2")}, }, + { + NodeID: types.RandomNodeID(), + Poet: "poet3", + Certificate: activation.Base64Enc{Inner: []byte("cert3")}, + }, } c := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), client, activation.WithCertificates(certs)) require.Equal(t, activation.PoetCert("cert1"), c.GetCertificate("poet1")) require.Equal(t, activation.PoetCert("cert2"), c.GetCertificate("poet2")) + require.Nil(t, c.GetCertificate("poet3")) } From cb33ecf480611a5bdcc064f7219d5b473c3c4592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 20 Nov 2023 14:39:25 +0700 Subject: [PATCH 22/55] Fix certifier config in systests --- systest/parameters/bignet/smesher.json | 4 +++- systest/parameters/fastnet/smesher.json | 4 +++- systest/parameters/longfast/smesher.json | 7 ++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/systest/parameters/bignet/smesher.json b/systest/parameters/bignet/smesher.json index 723fbc6502..e64bb0fcb3 100644 --- a/systest/parameters/bignet/smesher.json +++ b/systest/parameters/bignet/smesher.json @@ -39,6 +39,8 @@ "post-k3": 37 }, "certifier": { - "max-retries": 10 + "client": { + "max-retries": 10 + } } } \ No newline at end of file diff --git a/systest/parameters/fastnet/smesher.json b/systest/parameters/fastnet/smesher.json index 2aae66f950..97e90c6b15 100644 --- a/systest/parameters/fastnet/smesher.json +++ b/systest/parameters/fastnet/smesher.json @@ -30,6 +30,8 @@ "servers-metrics": true }, "certifier": { - "max-retries": 10 + "client": { + "max-retries": 10 + } } } \ No newline at end of file diff --git a/systest/parameters/longfast/smesher.json b/systest/parameters/longfast/smesher.json index 7940ec065a..37121a04f8 100644 --- a/systest/parameters/longfast/smesher.json +++ b/systest/parameters/longfast/smesher.json @@ -28,5 +28,10 @@ "beacon-rounds-number": 10, "beacon-voting-round-duration": "10s", "beacon-weak-coin-round-duration": "10s" + }, + "certifier": { + "client": { + "max-retries": 10 + } } -} +} \ No newline at end of file From 604bd51ec87850e55a48623e6221e430f8ee6fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 21 Nov 2023 08:50:08 +0700 Subject: [PATCH 23/55] Rename NewCertifierOption --- activation/certifier.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index ac4a992fca..6d2935b988 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -114,9 +114,9 @@ type Certifier struct { client certifierClient } -type NewCertifierOption func(*Certifier) +type CertifierOption func(*Certifier) -func WithCertificates(certs []Certificate) NewCertifierOption { +func WithCertificates(certs []Certificate) CertifierOption { return func(c *Certifier) { c.logger.Info("adding certificates", zap.Int("num", len(certs)), zap.Any("certs", certs)) for _, cert := range certs { @@ -131,7 +131,7 @@ func NewCertifier( db *localsql.Database, logger *zap.Logger, client certifierClient, - opts ...NewCertifierOption, + opts ...CertifierOption, ) *Certifier { c := &Certifier{ client: client, From ce012cade7a5653ddbf83e726f0f4e50086e2ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 21 Nov 2023 13:45:17 +0700 Subject: [PATCH 24/55] Apply localdb code migrations on checkpoint recovery --- checkpoint/recovery.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/checkpoint/recovery.go b/checkpoint/recovery.go index 72e7cd8c2e..ec35cba329 100644 --- a/checkpoint/recovery.go +++ b/checkpoint/recovery.go @@ -107,7 +107,10 @@ func Recover( return nil, fmt.Errorf("open old database: %w", err) } defer db.Close() - localDB, err := localsql.Open("file:" + filepath.Join(cfg.DataDir, cfg.LocalDbFile)) + localDB, err := localsql.Open( + "file:"+filepath.Join(cfg.DataDir, cfg.LocalDbFile), + sql.WithMigration(localsql.New0002Migration(cfg.DataDir)), + ) if err != nil { return nil, fmt.Errorf("open old local database: %w", err) } From 97df610436233c75f46404b08561b05006647d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 22 Nov 2023 22:59:38 +0700 Subject: [PATCH 25/55] Remove importing certificates --- activation/activation.go | 2 +- activation/certifier.go | 27 +-------------------------- activation/certifier_test.go | 28 ---------------------------- 3 files changed, 2 insertions(+), 55 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index a27faa5db9..62f33e9cad 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -405,7 +405,7 @@ func (b *Builder) certifyPost(ctx context.Context) { } client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig.Client)) - b.certifier = NewCertifier(b.localDB, b.log, client, WithCertificates(b.certifierConfig.Certificates)) + b.certifier = NewCertifier(b.localDB, b.log, client) b.certifier.CertifyAll(ctx, b.poets) } diff --git a/activation/certifier.go b/activation/certifier.go index 6d2935b988..872baaae22 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -59,15 +59,8 @@ func (e *Base64Enc) UnmarshalText(text []byte) error { return nil } -type Certificate struct { - NodeID types.NodeID `mapstructure:"node_id"` - Poet string `mapstructure:"poet"` - Certificate Base64Enc `mapstructure:"certificate"` -} - type CertifierConfig struct { - Client CertifierClientConfig `mapstructure:"client"` - Certificates []Certificate `mapstructure:"certificates"` + Client CertifierClientConfig `mapstructure:"client"` } func DefaultCertifierClientConfig() CertifierClientConfig { @@ -114,24 +107,10 @@ type Certifier struct { client certifierClient } -type CertifierOption func(*Certifier) - -func WithCertificates(certs []Certificate) CertifierOption { - return func(c *Certifier) { - c.logger.Info("adding certificates", zap.Int("num", len(certs)), zap.Any("certs", certs)) - for _, cert := range certs { - if err := certifier_db.AddCertificate(c.db, cert.NodeID, cert.Certificate.Inner, cert.Poet); err != nil { - c.logger.Warn("failed to persist poet cert", zap.Error(err)) - } - } - } -} - func NewCertifier( db *localsql.Database, logger *zap.Logger, client certifierClient, - opts ...CertifierOption, ) *Certifier { c := &Certifier{ client: client, @@ -139,10 +118,6 @@ func NewCertifier( db: db, } - for _, opt := range opts { - opt(c) - } - return c } diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 25e29bc842..56070ed523 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -47,31 +47,3 @@ func TestPersistsCerts(t *testing.T) { require.Equal(t, activation.PoetCert("cert"), cert) } } - -func TestSeedWithCerts(t *testing.T) { - client := activation.NewMockcertifierClient(gomock.NewController(t)) - nodeID := types.RandomNodeID() - client.EXPECT().Id().AnyTimes().Return(nodeID) - - certs := []activation.Certificate{ - { - NodeID: nodeID, - Poet: "poet1", - Certificate: activation.Base64Enc{Inner: []byte("cert1")}, - }, - { - NodeID: nodeID, - Poet: "poet2", - Certificate: activation.Base64Enc{Inner: []byte("cert2")}, - }, - { - NodeID: types.RandomNodeID(), - Poet: "poet3", - Certificate: activation.Base64Enc{Inner: []byte("cert3")}, - }, - } - c := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), client, activation.WithCertificates(certs)) - require.Equal(t, activation.PoetCert("cert1"), c.GetCertificate("poet1")) - require.Equal(t, activation.PoetCert("cert2"), c.GetCertificate("poet2")) - require.Nil(t, c.GetCertificate("poet3")) -} From 18e8f238486df45ab528c33165d3a66925bd2cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 23 Nov 2023 12:21:46 +0700 Subject: [PATCH 26/55] Build initial post if post cannot be found --- activation/activation.go | 126 ++++++++++++++++------------------ activation/activation_test.go | 64 +++++++++-------- 2 files changed, 91 insertions(+), 99 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 62f33e9cad..55f9ebcd08 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -352,28 +352,12 @@ func (b *Builder) SmesherID() types.NodeID { return b.signer.NodeID() } -func (b *Builder) buildInitialPost(ctx context.Context) error { - // Generate the initial POST if we don't have an ATX... - if _, err := b.cdb.GetLastAtx(b.signer.NodeID()); err == nil { - return nil - } - // ...and if we haven't stored an initial post yet. - _, err := nipost.GetPost(b.localDB, b.signer.NodeID()) - switch { - case err == nil: - b.log.Info("load initial post from db") - return nil - case errors.Is(err, sql.ErrNotFound): - b.log.Info("creating initial post") - default: - return fmt.Errorf("get initial post: %w", err) - } - - // Create the initial post and save it. +// Create the initial post and save it. +func (b *Builder) buildInitialPost(ctx context.Context) (*types.Post, *types.PostInfo, error) { startTime := time.Now() post, postInfo, err := b.proof(ctx, shared.ZeroChallenge) if err != nil { - return fmt.Errorf("post execution: %w", err) + return nil, nil, fmt.Errorf("post execution: %w", err) } metrics.PostDuration.Set(float64(time.Since(startTime).Nanoseconds())) public.PostSeconds.Set(float64(time.Since(startTime))) @@ -389,7 +373,10 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { CommitmentATX: postInfo.CommitmentATX, VRFNonce: *postInfo.Nonce, } - return nipost.AddPost(b.localDB, b.signer.NodeID(), initialPost) + if err := nipost.AddPost(b.localDB, b.signer.NodeID(), initialPost); err != nil { + b.log.Error("failed to save initial post", zap.Error(err)) + } + return post, postInfo, nil } // Obtain certificates for the poets. @@ -398,18 +385,46 @@ func (b *Builder) buildInitialPost(ctx context.Context) error { // submitting to the poets. // // New nodes should call it after the initial POST is created. -func (b *Builder) certifyPost(ctx context.Context) { - post, meta, ch, err := b.obtainPostForCertification() - if err != nil { - b.log.Error("failed to obtain post for certification", zap.Error(err)) - } - +func (b *Builder) certifyPost(ctx context.Context, post *types.Post, meta *types.PostInfo, ch []byte) { client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig.Client)) b.certifier = NewCertifier(b.localDB, b.log, client) b.certifier.CertifyAll(ctx, b.poets) } -func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, []byte, error) { +func (b *Builder) obtainPostFromLastAtx(ctx context.Context) (*types.Post, *types.PostInfo, []byte, error) { + atxid, err := atxs.GetLastIDByNodeID(b.cdb, b.SmesherID()) + if err != nil { + return nil, nil, nil, fmt.Errorf("no existing ATX found: %w", err) + } + atx, err := b.cdb.GetFullAtx(atxid) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to retrieve ATX: %w", err) + } + if atx.NIPost == nil { + return nil, nil, nil, errors.New("no NIPoST found in last ATX") + } + if atx.CommitmentATX == nil { + if commitmentAtx, err := atxs.CommitmentATX(b.cdb, b.SmesherID()); err != nil { + return nil, nil, nil, fmt.Errorf("failed to retrieve commitment ATX: %w", err) + } else { + atx.CommitmentATX = &commitmentAtx + } + } + + b.log.Info("found POST in an existing ATX", zap.String("atx_id", atxid.Hash32().ShortString())) + meta := &types.PostInfo{ + NodeID: b.SmesherID(), + CommitmentATX: *atx.CommitmentATX, + Nonce: atx.VRFNonce, + NumUnits: atx.NumUnits, + LabelsPerUnit: atx.NIPost.PostMetadata.LabelsPerUnit, + } + + return atx.NIPost.Post, meta, atx.NIPost.PostMetadata.Challenge, nil +} + +func (b *Builder) obtainPost(ctx context.Context) (*types.Post, *types.PostInfo, []byte, error) { + b.log.Info("looking for POST for poet certification") post, err := nipost.GetPost(b.localDB, b.signer.NodeID()) switch { case err == nil: @@ -426,6 +441,7 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, [] Indices: post.Indices, Pow: post.Pow, } + b.log.Info("found POST in local DB") return post, meta, challenge, nil case errors.Is(err, sql.ErrNotFound): // no post found @@ -433,58 +449,36 @@ func (b *Builder) obtainPostForCertification() (*types.Post, *types.PostInfo, [] return nil, nil, nil, fmt.Errorf("loading initial post from db: %w", err) } - b.log.Debug("trying to obtain POST from an existing ATX") - atxid, err := atxs.GetLastIDByNodeID(b.cdb, b.SmesherID()) - if err != nil { - return nil, nil, nil, fmt.Errorf("no existing ATX found: %w", err) - } - atx, err := b.cdb.GetFullAtx(atxid) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to retrieve ATX: %w", err) - } - var commitmentAtx *types.ATXID - if commitmentAtx = atx.CommitmentATX; commitmentAtx == nil { - atx, err := atxs.CommitmentATX(b.cdb, b.SmesherID()) - if err != nil { - return nil, nil, nil, fmt.Errorf("cannot determine own commitment ATX: %w", err) - } - commitmentAtx = &atx + b.log.Info("POST not found in local DB. Trying to obtain POST from an existing ATX") + if post, postInfo, ch, err := b.obtainPostFromLastAtx(ctx); err == nil { + return post, postInfo, ch, nil } - if atx.NIPost == nil { - return nil, nil, nil, errors.New("ATX does not contain a NIPost") - } - - b.log.Info("certifying using an existing ATX", zap.Any("atx", atx)) - - meta := &types.PostInfo{ - NodeID: b.SmesherID(), - CommitmentATX: *commitmentAtx, - Nonce: atx.VRFNonce, - NumUnits: atx.NumUnits, - LabelsPerUnit: atx.NIPost.PostMetadata.LabelsPerUnit, - } - - return atx.NIPost.Post, meta, atx.NIPost.PostMetadata.Challenge, nil -} - -func (b *Builder) run(ctx context.Context) { - defer b.log.Info("atx builder stopped") + b.log.Info("POST not found in existing ATXs. Regenerating the initial POST") for { - err := b.buildInitialPost(ctx) + post, postInfo, err := b.buildInitialPost(ctx) if err == nil { - break + return post, postInfo, shared.ZeroChallenge, nil } b.log.Error("failed to generate initial proof:", zap.Error(err)) currentLayer := b.layerClock.CurrentLayer() select { case <-ctx.Done(): - return + return nil, nil, nil, ctx.Err() case <-b.layerClock.AwaitLayer(currentLayer.Add(1)): } } +} + +func (b *Builder) run(ctx context.Context) { + defer b.log.Info("atx builder stopped") - b.certifyPost(ctx) + if post, meta, ch, err := b.obtainPost(ctx); err != nil { + b.log.Error("failed to obtain post for certification", zap.Error(err)) + return + } else { + b.certifyPost(ctx, post, meta, ch) + } for { err := b.PublishActivationTx(ctx) diff --git a/activation/activation_test.go b/activation/activation_test.go index 5729a2ec2f..889bcf44ed 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -292,10 +292,7 @@ func TestBuilder_StartSmeshingCoinbase(t *testing.T) { return nil, nil, ctx.Err() }). AnyTimes() - tab.mValidator.EXPECT(). - Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil) + tab.mclock.EXPECT().CurrentLayer().Return(types.LayerID(0)).AnyTimes() tab.mclock.EXPECT().AwaitLayer(gomock.Any()).Return(make(chan struct{})).AnyTimes() require.NoError(t, tab.StartSmeshing(coinbase)) @@ -317,14 +314,7 @@ func TestBuilder_RestartSmeshing(t *testing.T) { return nil, nil, ctx.Err() }, ) - tab.mValidator.EXPECT().Post( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - ).AnyTimes().Return(nil) + ch := make(chan struct{}) close(ch) tab.mclock.EXPECT().AwaitLayer(gomock.Any()).Return(ch).AnyTimes() @@ -492,7 +482,8 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { tab := newTestBuilder(t, WithPoetConfig(PoetConfig{PhaseShift: layerDuration * 4})) // current layer is too late to be able to build a nipost on time currLayer := (postGenesisEpoch + 1).FirstLayer() - ch := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, postGenesisEpoch, nil) + + ch := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, postGenesisEpoch, &types.ATXID{'c', 'a', 't', 'x'}) nipostData := newNIPostWithChallenge(t, types.HexToHash32("55555"), []byte("66666")) prevAtx := newAtx(t, tab.sig, ch, nipostData, 2, types.Address{}) SignAndFinalizeAtx(tab.sig, prevAtx) @@ -1183,7 +1174,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { WithPoetRetryInterval(retryInterval), ) posEpoch := types.EpochID(0) - challenge := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, posEpoch, nil) + challenge := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, posEpoch, &types.ATXID{'c', 'a', 't', 'x'}) poetBytes := []byte("66666") nipostData := newNIPostWithChallenge(t, types.HexToHash32("55555"), poetBytes) prevAtx := newAtx(t, tab.sig, challenge, nipostData, 2, types.Address{}) @@ -1301,11 +1292,9 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { }, nil, ) - tab.mValidator.EXPECT(). - Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil) - require.NoError(t, tab.buildInitialPost(context.Background())) + + _, _, err := tab.buildInitialPost(context.Background()) + require.NoError(t, err) posEpoch := postGenesisEpoch + 1 challenge := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, posEpoch, nil) @@ -1325,23 +1314,34 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { assertLastAtx(require.New(t), tab.sig.NodeID(), types.BytesToHash(poetByte), atx, vPrevAtx, vPrevAtx, layersPerEpoch) // postClient.Proof() should not be called again - require.NoError(t, tab.buildInitialPost(context.Background())) + _, _, _, err = tab.obtainPost(context.Background()) + require.NoError(t, err) } -func TestBuilder_ObtainPostForCertification(t *testing.T) { - t.Run("no POST or ATX - fail", func(t *testing.T) { +func TestBuilder_obtainPost(t *testing.T) { + t.Run("no POST or ATX - generate", func(t *testing.T) { tab := newTestBuilder(t) - _, _, _, err := tab.obtainPostForCertification() - require.Error(t, err) + tab.mpostClient.EXPECT().Proof(gomock.Any(), shared.ZeroChallenge). + Return( + &types.Post{Indices: make([]byte, 10)}, + &types.PostInfo{ + CommitmentATX: types.RandomATXID(), + Nonce: new(types.VRFPostIndex), + }, + nil, + ) + _, _, _, err := tab.obtainPost(context.Background()) + require.NoError(t, err) }) t.Run("initial POST available", func(t *testing.T) { tab := newTestBuilder(t) tab.mpostClient.EXPECT(). Proof(gomock.Any(), shared.ZeroChallenge). Return(&types.Post{Indices: []byte{1, 2, 3}}, &types.PostInfo{Nonce: new(types.VRFPostIndex)}, nil) - require.NoError(t, tab.buildInitialPost(context.Background())) + _, _, err := tab.buildInitialPost(context.Background()) + require.NoError(t, err) - _, _, ch, err := tab.obtainPostForCertification() + _, _, ch, err := tab.obtainPost(context.Background()) require.NoError(t, err) require.EqualValues(t, shared.ZeroChallenge, ch) }) @@ -1375,7 +1375,7 @@ func TestBuilder_ObtainPostForCertification(t *testing.T) { require.NoError(t, err) require.NoError(t, atxs.Add(tab.cdb, vAtx)) - post, meta, ch, err := tab.obtainPostForCertification() + post, meta, ch, err := tab.obtainPost(context.Background()) require.NoError(t, err) require.Equal(t, commitmentAtxId, meta.CommitmentATX) require.Equal(t, uint32(2), meta.NumUnits) @@ -1397,14 +1397,12 @@ func TestBuilder_InitialPostIsPersisted(t *testing.T) { }, nil, ) - tab.mValidator.EXPECT(). - Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil) - require.NoError(t, tab.buildInitialPost(context.Background())) + _, _, _, err := tab.obtainPost(context.Background()) + require.NoError(t, err) // postClient.Proof() should not be called again - require.NoError(t, tab.buildInitialPost(context.Background())) + _, _, _, err = tab.obtainPost(context.Background()) + require.NoError(t, err) } func TestWaitPositioningAtx(t *testing.T) { From 98defe308a41e682f18ccfc2af59e8f006851212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 23 Nov 2023 12:58:04 +0700 Subject: [PATCH 27/55] Remove sourcegraph/conc dep --- activation/certifier.go | 32 +++++++++++++++++++------------- go.mod | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index 872baaae22..2c774863be 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -14,8 +14,8 @@ import ( "time" "github.com/hashicorp/go-retryablehttp" - "github.com/sourcegraph/conc/iter" "go.uber.org/zap" + "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" @@ -173,19 +173,25 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri poet string } - certifierInfos := iter.Map(poetsToCertify, func(p *PoetClient) *certInfo { - poet := *p - url, pubkey, err := poet.CertifierInfo(ctx) - if err != nil { - c.logger.Warn("failed to query for certifier info", zap.Error(err), zap.String("poet", poet.Address())) + certifierInfos := make([]*certInfo, len(poetsToCertify)) + var eg errgroup.Group + for i, poet := range poetsToCertify { + i, poet := i, poet + eg.Go(func() error { + url, pubkey, err := poet.CertifierInfo(ctx) + if err != nil { + c.logger.Warn("failed to query for certifier info", zap.Error(err), zap.String("poet", poet.Address())) + return nil + } + certifierInfos[i] = &certInfo{ + url: url, + pubkey: pubkey, + poet: poet.Address(), + } return nil - } - return &certInfo{ - url: url, - pubkey: pubkey, - poet: poet.Address(), - } - }) + }) + } + eg.Wait() type certService struct { url *url.URL diff --git a/go.mod b/go.mod index f7551b3411..2e847a3e06 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/prometheus/common v0.45.0 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seehuhn/mt19937 v1.0.0 - github.com/sourcegraph/conc v0.3.0 github.com/spacemeshos/api/release/go v1.24.0 github.com/spacemeshos/economics v0.1.1 github.com/spacemeshos/fixed v0.1.1 @@ -186,6 +185,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spacemeshos/sha256-simd v0.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cast v1.5.1 // indirect From 4eaa867ddf1807ce659c282897e11eae0bab9652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 23 Nov 2023 14:39:19 +0700 Subject: [PATCH 28/55] Bump certifier-service in systests to v0.6.0 --- systest/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systest/Makefile b/systest/Makefile index a84dfd5a86..ad4ec1e68f 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,7 +5,7 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:bdbae9e09190a91d130fa6739493e391e0403a98 +certifier_image ?= spacemeshos/certifier-service:v0.6.0 poet_image ?= spacemeshos/poet:v0.10.0 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) From c1ccfa84109db51570e168513d793996de6ecb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 24 Nov 2023 12:48:26 +0700 Subject: [PATCH 29/55] Don't update post in localdb Persist POST extracted from existing ATX --- activation/activation.go | 23 ++++++++++++++-- activation/activation_test.go | 13 ++++++--- activation/e2e/nipost_test.go | 1 - activation/e2e/validation_test.go | 1 - activation/nipost.go | 20 -------------- activation/nipost_test.go | 20 -------------- node/node.go | 1 - sql/localsql/nipost/nipost.go | 2 +- sql/localsql/nipost/nipost_test.go | 42 ------------------------------ sql/migrations/local/0003_next.sql | 6 ++--- 10 files changed, 35 insertions(+), 94 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 55f9ebcd08..d3e1e18da1 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -410,6 +410,13 @@ func (b *Builder) obtainPostFromLastAtx(ctx context.Context) (*types.Post, *type atx.CommitmentATX = &commitmentAtx } } + if atx.VRFNonce == nil { + if nonce, err := atxs.VRFNonce(b.cdb, b.SmesherID(), b.layerClock.CurrentLayer().GetEpoch()); err != nil { + return nil, nil, nil, fmt.Errorf("failed to retrieve VRF nonce: %w", err) + } else { + atx.VRFNonce = &nonce + } + } b.log.Info("found POST in an existing ATX", zap.String("atx_id", atxid.Hash32().ShortString())) meta := &types.PostInfo{ @@ -428,7 +435,6 @@ func (b *Builder) obtainPost(ctx context.Context) (*types.Post, *types.PostInfo, post, err := nipost.GetPost(b.localDB, b.signer.NodeID()) switch { case err == nil: - b.log.Info("certifying using the post from local DB") meta := &types.PostInfo{ NodeID: b.SmesherID(), CommitmentATX: post.CommitmentATX, @@ -451,10 +457,23 @@ func (b *Builder) obtainPost(ctx context.Context) (*types.Post, *types.PostInfo, b.log.Info("POST not found in local DB. Trying to obtain POST from an existing ATX") if post, postInfo, ch, err := b.obtainPostFromLastAtx(ctx); err == nil { + b.log.Info("found POST in an existing ATX") + postToPersist := nipost.Post{ + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: ch, + NumUnits: postInfo.NumUnits, + CommitmentATX: postInfo.CommitmentATX, + VRFNonce: *postInfo.Nonce, + } + if err := nipost.AddPost(b.localDB, b.signer.NodeID(), postToPersist); err != nil { + b.log.Error("failed to save post", zap.Error(err)) + } return post, postInfo, ch, nil } - b.log.Info("POST not found in existing ATXs. Regenerating the initial POST") + b.log.Info("POST not found in existing ATXs. Generating the initial POST") for { post, postInfo, err := b.buildInitialPost(ctx) if err == nil { diff --git a/activation/activation_test.go b/activation/activation_test.go index 889bcf44ed..c7aee925dc 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -485,7 +485,10 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { ch := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, postGenesisEpoch, &types.ATXID{'c', 'a', 't', 'x'}) nipostData := newNIPostWithChallenge(t, types.HexToHash32("55555"), []byte("66666")) - prevAtx := newAtx(t, tab.sig, ch, nipostData, 2, types.Address{}) + vrf := types.VRFPostIndex(123) + prevAtx := types.NewActivationTx(ch, types.Address{}, nipostData, 2, &vrf) + prevAtx.SetEffectiveNumUnits(2) + prevAtx.SetReceived(time.Now()) SignAndFinalizeAtx(tab.sig, prevAtx) vPrevAtx, err := prevAtx.Verify(0, 1) require.NoError(t, err) @@ -1177,7 +1180,10 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { challenge := newChallenge(1, types.ATXID{1, 2, 3}, types.ATXID{1, 2, 3}, posEpoch, &types.ATXID{'c', 'a', 't', 'x'}) poetBytes := []byte("66666") nipostData := newNIPostWithChallenge(t, types.HexToHash32("55555"), poetBytes) - prevAtx := newAtx(t, tab.sig, challenge, nipostData, 2, types.Address{}) + vrf := types.VRFPostIndex(123) + prevAtx := types.NewActivationTx(challenge, types.Address{}, nipostData, 2, &vrf) + prevAtx.SetEffectiveNumUnits(2) + prevAtx.SetReceived(time.Now()) SignAndFinalizeAtx(tab.sig, prevAtx) vPrevAtx, err := prevAtx.Verify(0, 1) require.NoError(t, err) @@ -1367,7 +1373,8 @@ func TestBuilder_obtainPost(t *testing.T) { LabelsPerUnit: 777, }, } - atx := types.NewActivationTx(challenge, types.Address{}, nipost, 2, nil) + vrf := types.VRFPostIndex(1234) + atx := types.NewActivationTx(challenge, types.Address{}, nipost, 2, &vrf) atx.SetEffectiveNumUnits(2) atx.SetReceived(time.Now()) SignAndFinalizeAtx(tab.sig, atx) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 15f4d80d3d..827f075f00 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -213,7 +213,6 @@ func TestNIPostBuilderWithClients(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), activation.WithPoetClients(client), ) require.NoError(t, err) diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index 20af91269a..cf998bf830 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -101,7 +101,6 @@ func TestValidator_Validate(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), activation.WithPoetClients(client), ) require.NoError(t, err) diff --git a/activation/nipost.go b/activation/nipost.go index e84c12264f..6cdc3b102e 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -21,8 +21,6 @@ import ( "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/metrics/public" "github.com/spacemeshos/go-spacemesh/signing" - "github.com/spacemeshos/go-spacemesh/sql/localsql" - "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) const ( @@ -74,7 +72,6 @@ type NIPostBuilder struct { signer *signing.EdSigner layerClock layerClock poetCfg PoetConfig - localDB *localsql.Database } type NIPostBuilderOption func(*NIPostBuilder) @@ -97,7 +94,6 @@ func NewNIPostBuilder( signer *signing.EdSigner, poetCfg PoetConfig, layerClock layerClock, - localDB *localsql.Database, opts ...NIPostBuilderOption, ) (*NIPostBuilder, error) { b := &NIPostBuilder{ @@ -109,7 +105,6 @@ func NewNIPostBuilder( signer: signer, poetCfg: poetCfg, layerClock: layerClock, - localDB: localDB, } for _, opt := range opts { @@ -289,21 +284,6 @@ func (nb *NIPostBuilder) BuildNIPost( } nb.persistState() - - post := nipost.Post{ - Nonce: proof.Nonce, - Indices: proof.Indices, - Pow: proof.Pow, - Challenge: nb.state.PoetProofRef[:], - NumUnits: postInfo.NumUnits, - CommitmentATX: postInfo.CommitmentATX, - } - if postInfo.Nonce != nil { - post.VRFNonce = *postInfo.Nonce - } - if err := nipost.AddPost(nb.localDB, nb.signer.NodeID(), post); err != nil { - nb.log.Warn("failed to persist post", zap.Error(err)) - } } nb.log.Info("finished nipost construction") diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 1f0fe1058c..492cf8f432 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -15,7 +15,6 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" - "github.com/spacemeshos/go-spacemesh/sql/localsql" ) func defaultPoetServiceMock(ctrl *gomock.Controller, id []byte, address string) *MockPoetClient { @@ -79,7 +78,6 @@ func TestNIPostBuilderWithMocks(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProvider), ) require.NoError(t, err) @@ -121,7 +119,6 @@ func TestPostSetup(t *testing.T) { postProvider.signer, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProvider), ) require.NoError(t, err) @@ -186,7 +183,6 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -209,7 +205,6 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -232,7 +227,6 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -323,7 +317,6 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poets...), ) require.NoError(t, err) @@ -398,7 +391,6 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poets...), ) require.NoError(t, err) @@ -447,7 +439,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -478,7 +469,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -521,7 +511,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -550,7 +539,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -582,7 +570,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -624,7 +611,6 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { sig, PoetConfig{PhaseShift: layerDuration}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -703,7 +689,6 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -736,7 +721,6 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -775,7 +759,6 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(poetProver), ) require.NoError(t, err) @@ -857,7 +840,6 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poet), ) require.NoError(t, err) @@ -1022,7 +1004,6 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { sig, poetCfg, mclock, - localsql.InMemory(), WithPoetClients(poets...), ) require.NoError(t, err) @@ -1108,7 +1089,6 @@ func TestNIPostBuilder_Close(t *testing.T) { sig, PoetConfig{}, mclock, - localsql.InMemory(), WithPoetClients(defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999")), ) require.NoError(t, err) diff --git a/node/node.go b/node/node.go index e9baf97664..3642e406f1 100644 --- a/node/node.go +++ b/node/node.go @@ -918,7 +918,6 @@ func (app *App) initServices(ctx context.Context) error { app.edSgn, app.Config.POET, app.clock, - app.localDB, activation.WithPoetClients(poetClients...), ) if err != nil { diff --git a/sql/localsql/nipost/nipost.go b/sql/localsql/nipost/nipost.go index f46e23e975..127b67eebe 100644 --- a/sql/localsql/nipost/nipost.go +++ b/sql/localsql/nipost/nipost.go @@ -30,7 +30,7 @@ func AddPost(db sql.Executor, nodeID types.NodeID, post Post) error { stmt.BindInt64(8, int64(post.VRFNonce)) } if _, err := db.Exec(` - REPLACE into post ( + INSERT into post ( id, post_nonce, post_indices, post_pow, challenge, num_units, commit_atx, vrf_nonce ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);`, enc, nil, ); err != nil { diff --git a/sql/localsql/nipost/nipost_test.go b/sql/localsql/nipost/nipost_test.go index 796059535d..392dc55cd5 100644 --- a/sql/localsql/nipost/nipost_test.go +++ b/sql/localsql/nipost/nipost_test.go @@ -36,48 +36,6 @@ func Test_AddInitialPost(t *testing.T) { require.Equal(t, post, *got) } -func Test_OverwritePost(t *testing.T) { - db := localsql.InMemory( - sql.WithMigration(localsql.New0002Migration(t.TempDir())), - ) - - nodeID := types.RandomNodeID() - post := Post{ - Nonce: 1, - Indices: []byte{1, 2, 3}, - Pow: 1, - Challenge: shared.ZeroChallenge, - - NumUnits: 2, - CommitmentATX: types.RandomATXID(), - VRFNonce: 3, - } - require.NoError(t, AddPost(db, nodeID, post)) - - got, err := GetPost(db, nodeID) - require.NoError(t, err) - require.NotNil(t, got) - require.Equal(t, post, *got) - - // Overwrite - post2 := Post{ - Nonce: 11, - Indices: []byte{4, 5, 6}, - Pow: 11, - Challenge: []byte("challenge"), - - NumUnits: 22, - CommitmentATX: types.RandomATXID(), - VRFNonce: 33, - } - require.NoError(t, AddPost(db, nodeID, post2)) - - got, err = GetPost(db, nodeID) - require.NoError(t, err) - require.NotNil(t, got) - require.Equal(t, post2, *got) -} - func Test_AddChallenge(t *testing.T) { commitmentATX := types.RandomATXID() tt := []struct { diff --git a/sql/migrations/local/0003_next.sql b/sql/migrations/local/0003_next.sql index 1406a150c7..b47773e9e5 100644 --- a/sql/migrations/local/0003_next.sql +++ b/sql/migrations/local/0003_next.sql @@ -1,12 +1,12 @@ -ALTER TABLE initial_post ADD COLUMN challenge VARCHAR NOT NULL; +ALTER TABLE initial_post ADD COLUMN challenge BLOB NOT NULL DEFAULT x'00000000000000000000000000000000'; ALTER TABLE initial_post RENAME TO post; CREATE TABLE poet_certificates ( - node_id CHAR(32) NOT NULL, + node_id BLOB NOT NULL, poet_url VARCHAR NOT NULL, - certificate VARCHAR NOT NULL + certificate BLOB NOT NULL ); CREATE UNIQUE INDEX idx_poet_certificates ON poet_certificates (node_id, poet_url); From 8439433f67269d2b4ac3f5de460d56eeab089432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 27 Nov 2023 11:25:07 +0700 Subject: [PATCH 30/55] Fix UT --- sql/localsql/nipost/nipost_test.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sql/localsql/nipost/nipost_test.go b/sql/localsql/nipost/nipost_test.go index 65e1c9ac90..c79e0c2b87 100644 --- a/sql/localsql/nipost/nipost_test.go +++ b/sql/localsql/nipost/nipost_test.go @@ -11,7 +11,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql/localsql" ) -func Test_AddInitialPost(t *testing.T) { +func Test_AddPost(t *testing.T) { db := localsql.InMemory() nodeID := types.RandomNodeID() @@ -19,7 +19,7 @@ func Test_AddInitialPost(t *testing.T) { Nonce: 1, Indices: []byte{1, 2, 3}, Pow: 1, - Challenge: shared.ZeroChallenge, + Challenge: shared.Challenge([]byte{4, 5, 6}), NumUnits: 2, CommitmentATX: types.RandomATXID(), @@ -34,14 +34,15 @@ func Test_AddInitialPost(t *testing.T) { require.Equal(t, post, *got) } -func Test_AddInitialPost_NoDuplicates(t *testing.T) { +func Test_AddPost_NoDuplicates(t *testing.T) { db := localsql.InMemory() nodeID := types.RandomNodeID() post := Post{ - Nonce: 1, - Indices: []byte{1, 2, 3}, - Pow: 1, + Nonce: 1, + Indices: []byte{1, 2, 3}, + Pow: 1, + Challenge: shared.ZeroChallenge, NumUnits: 2, CommitmentATX: types.RandomATXID(), @@ -50,11 +51,12 @@ func Test_AddInitialPost_NoDuplicates(t *testing.T) { err := AddPost(db, nodeID, post) require.NoError(t, err) - // fail to add new initial post for same node + // fail to add new post for same node post2 := Post{ - Nonce: 2, - Indices: []byte{1, 2, 3}, - Pow: 1, + Nonce: 2, + Indices: []byte{1, 2, 3}, + Pow: 1, + Challenge: shared.ZeroChallenge, NumUnits: 4, CommitmentATX: types.RandomATXID(), @@ -63,7 +65,7 @@ func Test_AddInitialPost_NoDuplicates(t *testing.T) { err = AddPost(db, nodeID, post2) require.Error(t, err) - // succeed to add initial post for different node + // succeed to add post for different node err = AddPost(db, types.RandomNodeID(), post2) require.NoError(t, err) } From 00edbc333c09dd9a0b01896a66b2ec4a7d7512ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 28 Dec 2023 08:57:22 +0100 Subject: [PATCH 31/55] Fix default value of challenge in initial_post table --- sql/migrations/local/0003_next.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/migrations/local/0003_next.sql b/sql/migrations/local/0003_next.sql index b47773e9e5..f09bcf4cfe 100644 --- a/sql/migrations/local/0003_next.sql +++ b/sql/migrations/local/0003_next.sql @@ -1,4 +1,4 @@ -ALTER TABLE initial_post ADD COLUMN challenge BLOB NOT NULL DEFAULT x'00000000000000000000000000000000'; +ALTER TABLE initial_post ADD COLUMN challenge BLOB NOT NULL DEFAULT x'0000000000000000000000000000000000000000000000000000000000000000'; ALTER TABLE initial_post RENAME TO post; From e81a4c17d87d6c66f0421bf013e0b68efe63bf60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 9 Jan 2024 13:12:39 +0100 Subject: [PATCH 32/55] Fix flaky TestNIPostBuilder_Close --- activation/nipost_test.go | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 6a54199187..299fb76367 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -18,7 +18,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) -func defaultPoetServiceMock(ctrl *gomock.Controller, id []byte, address string) *MockPoetClient { +func defaultPoetServiceMock(ctrl *gomock.Controller, address string) *MockPoetClient { poetClient := NewMockPoetClient(ctrl) poetClient.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). @@ -33,7 +33,6 @@ func defaultLayerClockMock(ctrl *gomock.Controller) *MocklayerClock { mclock := 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)) }, @@ -101,7 +100,7 @@ func Test_NIPostBuilder_WithMocks(t *testing.T) { ctrl := gomock.NewController(t) - poetProvider := defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999") + poetProvider := defaultPoetServiceMock(ctrl, "http://localhost:9999") poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{ PoetProof: types.PoetProof{}, }, []types.Member{types.Member(challenge.Hash())}, nil) @@ -149,7 +148,7 @@ func TestPostSetup(t *testing.T) { require.NoError(t, err) ctrl := gomock.NewController(t) - poetProvider := defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999") + poetProvider := defaultPoetServiceMock(ctrl, "http://localhost:9999") poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProofMessage{ PoetProof: types.PoetProof{}, }, []types.Member{types.Member(challenge.Hash())}, nil) @@ -207,7 +206,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { } ctrl := gomock.NewController(t) - poetProver := defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999") + poetProver := defaultPoetServiceMock(ctrl, "http://localhost:9999") poetProver.EXPECT().Proof(gomock.Any(), "").AnyTimes().Return( &types.PoetProofMessage{ PoetProof: types.PoetProof{}, @@ -420,7 +419,7 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { poets = append(poets, poet) } { - poet := defaultPoetServiceMock(ctrl, []byte("poet1"), "http://localhost:9998") + poet := defaultPoetServiceMock(ctrl, "http://localhost:9998") poet.EXPECT().Proof(gomock.Any(), "").Return(proofBetter, []types.Member{types.Member(challenge.Hash())}, nil) poets = append(poets, poet) } @@ -551,7 +550,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) - poetProver := defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999") + poetProver := defaultPoetServiceMock(ctrl, "http://localhost:9999") poetProver.EXPECT().Proof(gomock.Any(), "").Return(nil, nil, errors.New("failed")) postService := NewMockpostService(ctrl) @@ -580,7 +579,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetDb := NewMockpoetDbAPI(ctrl) poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) mclock := defaultLayerClockMock(ctrl) - poetProver := defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999") + poetProver := defaultPoetServiceMock(ctrl, "http://localhost:9999") poetProver.EXPECT(). Proof(gomock.Any(), ""). Return(&types.PoetProofMessage{PoetProof: types.PoetProof{}}, []types.Member{}, nil) @@ -1095,19 +1094,14 @@ func TestCalculatingGetProofWaitTime(t *testing.T) { func TestNIPostBuilder_Close(t *testing.T) { ctrl := gomock.NewController(t) - - mclock := 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)) - }, - ) - sig, err := signing.NewEdSigner() require.NoError(t, err) + poet := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poet.EXPECT().Proof(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( + func(ctx context.Context, _ string) (*types.PoetProofMessage, []types.Member, error) { + return nil, nil, ctx.Err() + }) nb, err := NewNIPostBuilder( localsql.InMemory(), NewMockpoetDbAPI(ctrl), @@ -1115,8 +1109,8 @@ func TestNIPostBuilder_Close(t *testing.T) { zaptest.NewLogger(t), sig, PoetConfig{}, - mclock, - WithPoetClients(defaultPoetServiceMock(ctrl, []byte("poet"), "http://localhost:9999")), + defaultLayerClockMock(ctrl), + WithPoetClients(poet), ) require.NoError(t, err) @@ -1129,7 +1123,6 @@ func TestNIPostBuilder_Close(t *testing.T) { certifier := NewMockcertifierService(ctrl) certifier.EXPECT().GetCertificate("http://localhost:9999").Return(PoetCert("cert")) - nipost, err := nb.BuildNIPost(ctx, &challenge, certifier) + _, err = nb.BuildNIPost(ctx, &challenge, certifier) require.Error(t, err) - require.Nil(t, nipost) } From c05355201eb701f4a7fe74c3a083aa068e8e0067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 12 Feb 2024 12:53:06 +0100 Subject: [PATCH 33/55] Fixes --- activation/activation.go | 25 ++++++++++++++++--------- activation/activation_multi_test.go | 4 ++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 31422083ed..9269eb0f88 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -75,7 +75,6 @@ type Builder struct { publisher pubsub.Publisher nipostBuilder nipostBuilder validator nipostValidator - certifier certifierService certifierConfig CertifierConfig layerClock layerClock syncer syncer @@ -91,6 +90,7 @@ type Builder struct { // since they (can) modify the fields below. smeshingMutex sync.Mutex signers map[types.NodeID]*signing.EdSigner + certifiers map[types.NodeID]certifierService eg errgroup.Group stop context.CancelFunc } @@ -169,6 +169,7 @@ func NewBuilder( log: log, poetRetryInterval: defaultPoetRetryInterval, postValidityDelay: 12 * time.Hour, + certifiers: make(map[types.NodeID]certifierService), certifierConfig: DefaultCertifierConfig(), } for _, opt := range opts { @@ -324,12 +325,14 @@ func (b *Builder) buildInitialPost(ctx context.Context, nodeId types.NodeID) (*t // We want to certify immediately after the startup or creating the initial POST // to avoid all nodes spamming the certifier at the same time when // submitting to the poets. -// -// New nodes should call it after the initial POST is created. func (b *Builder) certifyPost(ctx context.Context, post *types.Post, meta *types.PostInfo, ch []byte) { client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig.Client)) - b.certifier = NewCertifier(b.localDB, b.log, client) - b.certifier.CertifyAll(ctx, b.poets) + certifier := NewCertifier(b.localDB, b.log, client) + certifier.CertifyAll(ctx, b.poets) + + b.smeshingMutex.Lock() + b.certifiers[meta.NodeID] = certifier + b.smeshingMutex.Unlock() } func (b *Builder) obtainPostFromLastAtx( @@ -436,12 +439,12 @@ func (b *Builder) obtainPost(ctx context.Context, nodeId types.NodeID) (*types.P func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { defer b.log.Info("atx builder stopped") - if post, meta, ch, err := b.obtainPost(ctx, sig.NodeID()); err != nil { + post, meta, ch, err := b.obtainPost(ctx, sig.NodeID()) + if err != nil { b.log.Error("failed to obtain post for certification", zap.Error(err)) return - } else { - b.certifyPost(ctx, post, meta, ch) } + b.certifyPost(ctx, post, meta, ch) for { err := b.PublishActivationTx(ctx, sig) @@ -665,7 +668,11 @@ func (b *Builder) createAtx( ) (*types.ActivationTx, error) { pubEpoch := challenge.PublishEpoch - nipostState, err := b.nipostBuilder.BuildNIPost(ctx, sig, challenge, b.certifier) + b.smeshingMutex.Lock() + certifier := b.certifiers[sig.NodeID()] + b.smeshingMutex.Unlock() + + nipostState, err := b.nipostBuilder.BuildNIPost(ctx, sig, challenge, certifier) if err != nil { return nil, fmt.Errorf("build NIPost: %w", err) } diff --git a/activation/activation_multi_test.go b/activation/activation_multi_test.go index b2e139794f..7a77367ca6 100644 --- a/activation/activation_multi_test.go +++ b/activation/activation_multi_test.go @@ -234,11 +234,11 @@ func Test_Builder_Multi_InitialPost(t *testing.T) { }, nil, ) - _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) + _, _, _, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) // postClient.Proof() should not be called again - _, _, err = tab.buildInitialPost(context.Background(), sig.NodeID()) + _, _, _, err = tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) return nil }) From 7319d84593955c24340e19c68ba684417c214a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 12 Feb 2024 14:55:58 +0100 Subject: [PATCH 34/55] Bump post and certifier services in systests --- systest/Makefile | 4 ++-- systest/parameters/bignet/certifier.yaml | 1 - systest/parameters/fastnet/certifier.yaml | 1 - systest/parameters/longfast/certifier.yaml | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/systest/Makefile b/systest/Makefile index e5f39d7bd7..ec074bb29a 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,9 +5,9 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:v0.6.0 +certifier_image ?= spacemeshos/certifier-service:v0.7.0 poet_image ?= $(org)/poet:v0.10.2 -post_service_image ?= $(org)/post-service:v0.6.5 +post_service_image ?= $(org)/post-service:v0.7.0 post_init_image ?= $(org)/postcli:v0.10.4 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) diff --git a/systest/parameters/bignet/certifier.yaml b/systest/parameters/bignet/certifier.yaml index 2190dbd3c8..fb92d02979 100644 --- a/systest/parameters/bignet/certifier.yaml +++ b/systest/parameters/bignet/certifier.yaml @@ -4,7 +4,6 @@ metrics: "0.0.0.0:2112" post_cfg: k1: 12 k2: 4 - k3: 4 pow_difficulty: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" init_cfg: min_num_units: 2 diff --git a/systest/parameters/fastnet/certifier.yaml b/systest/parameters/fastnet/certifier.yaml index 2190dbd3c8..fb92d02979 100644 --- a/systest/parameters/fastnet/certifier.yaml +++ b/systest/parameters/fastnet/certifier.yaml @@ -4,7 +4,6 @@ metrics: "0.0.0.0:2112" post_cfg: k1: 12 k2: 4 - k3: 4 pow_difficulty: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" init_cfg: min_num_units: 2 diff --git a/systest/parameters/longfast/certifier.yaml b/systest/parameters/longfast/certifier.yaml index 2190dbd3c8..fb92d02979 100644 --- a/systest/parameters/longfast/certifier.yaml +++ b/systest/parameters/longfast/certifier.yaml @@ -4,7 +4,6 @@ metrics: "0.0.0.0:2112" post_cfg: k1: 12 k2: 4 - k3: 4 pow_difficulty: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" init_cfg: min_num_units: 2 From 97f0ff1cfd3025a7241cf91b7510791257369573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 21 Mar 2024 18:26:16 +0100 Subject: [PATCH 35/55] Support for poet certs with expiration --- activation/certifier.go | 48 +++++++++++---- activation/certifier_test.go | 24 ++++---- activation/e2e/certifier_client_test.go | 12 +++- activation/e2e/poet_test.go | 13 +++- activation/interface.go | 17 ++---- activation/mocks.go | 41 +++++++------ activation/nipost.go | 2 +- activation/nipost_test.go | 61 ++++++++++--------- activation/poet.go | 3 +- cmd/merge-nodes/internal/merge_action_test.go | 2 +- go.mod | 4 +- go.sum | 8 +-- sql/localsql/certifier/db.go | 28 ++++++--- sql/localsql/certifier/db_test.go | 23 ++++--- sql/migrations/local/0007_next.sql | 3 +- systest/Makefile | 4 +- 16 files changed, 172 insertions(+), 121 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index 2c774863be..98296ddb3b 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -14,12 +14,15 @@ import ( "time" "github.com/hashicorp/go-retryablehttp" + "github.com/spacemeshos/poet/shared" "go.uber.org/zap" "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" certifier_db "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) @@ -97,8 +100,9 @@ type CertifyRequest struct { } type CertifyResponse struct { - Signature []byte `json:"signature"` - PubKey []byte `json:"pub_key"` + Certificate []byte `json:"certificate"` + Signature []byte `json:"signature"` + PubKey []byte `json:"pub_key"` } type Certifier struct { @@ -121,7 +125,7 @@ func NewCertifier( return c } -func (c *Certifier) GetCertificate(poet string) PoetCert { +func (c *Certifier) GetCertificate(poet string) *certifier.PoetCert { cert, err := certifier_db.Certificate(c.db, c.client.Id(), poet) switch { case err == nil: @@ -132,7 +136,7 @@ func (c *Certifier) GetCertificate(poet string) PoetCert { return nil } -func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (PoetCert, error) { +func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) { url, pubkey, err := poet.CertifierInfo(ctx) if err != nil { return nil, fmt.Errorf("querying certifier info: %w", err) @@ -142,7 +146,7 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (PoetCert, e return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), url, err) } - if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Bytes(), poet.Address()); err != nil { + if err := certifier_db.AddCertificate(c.db, c.client.Id(), *cert, poet.Address()); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } @@ -153,8 +157,8 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (PoetCert, e // It optimizes the number of certification requests by taking a unique set of // certifiers among the given poets and sending a single request to each of them. // It returns a map of a poet address to a certificate for it. -func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[string]PoetCert { - certs := make(map[string]PoetCert) +func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert { + certs := make(map[string]*certifier.PoetCert) poetsToCertify := []PoetClient{} for _, poet := range poets { if cert := c.GetCertificate(poet.Address()); cert != nil { @@ -230,10 +234,11 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri c.logger.Info( "successfully obtained certificate", zap.Stringer("certifier", svc.url), - zap.Binary("cert", cert.Bytes()), + zap.Binary("cert data", cert.Data), + zap.Binary("cert signature", cert.Signature), ) for _, poet := range svc.poets { - if err := certifier_db.AddCertificate(c.db, c.client.Id(), cert.Bytes(), poet); err != nil { + if err := certifier_db.AddCertificate(c.db, c.client.Id(), *cert, poet); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } certs[poet] = cert @@ -269,7 +274,7 @@ func NewCertifierClient( ) *CertifierClient { c := &CertifierClient{ client: retryablehttp.NewClient(), - logger: logger, + logger: logger.With(log.ZShortStringer("smesherID", postInfo.NodeID)), post: post, postInfo: postInfo, postCh: postCh, @@ -294,7 +299,7 @@ func (c *CertifierClient) Id() types.NodeID { return c.postInfo.NodeID } -func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (PoetCert, error) { +func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) { request := CertifyRequest{ Proof: ProofToCertify{ Pow: c.post.Pow, @@ -341,8 +346,25 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by if !bytes.Equal(certRespose.PubKey, pubkey) { return nil, errors.New("pubkey is invalid") } - if !ed25519.Verify(pubkey, c.postInfo.NodeID[:], certRespose.Signature) { + if !ed25519.Verify(pubkey, certRespose.Certificate, certRespose.Signature) { return nil, errors.New("signature is invalid") } - return certRespose.Signature, nil + cert, err := shared.DecodeCert(certRespose.Certificate) + if err != nil { + return nil, fmt.Errorf("decoding certificate: %w", err) + } + if !bytes.Equal(cert.Pubkey, c.Id().Bytes()) { + return nil, errors.New("certificate pubkey doesn't match node ID") + } + if cert.Expiration != nil { + c.logger.Info("certificate has expiration date", zap.Time("expiration", *cert.Expiration)) + if time.Until(*cert.Expiration) < 0 { + return nil, errors.New("certificate is expired") + } + } + + return &certifier.PoetCert{ + Data: certRespose.Certificate, + Signature: certRespose.Signature, + }, nil } diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 56070ed523..6a8f205f4f 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -12,14 +12,16 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) func TestPersistsCerts(t *testing.T) { client := activation.NewMockcertifierClient(gomock.NewController(t)) client.EXPECT().Id().AnyTimes().Return(types.RandomNodeID()) db := localsql.InMemory() + cert := &certifier.PoetCert{Data: []byte("cert"), Signature: []byte("sig")} { - certifier := activation.NewCertifier(db, zaptest.NewLogger(t), client) + c := activation.NewCertifier(db, zaptest.NewLogger(t), client) poetMock := activation.NewMockPoetClient(gomock.NewController(t)) poetMock.EXPECT().Address().Return("http://poet") @@ -29,21 +31,21 @@ func TestPersistsCerts(t *testing.T) { client.EXPECT(). Certify(gomock.Any(), &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). - Return(activation.PoetCert("cert"), nil) + Return(cert, nil) - require.Nil(t, certifier.GetCertificate("http://poet")) - certs, err := certifier.Recertify(context.Background(), poetMock) + require.Nil(t, c.GetCertificate("http://poet")) + got, err := c.Recertify(context.Background(), poetMock) require.NoError(t, err) - require.Equal(t, activation.PoetCert("cert"), certs) + require.Equal(t, cert, got) - cert := certifier.GetCertificate("http://poet") - require.Equal(t, activation.PoetCert("cert"), cert) - require.Nil(t, certifier.GetCertificate("http://other-poet")) + got = c.GetCertificate("http://poet") + require.Equal(t, cert, got) + require.Nil(t, c.GetCertificate("http://other-poet")) } { // Create new certifier and check that it loads the certs back. - certifier := activation.NewCertifier(db, zaptest.NewLogger(t), client) - cert := certifier.GetCertificate("http://poet") - require.Equal(t, activation.PoetCert("cert"), cert) + c := activation.NewCertifier(db, zaptest.NewLogger(t), client) + got := c.GetCertificate("http://poet") + require.Equal(t, cert, got) } } diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 35de06142e..e915c527e0 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/spacemeshos/poet/registration" + poetShared "github.com/spacemeshos/poet/shared" "github.com/spacemeshos/post/initialization" "github.com/spacemeshos/post/shared" "github.com/spacemeshos/post/verifying" @@ -150,10 +151,15 @@ func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { return } - // Certify nodeID + certData, err := poetShared.EncodeCert(&poetShared.Cert{Pubkey: req.Metadata.NodeId}) + if err != nil { + panic(fmt.Sprintf("encoding cert: %v", err)) + } + resp := activation.CertifyResponse{ - Signature: ed25519.Sign(c.privKey, req.Metadata.NodeId), - PubKey: c.privKey.Public().(ed25519.PublicKey), + Certificate: certData, + Signature: ed25519.Sign(c.privKey, certData), + PubKey: c.privKey.Public().(ed25519.PublicKey), } if err := json.NewEncoder(w).Encode(resp); err != nil { http.Error(w, fmt.Sprintf("encoding response: %v", err), http.StatusInternalServerError) diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index 0be801f717..138d6906b6 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -11,6 +11,7 @@ import ( "github.com/spacemeshos/poet/registration" "github.com/spacemeshos/poet/server" + "github.com/spacemeshos/poet/shared" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" @@ -18,6 +19,7 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) // HTTPPoetTestHarness utilizes a local self-contained poet server instance @@ -139,6 +141,10 @@ func TestHTTPPoet(t *testing.T) { prefix := bytes.Join([][]byte{signer.Prefix(), {byte(signing.POET)}}, nil) t.Run("submit with cert", func(t *testing.T) { + cert := shared.Cert{Pubkey: signer.NodeID().Bytes()} + encoded, err := shared.EncodeCert(&cert) + require.NoError(t, err) + poetRound, err := client.Submit( context.Background(), time.Time{}, @@ -147,7 +153,10 @@ func TestHTTPPoet(t *testing.T) { signature, signer.NodeID(), activation.PoetAuth{ - PoetCert: ed25519.Sign(certPrivKey, signer.NodeID().Bytes()), + PoetCert: &certifier.PoetCert{ + Data: encoded, + Signature: ed25519.Sign(certPrivKey, encoded), + }, }, ) require.NoError(t, err) @@ -161,7 +170,7 @@ func TestHTTPPoet(t *testing.T) { ch.Bytes(), signature, signer.NodeID(), - activation.PoetAuth{PoetCert: activation.PoetCert("oops")}, + activation.PoetAuth{PoetCert: &certifier.PoetCert{Data: []byte("oops")}}, ) require.ErrorIs(t, err, activation.ErrUnathorized) }) diff --git a/activation/interface.go b/activation/interface.go index 946a54d01c..044f067511 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -12,6 +12,7 @@ import ( "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) @@ -119,15 +120,9 @@ type SmeshingProvider interface { SetCoinbase(coinbase types.Address) } -type PoetCert []byte - -func (c PoetCert) Bytes() []byte { - return c -} - type PoetAuth struct { *PoetPoW - PoetCert + *certifier.PoetCert } // PoetClient servers as an interface to communicate with a PoET server. @@ -162,22 +157,22 @@ type certifierClient interface { // The ID for which this client certifies. Id() types.NodeID // Certify the ID in the given certifier. - Certify(ctx context.Context, url *url.URL, pubkey []byte) (PoetCert, error) + Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) } // certifierService is used to certify nodeID for registerting in the poet. // It holds the certificates and can recertify if needed. type certifierService interface { // Acquire a certificate for the given poet. - GetCertificate(poet string) PoetCert + GetCertificate(poet string) *certifier.PoetCert // Recertify the nodeID and return a certificate confirming that // it is verified. The certificate can be later used to submit in poet. - Recertify(ctx context.Context, poet PoetClient) (PoetCert, error) + Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) // Certify the nodeID for all given poets. // It won't recertify poets that already have a certificate. // It returns a map of a poet address to a certificate for it. - CertifyAll(ctx context.Context, poets []PoetClient) map[string]PoetCert + CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert } type poetDbAPI interface { diff --git a/activation/mocks.go b/activation/mocks.go index 4f3980a02b..8365096659 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -17,6 +17,7 @@ import ( types "github.com/spacemeshos/go-spacemesh/common/types" signing "github.com/spacemeshos/go-spacemesh/signing" + certifier "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" nipost "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" shared "github.com/spacemeshos/post/shared" verifying "github.com/spacemeshos/post/verifying" @@ -1731,10 +1732,10 @@ func (m *MockcertifierClient) EXPECT() *MockcertifierClientMockRecorder { } // Certify mocks base method. -func (m *MockcertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (PoetCert, error) { +func (m *MockcertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Certify", ctx, url, pubkey) - ret0, _ := ret[0].(PoetCert) + ret0, _ := ret[0].(*certifier.PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1752,19 +1753,19 @@ type MockcertifierClientCertifyCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockcertifierClientCertifyCall) Return(arg0 PoetCert, arg1 error) *MockcertifierClientCertifyCall { +func (c *MockcertifierClientCertifyCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierClientCertifyCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierClientCertifyCall) Do(f func(context.Context, *url.URL, []byte) (PoetCert, error)) *MockcertifierClientCertifyCall { +func (c *MockcertifierClientCertifyCall) Do(f func(context.Context, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.URL, []byte) (PoetCert, error)) *MockcertifierClientCertifyCall { +func (c *MockcertifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1831,10 +1832,10 @@ func (m *MockcertifierService) EXPECT() *MockcertifierServiceMockRecorder { } // CertifyAll mocks base method. -func (m *MockcertifierService) CertifyAll(ctx context.Context, poets []PoetClient) map[string]PoetCert { +func (m *MockcertifierService) CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CertifyAll", ctx, poets) - ret0, _ := ret[0].(map[string]PoetCert) + ret0, _ := ret[0].(map[string]*certifier.PoetCert) return ret0 } @@ -1851,28 +1852,28 @@ type MockcertifierServiceCertifyAllCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockcertifierServiceCertifyAllCall) Return(arg0 map[string]PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertifyAllCall) Return(arg0 map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceCertifyAllCall) Do(f func(context.Context, []PoetClient) map[string]PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertifyAllCall) Do(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []PoetClient) map[string]PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.DoAndReturn(f) return c } // GetCertificate mocks base method. -func (m *MockcertifierService) GetCertificate(poet string) PoetCert { +func (m *MockcertifierService) GetCertificate(poet string) *certifier.PoetCert { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCertificate", poet) - ret0, _ := ret[0].(PoetCert) + ret0, _ := ret[0].(*certifier.PoetCert) return ret0 } @@ -1889,28 +1890,28 @@ type MockcertifierServiceGetCertificateCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockcertifierServiceGetCertificateCall) Return(arg0 PoetCert) *MockcertifierServiceGetCertificateCall { +func (c *MockcertifierServiceGetCertificateCall) Return(arg0 *certifier.PoetCert) *MockcertifierServiceGetCertificateCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceGetCertificateCall) Do(f func(string) PoetCert) *MockcertifierServiceGetCertificateCall { +func (c *MockcertifierServiceGetCertificateCall) Do(f func(string) *certifier.PoetCert) *MockcertifierServiceGetCertificateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceGetCertificateCall) DoAndReturn(f func(string) PoetCert) *MockcertifierServiceGetCertificateCall { +func (c *MockcertifierServiceGetCertificateCall) DoAndReturn(f func(string) *certifier.PoetCert) *MockcertifierServiceGetCertificateCall { c.Call = c.Call.DoAndReturn(f) return c } // Recertify mocks base method. -func (m *MockcertifierService) Recertify(ctx context.Context, poet PoetClient) (PoetCert, error) { +func (m *MockcertifierService) Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Recertify", ctx, poet) - ret0, _ := ret[0].(PoetCert) + ret0, _ := ret[0].(*certifier.PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1928,19 +1929,19 @@ type MockcertifierServiceRecertifyCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockcertifierServiceRecertifyCall) Return(arg0 PoetCert, arg1 error) *MockcertifierServiceRecertifyCall { +func (c *MockcertifierServiceRecertifyCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierServiceRecertifyCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceRecertifyCall) Do(f func(context.Context, PoetClient) (PoetCert, error)) *MockcertifierServiceRecertifyCall { +func (c *MockcertifierServiceRecertifyCall) Do(f func(context.Context, PoetClient) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceRecertifyCall) DoAndReturn(f func(context.Context, PoetClient) (PoetCert, error)) *MockcertifierServiceRecertifyCall { +func (c *MockcertifierServiceRecertifyCall) DoAndReturn(f func(context.Context, PoetClient) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index b6035f7093..9b91310e54 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -378,7 +378,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( Params: *powParams, } } else { - logger.Info("registering with a certificate", zap.Binary("cert", auth.PoetCert)) + logger.Info("registering with a certificate", zap.Binary("cert", auth.PoetCert.Data)) } logger.Debug("submitting challenge to poet proving service") diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 3137528fc8..883b687ca6 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -21,6 +21,7 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) @@ -410,10 +411,10 @@ func Test_NIPostBuilder_WithMocks(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(PoetCert("cert")) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) require.NotNil(t, nipost) } @@ -454,10 +455,10 @@ func TestPostSetup(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poetProvider.Address()).Return(PoetCert("cert")) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) require.NotNil(t, nipost) } @@ -520,9 +521,9 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poetProver.Address()).AnyTimes().Return(PoetCert("cert")) - nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().GetCertificate(poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) + nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -544,7 +545,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(nil, nil, fmt.Errorf("error")) // check that proof ref is not called again - nipost, err = nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + nipost, err = nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.Nil(t, nipost) require.Error(t, err) @@ -568,7 +569,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { ) // check that proof ref is not called again - nipost, err = nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + nipost, err = nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) require.NotNil(t, nipost) } @@ -644,12 +645,12 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poets[0].Address()).Return(PoetCert("cert")) - certifier.EXPECT().GetCertificate(poets[1].Address()).Return(PoetCert("cert")) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().GetCertificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().GetCertificate(poets[1].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) // Act - nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) // Verify @@ -719,13 +720,13 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate(poets[0].Address()).Return(PoetCert("cert")) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().GetCertificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) // No certs - fallback to PoW - certifier.EXPECT().GetCertificate(poets[1].Address()).Return(nil) + mCertifier.EXPECT().GetCertificate(poets[1].Address()).Return(nil) // Act - nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) // Verify @@ -741,7 +742,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetCfg := PoetConfig{ PhaseShift: layerDuration, } - cert := PoetCert("cert") + cert := &certifier.PoetCert{Data: []byte("cert")} sig, err := signing.NewEdSigner() require.NoError(t, err) @@ -909,10 +910,10 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) + mCertifier := NewMockcertifierService(ctrl) - invalid := PoetCert("certInvalid") - getInvalid := certifier.EXPECT().GetCertificate("http://localhost:9999").Return(invalid) + invalid := &certifier.PoetCert{} + getInvalid := mCertifier.EXPECT().GetCertificate("http://localhost:9999").Return(invalid) submitFailed := poetProver.EXPECT(). Submit( gomock.Any(), @@ -926,8 +927,8 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { After(getInvalid.Call). Return(nil, ErrUnathorized) - valid := PoetCert("certInvalid") - recertify := certifier.EXPECT().Recertify(gomock.Any(), poetProver).After(submitFailed).Return(valid, nil) + valid := &certifier.PoetCert{Data: []byte("valid")} + recertify := mCertifier.EXPECT().Recertify(gomock.Any(), poetProver).After(submitFailed).Return(valid, nil) submitOK := poetProver.EXPECT(). Submit( gomock.Any(), @@ -946,7 +947,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { After(submitOK). Return(&types.PoetProofMessage{}, []types.Member{types.Member(challenge.Hash())}, nil) - _, err = nb.BuildNIPost(context.Background(), sig, &challenge, certifier) + _, err = nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) } @@ -1103,7 +1104,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { LeafCount: 777, }, } - cert := PoetCert("cert") + cert := &certifier.PoetCert{} ctrl := gomock.NewController(t) poetDb := NewMockpoetDbAPI(ctrl) @@ -1381,9 +1382,9 @@ func TestNIPostBuilder_Close(t *testing.T) { PublishEpoch: postGenesisEpoch + 2, } - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(PoetCert("cert")) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().GetCertificate("http://localhost:9999").Return(&certifier.PoetCert{}) - _, err = nb.BuildNIPost(ctx, sig, &challenge, certifier) + _, err = nb.BuildNIPost(ctx, sig, &challenge, mCertifier) require.Error(t, err) } diff --git a/activation/poet.go b/activation/poet.go index 0c8c08beda..14cf705c2f 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -191,7 +191,8 @@ func (c *HTTPPoetClient) Submit( } if auth.PoetCert != nil { request.Certificate = &rpcapi.SubmitRequest_Certificate{ - Signature: auth.PoetCert, + Data: auth.PoetCert.Data, + Signature: auth.PoetCert.Signature, } } diff --git a/cmd/merge-nodes/internal/merge_action_test.go b/cmd/merge-nodes/internal/merge_action_test.go index 2cd43f4417..84af9fcf83 100644 --- a/cmd/merge-nodes/internal/merge_action_test.go +++ b/cmd/merge-nodes/internal/merge_action_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/spacemeshos/post/shared" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -21,7 +22,6 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" - "github.com/spacemeshos/post/shared" ) func Test_MergeDBs_InvalidTargetScheme(t *testing.T) { diff --git a/go.mod b/go.mod index 692e0d5286..21fc86bebd 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.1.13 github.com/spacemeshos/merkle-tree v0.2.3 - github.com/spacemeshos/poet v0.10.2 + github.com/spacemeshos/poet v0.10.3-0.20240321171438-96878e4dfa8d github.com/spacemeshos/post v0.12.5 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 @@ -216,7 +216,7 @@ require ( google.golang.org/api v0.167.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index bd302968bc..8a48f5ab7a 100644 --- a/go.sum +++ b/go.sum @@ -570,8 +570,8 @@ github.com/spacemeshos/go-scale v1.1.13 h1:y5rl/qmN0tE38ErpCeq8Ofq4+fCKRWF1qUIjA github.com/spacemeshos/go-scale v1.1.13/go.mod h1:HV6e3/X5h9u2aFpYKJxt7PY/fBuLBegEKWgeZJ+/5jE= github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas= github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww= -github.com/spacemeshos/poet v0.10.2 h1:FVb0xgCFcjZyIGBQ92SlOZVx4KCmlCRRL4JSHL6LMGU= -github.com/spacemeshos/poet v0.10.2/go.mod h1:73ROEXGladw3RbvhAk0sIGi/ttfpo+ASUBRvnBK55N8= +github.com/spacemeshos/poet v0.10.3-0.20240321171438-96878e4dfa8d h1:hTTVfcln6E04eCDfcdydApzsJtfMJ02gMT8JXQQwgRw= +github.com/spacemeshos/poet v0.10.3-0.20240321171438-96878e4dfa8d/go.mod h1:+H37RV00jlztjRvKML/cBp/DIAGCweOgjNGaI814+W8= github.com/spacemeshos/post v0.12.5 h1:kIqweQ2vzojd6/+KAfw4x0CCYEbgo9BPdYdsqHDKXlE= github.com/spacemeshos/post v0.12.5/go.mod h1:NEstvZ4BKHuiGTcb+H+cQsZiNSh0G7GOLjZv6jjnHxM= github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= @@ -844,8 +844,8 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78 h1:SzXBGiWM1LNVYLCRP3e0/Gsze804l4jGoJ5lYysEO5I= -google.golang.org/genproto/googleapis/api v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 h1:8eadJkXbwDEMNwcB5O0s5Y5eCfyuCLdvaiOIaGTrWmQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= diff --git a/sql/localsql/certifier/db.go b/sql/localsql/certifier/db.go index ca30a0a359..7b732587fa 100644 --- a/sql/localsql/certifier/db.go +++ b/sql/localsql/certifier/db.go @@ -7,34 +7,42 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" ) -func AddCertificate(db sql.Executor, nodeID types.NodeID, cert []byte, poetUrl string) error { +type PoetCert struct { + Data []byte + Signature []byte +} + +func AddCertificate(db sql.Executor, nodeID types.NodeID, cert PoetCert, poetUrl string) error { enc := func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) stmt.BindBytes(2, []byte(poetUrl)) - stmt.BindBytes(3, cert) + stmt.BindBytes(3, cert.Data) + stmt.BindBytes(4, cert.Signature) } if _, err := db.Exec(` - REPLACE INTO poet_certificates (node_id, poet_url, certificate) - VALUES (?1, ?2, ?3);`, enc, nil, + REPLACE INTO poet_certificates (node_id, poet_url, certificate, signature) + VALUES (?1, ?2, ?3, ?4);`, enc, nil, ); err != nil { return fmt.Errorf("storing poet certificate for (%s; %s): %w", nodeID.ShortString(), poetUrl, err) } return nil } -func Certificate(db sql.Executor, nodeID types.NodeID, poetUrl string) ([]byte, error) { +func Certificate(db sql.Executor, nodeID types.NodeID, poetUrl string) (*PoetCert, error) { enc := func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) stmt.BindBytes(2, []byte(poetUrl)) } - var cert []byte + var cert PoetCert dec := func(stmt *sql.Statement) bool { - cert = make([]byte, stmt.ColumnLen(0)) - stmt.ColumnBytes(0, cert) + cert.Data = make([]byte, stmt.ColumnLen(0)) + cert.Signature = make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(0, cert.Data) + stmt.ColumnBytes(1, cert.Signature) return true } rows, err := db.Exec(` - select certificate + select certificate, signature from poet_certificates where node_id = ?1 and poet_url = ?2 limit 1;`, enc, dec, ) switch { @@ -43,5 +51,5 @@ func Certificate(db sql.Executor, nodeID types.NodeID, poetUrl string) ([]byte, case rows == 0: return nil, sql.ErrNotFound } - return cert, nil + return &cert, nil } diff --git a/sql/localsql/certifier/db_test.go b/sql/localsql/certifier/db_test.go index 8910b8bb37..a8d0fc773e 100644 --- a/sql/localsql/certifier/db_test.go +++ b/sql/localsql/certifier/db_test.go @@ -14,32 +14,37 @@ func TestAddingCertificates(t *testing.T) { db := localsql.InMemory() nodeId := types.RandomNodeID() - require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert"), "poet-0")) + expCert := certifier.PoetCert{Data: []byte("data"), Signature: []byte("sig")} + + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert, "poet-0")) cert, err := certifier.Certificate(db, nodeId, "poet-0") require.NoError(t, err) - require.Equal(t, []byte("cert"), cert) + require.Equal(t, &expCert, cert) - require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert2"), "poet-1")) + expCert2 := certifier.PoetCert{Data: []byte("data2"), Signature: []byte("sig2")} + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert2, "poet-1")) cert, err = certifier.Certificate(db, nodeId, "poet-1") require.NoError(t, err) - require.Equal(t, []byte("cert2"), cert) + require.Equal(t, &expCert2, cert) cert, err = certifier.Certificate(db, nodeId, "poet-0") require.NoError(t, err) - require.Equal(t, []byte("cert"), cert) + require.Equal(t, &expCert, cert) } func TestOverwritingCertificates(t *testing.T) { db := localsql.InMemory() nodeId := types.RandomNodeID() - require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert"), "poet-0")) + expCert := certifier.PoetCert{Data: []byte("data"), Signature: []byte("sig")} + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert, "poet-0")) cert, err := certifier.Certificate(db, nodeId, "poet-0") require.NoError(t, err) - require.Equal(t, []byte("cert"), cert) + require.Equal(t, &expCert, cert) - require.NoError(t, certifier.AddCertificate(db, nodeId, []byte("cert2"), "poet-0")) + expCert2 := certifier.PoetCert{Data: []byte("data2"), Signature: []byte("sig2")} + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert2, "poet-0")) cert, err = certifier.Certificate(db, nodeId, "poet-0") require.NoError(t, err) - require.Equal(t, []byte("cert2"), cert) + require.Equal(t, &expCert2, cert) } diff --git a/sql/migrations/local/0007_next.sql b/sql/migrations/local/0007_next.sql index f09bcf4cfe..19c3249c1f 100644 --- a/sql/migrations/local/0007_next.sql +++ b/sql/migrations/local/0007_next.sql @@ -6,7 +6,8 @@ CREATE TABLE poet_certificates ( node_id BLOB NOT NULL, poet_url VARCHAR NOT NULL, - certificate BLOB NOT NULL + certificate BLOB NOT NULL, + signature BLOB NOT NULL ); CREATE UNIQUE INDEX idx_poet_certificates ON poet_certificates (node_id, poet_url); diff --git a/systest/Makefile b/systest/Makefile index 298e80026f..1ccb81112f 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,8 +5,8 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:v0.7.0 -poet_image ?= $(org)/poet:v0.10.2 +certifier_image ?= spacemeshos/certifier-service:121fb911b038834bd45fd0680ae575d242d72c93 +poet_image ?= $(org)/poet:v0.10.2-42-gceab169 post_service_image ?= $(org)/post-service:v0.7.4 post_init_image ?= $(org)/postcli:v0.12.5 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) From 7e35d3f1271f98d3c33f9577a9f8847ec957f477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 25 Mar 2024 09:39:29 +0100 Subject: [PATCH 36/55] Use cert verification shared from poet --- activation/certifier.go | 20 ++-- activation/e2e/certifier_client_test.go | 117 +++++++++++++++++------- activation/e2e/nipost_test.go | 2 +- go.mod | 2 +- go.sum | 4 +- 5 files changed, 96 insertions(+), 49 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index 98296ddb3b..afa9f7a1ff 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -3,7 +3,6 @@ package activation import ( "bytes" "context" - "crypto/ed25519" "encoding/base64" "encoding/json" "errors" @@ -346,16 +345,17 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by if !bytes.Equal(certRespose.PubKey, pubkey) { return nil, errors.New("pubkey is invalid") } - if !ed25519.Verify(pubkey, certRespose.Certificate, certRespose.Signature) { - return nil, errors.New("signature is invalid") + + opaqueCert := &shared.OpaqueCert{ + Data: certRespose.Certificate, + Signature: certRespose.Signature, } - cert, err := shared.DecodeCert(certRespose.Certificate) + + cert, err := shared.VerifyCertificate(opaqueCert, pubkey, c.Id().Bytes()) if err != nil { - return nil, fmt.Errorf("decoding certificate: %w", err) - } - if !bytes.Equal(cert.Pubkey, c.Id().Bytes()) { - return nil, errors.New("certificate pubkey doesn't match node ID") + return nil, fmt.Errorf("verifying certificate: %w", err) } + if cert.Expiration != nil { c.logger.Info("certificate has expiration date", zap.Time("expiration", *cert.Expiration)) if time.Until(*cert.Expiration) < 0 { @@ -364,7 +364,7 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by } return &certifier.PoetCert{ - Data: certRespose.Certificate, - Signature: certRespose.Signature, + Data: opaqueCert.Data, + Signature: opaqueCert.Signature, }, nil } diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index e915c527e0..3ca7e4b27f 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + "net/url" "testing" "time" @@ -72,49 +73,84 @@ func TestCertification(t *testing.T) { post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) - poets := []activation.PoetClient{} - // Spawn certifier and 2 poets using it - pubKey, addr := spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) - certifierCfg := ®istration.CertifierConfig{ - URL: "http://" + addr.String(), - PubKey: registration.Base64Enc(pubKey), - } + t.Run("certify all poets", func(t *testing.T) { + poets := []activation.PoetClient{} + // Spawn certifier and 2 poets using it + pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) + certifierCfg := ®istration.CertifierConfig{ + URL: "http://" + addr.String(), + PubKey: registration.Base64Enc(pubKey), + } + + for i := 0; i < 2; i++ { + poet := spawnPoet(t, WithCertifier(certifierCfg)) + address := poet.RestURL().String() + client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) + require.NoError(t, err) + poets = append(poets, client) + } + + // Spawn another certifier and 1 poet using it + pubKey, addr = spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) + certifierCfg = ®istration.CertifierConfig{ + URL: "http://" + addr.String(), + PubKey: registration.Base64Enc(pubKey), + } - for i := 0; i < 2; i++ { poet := spawnPoet(t, WithCertifier(certifierCfg)) address := poet.RestURL().String() client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) require.NoError(t, err) poets = append(poets, client) - } - // Spawn another certifier and 1 poet using it - pubKey, addr = spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) - certifierCfg = ®istration.CertifierConfig{ - URL: "http://" + addr.String(), - PubKey: registration.Base64Enc(pubKey), - } + // poet not using certifier + poet = spawnPoet(t) + address = poet.RestURL().String() + client, err = activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) + require.NoError(t, err) + poets = append(poets, client) - poet := spawnPoet(t, WithCertifier(certifierCfg)) - address := poet.RestURL().String() - client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) - require.NoError(t, err) - poets = append(poets, client) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + certifier := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), certifierClient) + certs := certifier.CertifyAll(context.Background(), poets) + require.Len(t, certs, 3) + require.Contains(t, certs, poets[0].Address()) + require.Contains(t, certs, poets[1].Address()) + require.Contains(t, certs, poets[2].Address()) + }) + t.Run("certify accepts valid cert", func(t *testing.T) { + pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) - // poet not using certifier - poet = spawnPoet(t) - address = poet.RestURL().String() - client, err = activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) - require.NoError(t, err) - poets = append(poets, client) - - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) - certifier := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), certifierClient) - certs := certifier.CertifyAll(context.Background(), poets) - require.Len(t, certs, 3) - require.Contains(t, certs, poets[0].Address()) - require.Contains(t, certs, poets[1].Address()) - require.Contains(t, certs, poets[2].Address()) + client := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + _, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + require.NoError(t, err) + }) + t.Run("certify rejects invalid cert (expired)", func(t *testing.T) { + makeCert := func(nodeID []byte) *poetShared.Cert { + expired := time.Now().Add(-time.Hour) + return &poetShared.Cert{ + Pubkey: nodeID, + Expiration: &expired, + } + } + pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) + + client := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + cert, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + require.Error(t, err) + require.Nil(t, cert) + }) + t.Run("certify rejects invalid cert (wrong ID)", func(t *testing.T) { + makeCert := func(_ []byte) *poetShared.Cert { + return &poetShared.Cert{Pubkey: []byte("wrong")} + } + pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) + + client := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + cert, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + require.Error(t, err) + require.Nil(t, cert) + }) } // A testCertifier for use in tests. @@ -124,6 +160,8 @@ type testCertifier struct { postVerifier activation.PostVerifier opts []verifying.OptionFunc cfg activation.PostConfig + + makeCert func(nodeID []byte) *poetShared.Cert } func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { @@ -151,7 +189,13 @@ func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { return } - certData, err := poetShared.EncodeCert(&poetShared.Cert{Pubkey: req.Metadata.NodeId}) + var cert *poetShared.Cert + if c.makeCert != nil { + cert = c.makeCert(req.Metadata.NodeId) + } else { + cert = &poetShared.Cert{Pubkey: req.Metadata.NodeId} + } + certData, err := poetShared.EncodeCert(cert) if err != nil { panic(fmt.Sprintf("encoding cert: %v", err)) } @@ -170,6 +214,8 @@ func (c *testCertifier) certify(w http.ResponseWriter, r *http.Request) { func spawnTestCertifier( t *testing.T, cfg activation.PostConfig, + // optional - if nil, will create valid certs + makeCert func(nodeID []byte) *poetShared.Cert, opts ...verifying.OptionFunc, ) (ed25519.PublicKey, net.Addr) { t.Helper() @@ -191,6 +237,7 @@ func spawnTestCertifier( postVerifier: postVerifier, opts: opts, cfg: cfg, + makeCert: makeCert, } mux := http.NewServeMux() diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 7d0c496ea3..221a4a2bd7 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -153,7 +153,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { MaxRequestRetries: 10, } - pubKey, addr := spawnTestCertifier(t, cfg, verifying.WithLabelScryptParams(opts.Scrypt)) + pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) certifierCfg := ®istration.CertifierConfig{ URL: "http://" + addr.String(), PubKey: registration.Base64Enc(pubKey), diff --git a/go.mod b/go.mod index 21fc86bebd..85ce303488 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.1.13 github.com/spacemeshos/merkle-tree v0.2.3 - github.com/spacemeshos/poet v0.10.3-0.20240321171438-96878e4dfa8d + github.com/spacemeshos/poet v0.10.3-0.20240325083037-7ba2761c0a35 github.com/spacemeshos/post v0.12.5 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 8a48f5ab7a..a0a4b5e1d4 100644 --- a/go.sum +++ b/go.sum @@ -570,8 +570,8 @@ github.com/spacemeshos/go-scale v1.1.13 h1:y5rl/qmN0tE38ErpCeq8Ofq4+fCKRWF1qUIjA github.com/spacemeshos/go-scale v1.1.13/go.mod h1:HV6e3/X5h9u2aFpYKJxt7PY/fBuLBegEKWgeZJ+/5jE= github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas= github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww= -github.com/spacemeshos/poet v0.10.3-0.20240321171438-96878e4dfa8d h1:hTTVfcln6E04eCDfcdydApzsJtfMJ02gMT8JXQQwgRw= -github.com/spacemeshos/poet v0.10.3-0.20240321171438-96878e4dfa8d/go.mod h1:+H37RV00jlztjRvKML/cBp/DIAGCweOgjNGaI814+W8= +github.com/spacemeshos/poet v0.10.3-0.20240325083037-7ba2761c0a35 h1:jtnlrIPValFEIxrksCDQkZi0H0PhjeqGwuxvw155yVw= +github.com/spacemeshos/poet v0.10.3-0.20240325083037-7ba2761c0a35/go.mod h1:+H37RV00jlztjRvKML/cBp/DIAGCweOgjNGaI814+W8= github.com/spacemeshos/post v0.12.5 h1:kIqweQ2vzojd6/+KAfw4x0CCYEbgo9BPdYdsqHDKXlE= github.com/spacemeshos/post v0.12.5/go.mod h1:NEstvZ4BKHuiGTcb+H+cQsZiNSh0G7GOLjZv6jjnHxM= github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= From bd3130d52cdbf512aa20c2b41d9b4c4bb5cd9b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 25 Mar 2024 10:29:49 +0100 Subject: [PATCH 37/55] Fix linting issues --- activation/activation_test.go | 12 ++++++++---- activation/e2e/certifier_client_test.go | 3 ++- activation/nipost_test.go | 12 ++++++++---- systest/cluster/nodes.go | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/activation/activation_test.go b/activation/activation_test.go index 2c72fcbd6b..7dcba591d9 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1546,7 +1546,8 @@ func TestBuilder_obtainPost(t *testing.T) { tab.mnipost.EXPECT(). Proof(gomock.Any(), sig.NodeID(), shared.ZeroChallenge). Return(&post, &postInfo, nil) - tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) + tab.mValidator.EXPECT(). + Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) _, _, _, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) }) @@ -1561,7 +1562,8 @@ func TestBuilder_obtainPost(t *testing.T) { tab.mnipost.EXPECT(). Proof(gomock.Any(), sig.NodeID(), shared.ZeroChallenge). Return(&post, &postInfo, nil) - tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) + tab.mValidator.EXPECT(). + Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) @@ -1679,7 +1681,8 @@ func TestBuilder_InitialPostLogErrorMissingVRFNonce(t *testing.T) { ) tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), commitmentATX, initialPost, meta, numUnits). Return(nil) - require.ErrorContains(t, tab.buildInitialPost(context.Background(), sig.NodeID()), "nil VRF nonce") + _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) + require.ErrorContains(t, err, "nil VRF nonce") observedLogs := tab.observedLogs.FilterLevelExact(zapcore.ErrorLevel) require.Equal(t, 1, observedLogs.Len(), "expected 1 log message") @@ -1701,7 +1704,8 @@ func TestBuilder_InitialPostLogErrorMissingVRFNonce(t *testing.T) { }, nil, ) - require.NoError(t, tab.buildInitialPost(context.Background(), sig.NodeID())) + _, _, err = tab.buildInitialPost(context.Background(), sig.NodeID()) + require.NoError(t, err) } func TestWaitPositioningAtx(t *testing.T) { diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 3ca7e4b27f..c17e16795c 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -85,7 +85,8 @@ func TestCertification(t *testing.T) { for i := 0; i < 2; i++ { poet := spawnPoet(t, WithCertifier(certifierCfg)) address := poet.RestURL().String() - client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) + cfg := activation.DefaultPoetConfig() + client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, cfg) require.NoError(t, err) poets = append(poets, client) } diff --git a/activation/nipost_test.go b/activation/nipost_test.go index da3e24921f..f648846def 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -521,7 +521,8 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { require.NoError(t, err) mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().GetCertificate(poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT(). + GetCertificate(poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) nipost, err := nb.BuildNIPost(context.Background(), sig, &challenge, mCertifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -751,8 +752,9 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) + auth := PoetAuth{PoetCert: cert} poetProver.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), PoetAuth{PoetCert: cert}). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), auth). Return(nil, errors.New("test")) poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) @@ -781,8 +783,9 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) + auth := PoetAuth{PoetCert: cert} poetProver.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), PoetAuth{PoetCert: cert}). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), auth). DoAndReturn(func( ctx context.Context, _ time.Time, @@ -1113,8 +1116,9 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { buildCtx, cancel := context.WithCancel(context.Background()) poet := NewMockPoetClient(ctrl) + auth := PoetAuth{PoetCert: cert} poet.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), PoetAuth{PoetCert: cert}). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID(), auth). DoAndReturn(func( _ context.Context, _ time.Time, diff --git a/systest/cluster/nodes.go b/systest/cluster/nodes.go index 6607dab1f6..4c1c3b875a 100644 --- a/systest/cluster/nodes.go +++ b/systest/cluster/nodes.go @@ -340,7 +340,7 @@ func deployCertifierD(ctx *testcontext.Context, id, privkey string) (*NodeClient WithArgs(args...). WithEnv(corev1.EnvVar().WithName("CERTIFIER_SIGNING_KEY").WithValue(privkey)). WithPorts( - corev1.ContainerPort().WithName("rest").WithProtocol("TCP").WithContainerPort(certifierPort), + corev1.ContainerPort().WithProtocol("TCP").WithContainerPort(certifierPort), ). WithVolumeMounts( corev1.VolumeMount().WithName("config").WithMountPath(configDir), From 87c92849f0351db97123336d9732087b80cb3670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 25 Mar 2024 12:01:41 +0100 Subject: [PATCH 38/55] Bump certifier service in systests --- systest/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systest/Makefile b/systest/Makefile index 363bab6691..e737cd2def 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,7 +5,7 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:121fb911b038834bd45fd0680ae575d242d72c93 +certifier_image ?= spacemeshos/certifier-service:51e174bbdb417bca21d4158ac04b48ebcb291fe1 poet_image ?= $(org)/poet:v0.10.2-42-gceab169 post_service_image ?= $(org)/post-service:v0.7.5 post_init_image ?= $(org)/postcli:v0.12.5 From f347e3de0320842642bdc510cfe7467e1417258c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 25 Mar 2024 12:22:00 +0100 Subject: [PATCH 39/55] Rename local sql migration --- sql/migrations/local/{0007_next.sql => 0008_next.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename sql/migrations/local/{0007_next.sql => 0008_next.sql} (100%) diff --git a/sql/migrations/local/0007_next.sql b/sql/migrations/local/0008_next.sql similarity index 100% rename from sql/migrations/local/0007_next.sql rename to sql/migrations/local/0008_next.sql From 377a5d5b0bd69f142209c45c317ee575863c0790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 25 Mar 2024 12:52:33 +0100 Subject: [PATCH 40/55] Fix staticcheck --- activation/certifier.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index afa9f7a1ff..4a4f47374c 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -22,7 +22,6 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" - certifier_db "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) type CertifierClientConfig struct { @@ -125,7 +124,7 @@ func NewCertifier( } func (c *Certifier) GetCertificate(poet string) *certifier.PoetCert { - cert, err := certifier_db.Certificate(c.db, c.client.Id(), poet) + cert, err := certifier.Certificate(c.db, c.client.Id(), poet) switch { case err == nil: return cert @@ -145,7 +144,7 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*certifier. return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), url, err) } - if err := certifier_db.AddCertificate(c.db, c.client.Id(), *cert, poet.Address()); err != nil { + if err := certifier.AddCertificate(c.db, c.client.Id(), *cert, poet.Address()); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } @@ -237,7 +236,7 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri zap.Binary("cert signature", cert.Signature), ) for _, poet := range svc.poets { - if err := certifier_db.AddCertificate(c.db, c.client.Id(), *cert, poet); err != nil { + if err := certifier.AddCertificate(c.db, c.client.Id(), *cert, poet); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } certs[poet] = cert From 459f453e01bad8f11d5b539d503219560e33a168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 18 Apr 2024 11:42:49 +0200 Subject: [PATCH 41/55] Use nipost.Post --- activation/activation.go | 86 ++++++++----------- activation/activation_multi_test.go | 4 +- activation/activation_test.go | 26 +++--- activation/certifier.go | 44 +++++----- activation/e2e/certifier_client_test.go | 19 +++- activation/e2e/nipost_test.go | 19 +++- activation/e2e/validation_test.go | 3 +- .../distributed_post_verification_test.go | 10 +-- systest/tests/poets_test.go | 1 + 9 files changed, 111 insertions(+), 101 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 17f07864cb..57cc2feb50 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -365,112 +365,96 @@ func (b *Builder) buildInitialPost(ctx context.Context, nodeID types.NodeID) (*t // We want to certify immediately after the startup or creating the initial POST // to avoid all nodes spamming the certifier at the same time when // submitting to the poets. -func (b *Builder) certifyPost(ctx context.Context, post *types.Post, meta *types.PostInfo, ch []byte) { - client := NewCertifierClient(b.log, post, meta, ch, WithCertifierClientConfig(b.certifierConfig.Client)) +func (b *Builder) certifyPost(ctx context.Context, nodeID types.NodeID, post *nipost.Post) { + client := NewCertifierClient(b.log, nodeID, post, WithCertifierClientConfig(b.certifierConfig.Client)) certifier := NewCertifier(b.localDB, b.log, client) certifier.CertifyAll(ctx, b.poets) b.smeshingMutex.Lock() - b.certifiers[meta.NodeID] = certifier + b.certifiers[nodeID] = certifier b.smeshingMutex.Unlock() } -func (b *Builder) obtainPostFromLastAtx( - ctx context.Context, - nodeId types.NodeID, -) (*types.Post, *types.PostInfo, []byte, error) { +func (b *Builder) obtainPostFromLastAtx(ctx context.Context, nodeId types.NodeID) (*nipost.Post, error) { atxid, err := atxs.GetLastIDByNodeID(b.db, nodeId) if err != nil { - return nil, nil, nil, fmt.Errorf("no existing ATX found: %w", err) + return nil, fmt.Errorf("no existing ATX found: %w", err) } atx, err := atxs.Get(b.db, atxid) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to retrieve ATX: %w", err) + return nil, fmt.Errorf("failed to retrieve ATX: %w", err) } if atx.NIPost == nil { - return nil, nil, nil, errors.New("no NIPoST found in last ATX") + return nil, errors.New("no NIPoST found in last ATX") } if atx.CommitmentATX == nil { if commitmentAtx, err := atxs.CommitmentATX(b.db, nodeId); err != nil { - return nil, nil, nil, fmt.Errorf("failed to retrieve commitment ATX: %w", err) + return nil, fmt.Errorf("failed to retrieve commitment ATX: %w", err) } else { atx.CommitmentATX = &commitmentAtx } } if atx.VRFNonce == nil { if nonce, err := atxs.VRFNonce(b.db, nodeId, b.layerClock.CurrentLayer().GetEpoch()); err != nil { - return nil, nil, nil, fmt.Errorf("failed to retrieve VRF nonce: %w", err) + return nil, fmt.Errorf("failed to retrieve VRF nonce: %w", err) } else { atx.VRFNonce = &nonce } } b.log.Info("found POST in an existing ATX", zap.String("atx_id", atxid.Hash32().ShortString())) - meta := &types.PostInfo{ - NodeID: nodeId, - CommitmentATX: *atx.CommitmentATX, - Nonce: atx.VRFNonce, + return &nipost.Post{ + Nonce: atx.NIPost.Post.Nonce, + Indices: atx.NIPost.Post.Indices, + Pow: atx.NIPost.Post.Pow, + Challenge: atx.NIPost.PostMetadata.Challenge, NumUnits: atx.NumUnits, - LabelsPerUnit: atx.NIPost.PostMetadata.LabelsPerUnit, - } - - return atx.NIPost.Post, meta, atx.NIPost.PostMetadata.Challenge, nil + CommitmentATX: *atx.CommitmentATX, + VRFNonce: *atx.VRFNonce, + }, nil } -func (b *Builder) obtainPost(ctx context.Context, nodeID types.NodeID) (*types.Post, *types.PostInfo, []byte, error) { +func (b *Builder) obtainPost(ctx context.Context, nodeID types.NodeID) (*nipost.Post, error) { b.log.Info("looking for POST for poet certification") post, err := nipost.GetPost(b.localDB, nodeID) switch { case err == nil: - meta := &types.PostInfo{ - NodeID: nodeID, - CommitmentATX: post.CommitmentATX, - Nonce: &post.VRFNonce, - NumUnits: post.NumUnits, - } - challenge := post.Challenge - post := &types.Post{ - Nonce: post.Nonce, - Indices: post.Indices, - Pow: post.Pow, - } b.log.Info("found POST in local DB") - return post, meta, challenge, nil + return post, nil case errors.Is(err, sql.ErrNotFound): // no post found default: - return nil, nil, nil, fmt.Errorf("loading initial post from db: %w", err) + return nil, fmt.Errorf("loading initial post from db: %w", err) } b.log.Info("POST not found in local DB. Trying to obtain POST from an existing ATX") - if post, postInfo, ch, err := b.obtainPostFromLastAtx(ctx, nodeID); err == nil { + if post, err := b.obtainPostFromLastAtx(ctx, nodeID); err == nil { b.log.Info("found POST in an existing ATX") - postToPersist := nipost.Post{ - Nonce: post.Nonce, - Indices: post.Indices, - Pow: post.Pow, - Challenge: ch, - NumUnits: postInfo.NumUnits, - CommitmentATX: postInfo.CommitmentATX, - VRFNonce: *postInfo.Nonce, - } - if err := nipost.AddPost(b.localDB, nodeID, postToPersist); err != nil { + if err := nipost.AddPost(b.localDB, nodeID, *post); err != nil { b.log.Error("failed to save post", zap.Error(err)) } - return post, postInfo, ch, nil + return post, nil } b.log.Info("POST not found in existing ATXs. Generating the initial POST") for { post, postInfo, err := b.buildInitialPost(ctx, nodeID) if err == nil { - return post, postInfo, shared.ZeroChallenge, nil + return &nipost.Post{ + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: shared.ZeroChallenge, + NumUnits: postInfo.NumUnits, + CommitmentATX: postInfo.CommitmentATX, + VRFNonce: *postInfo.Nonce, + }, nil } b.log.Error("failed to generate initial proof:", zap.Error(err)) currentLayer := b.layerClock.CurrentLayer() select { case <-ctx.Done(): - return nil, nil, nil, ctx.Err() + return nil, ctx.Err() case <-b.layerClock.AwaitLayer(currentLayer.Add(1)): } } @@ -479,12 +463,12 @@ func (b *Builder) obtainPost(ctx context.Context, nodeID types.NodeID) (*types.P func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { defer b.log.Info("atx builder stopped") - post, meta, ch, err := b.obtainPost(ctx, sig.NodeID()) + post, err := b.obtainPost(ctx, sig.NodeID()) if err != nil { b.log.Error("failed to obtain post for certification", zap.Error(err)) return } - b.certifyPost(ctx, post, meta, ch) + b.certifyPost(ctx, sig.NodeID(), post) for { err := b.PublishActivationTx(ctx, sig) diff --git a/activation/activation_multi_test.go b/activation/activation_multi_test.go index b037d91aba..7ef17cdc97 100644 --- a/activation/activation_multi_test.go +++ b/activation/activation_multi_test.go @@ -251,11 +251,11 @@ func Test_Builder_Multi_InitialPost(t *testing.T) { }, nil, ) - _, _, _, err := tab.obtainPost(context.Background(), sig.NodeID()) + _, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) // postClient.Proof() should not be called again - _, _, _, err = tab.obtainPost(context.Background(), sig.NodeID()) + _, err = tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) return nil }) diff --git a/activation/activation_test.go b/activation/activation_test.go index 5e7b3232fb..53c4ec30a7 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1518,7 +1518,7 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { require.Equal(t, types.BytesToHash(poetByte), atx.GetPoetProofRef()) // postClient.Proof() should not be called again - _, _, _, err = tab.obtainPost(context.Background(), sig.NodeID()) + _, err = tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) } @@ -1536,7 +1536,7 @@ func TestBuilder_obtainPost(t *testing.T) { Return(&post, &postInfo, nil) tab.mValidator.EXPECT(). Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) - _, _, _, err := tab.obtainPost(context.Background(), sig.NodeID()) + _, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) }) t.Run("initial POST available", func(t *testing.T) { @@ -1556,9 +1556,9 @@ func TestBuilder_obtainPost(t *testing.T) { _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) - _, _, ch, err := tab.obtainPost(context.Background(), sig.NodeID()) + gotPost, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) - require.EqualValues(t, shared.ZeroChallenge, ch) + require.EqualValues(t, shared.ZeroChallenge, gotPost.Challenge) }) t.Run("initial POST unavailable but ATX exists", func(t *testing.T) { tab := newTestBuilder(t, 1) @@ -1592,14 +1592,14 @@ func TestBuilder_obtainPost(t *testing.T) { require.NoError(t, err) require.NoError(t, atxs.Add(tab.db, vAtx)) - post, meta, ch, err := tab.obtainPost(context.Background(), sig.NodeID()) + post, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) - require.Equal(t, commitmentAtxId, meta.CommitmentATX) - require.Equal(t, uint32(2), meta.NumUnits) - require.Equal(t, sig.NodeID(), meta.NodeID) - require.Equal(t, uint64(777), meta.LabelsPerUnit) - require.Equal(t, nipost.PostMetadata.Challenge, ch) - require.Equal(t, nipost.Post, post) + require.Equal(t, commitmentAtxId, post.CommitmentATX) + require.Equal(t, uint32(2), post.NumUnits) + require.Equal(t, nipost.PostMetadata.Challenge, post.Challenge) + require.Equal(t, nipost.Post.Indices, post.Indices) + require.Equal(t, nipost.Post.Pow, post.Pow) + require.Equal(t, nipost.Post.Nonce, post.Nonce) }) } @@ -1633,11 +1633,11 @@ func TestBuilder_InitialPostIsPersisted(t *testing.T) { ) tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), commitmentATX, initialPost, meta, numUnits). Return(nil) - _, _, _, err := tab.obtainPost(context.Background(), sig.NodeID()) + _, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) // postClient.Proof() should not be called again - _, _, _, err = tab.obtainPost(context.Background(), sig.NodeID()) + _, err = tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) } diff --git a/activation/certifier.go b/activation/certifier.go index 0b7a9c7159..15cfabebd0 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -22,6 +22,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" + "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) type CertifierClientConfig struct { @@ -245,11 +246,10 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri } type CertifierClient struct { - client *retryablehttp.Client - post *types.Post - postInfo *types.PostInfo - postCh []byte - logger *zap.Logger + client *retryablehttp.Client + post *nipost.Post + nodeID types.NodeID + logger *zap.Logger } type certifierClientOpts func(*CertifierClient) @@ -264,17 +264,15 @@ func WithCertifierClientConfig(cfg CertifierClientConfig) certifierClientOpts { func NewCertifierClient( logger *zap.Logger, - post *types.Post, - postInfo *types.PostInfo, - postCh []byte, + nodeID types.NodeID, + post *nipost.Post, opts ...certifierClientOpts, ) *CertifierClient { c := &CertifierClient{ - client: retryablehttp.NewClient(), - logger: logger.With(log.ZShortStringer("smesherID", postInfo.NodeID)), - post: post, - postInfo: postInfo, - postCh: postCh, + client: retryablehttp.NewClient(), + logger: logger.With(log.ZShortStringer("smesherID", nodeID)), + post: post, + nodeID: nodeID, } config := DefaultCertifierClientConfig() c.client.RetryMax = config.MaxRetries @@ -293,7 +291,7 @@ func NewCertifierClient( } func (c *CertifierClient) Id() types.NodeID { - return c.postInfo.NodeID + return c.nodeID } func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) { @@ -304,10 +302,10 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by Indices: c.post.Indices, }, Metadata: ProofToCertifyMetadata{ - NodeId: c.postInfo.NodeID[:], - CommitmentAtxId: c.postInfo.CommitmentATX[:], - NumUnits: c.postInfo.NumUnits, - Challenge: c.postCh, + NodeId: c.nodeID[:], + CommitmentAtxId: c.post.CommitmentATX[:], + NumUnits: c.post.NumUnits, + Challenge: c.post.Challenge, }, } @@ -336,17 +334,17 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by return nil, fmt.Errorf("request failed with code %d (message: %s)", resp.StatusCode, body) } - certRespose := CertifyResponse{} - if err := json.NewDecoder(resp.Body).Decode(&certRespose); err != nil { + certResponse := CertifyResponse{} + if err := json.NewDecoder(resp.Body).Decode(&certResponse); err != nil { return nil, fmt.Errorf("decoding JSON response: %w", err) } - if !bytes.Equal(certRespose.PubKey, pubkey) { + if !bytes.Equal(certResponse.PubKey, pubkey) { return nil, errors.New("pubkey is invalid") } opaqueCert := &shared.OpaqueCert{ - Data: certRespose.Certificate, - Signature: certRespose.Signature, + Data: certResponse.Certificate, + Signature: certResponse.Signature, } cert, err := shared.VerifyCertificate(opaqueCert, pubkey, c.Id().Bytes()) diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index c9ee58bb4d..025f3e7fb8 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -29,6 +29,7 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) func TestCertification(t *testing.T) { @@ -74,6 +75,16 @@ func TestCertification(t *testing.T) { post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) + fullPost := &nipost.Post{ + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: shared.ZeroChallenge, + NumUnits: info.NumUnits, + CommitmentATX: info.CommitmentATX, + VRFNonce: *info.Nonce, + } + t.Run("certify all poets", func(t *testing.T) { poets := []activation.PoetClient{} // Spawn certifier and 2 poets using it @@ -112,7 +123,7 @@ func TestCertification(t *testing.T) { require.NoError(t, err) poets = append(poets, client) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) certifier := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), certifierClient) certs := certifier.CertifyAll(context.Background(), poets) require.Len(t, certs, 3) @@ -123,7 +134,7 @@ func TestCertification(t *testing.T) { t.Run("certify accepts valid cert", func(t *testing.T) { pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + client := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) _, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.NoError(t, err) }) @@ -137,7 +148,7 @@ func TestCertification(t *testing.T) { } pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + client := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) cert, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) @@ -148,7 +159,7 @@ func TestCertification(t *testing.T) { } pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + client := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) cert, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 5f4274da8f..93b0aff360 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -28,6 +28,7 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) const ( @@ -42,6 +43,18 @@ func TestMain(m *testing.M) { os.Exit(res) } +func fullPost(post *types.Post, info *types.PostInfo, challenge []byte) *nipost.Post { + return &nipost.Post{ + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: challenge, + NumUnits: info.NumUnits, + CommitmentATX: info.CommitmentATX, + VRFNonce: *info.Nonce, + } +} + func spawnPoet(tb testing.TB, opts ...HTTPPoetOpt) *HTTPPoetTestHarness { tb.Helper() ctx, cancel := context.WithCancel(logging.NewContext(context.Background(), zaptest.NewLogger(tb))) @@ -197,6 +210,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) + initialPost := fullPost(post, info, shared.ZeroChallenge) client, err := activation.NewHTTPPoetClient( types.PoetServer{Address: poetProver.RestURL().String()}, @@ -205,7 +219,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { ) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), initialPost) certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) @@ -347,7 +361,8 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { eg.Go(func() error { post, info, err := nb.Proof(context.Background(), sig.NodeID(), shared.ZeroChallenge) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + initialPost := fullPost(post, info, shared.ZeroChallenge) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), initialPost) certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index 926b57dcaf..633878ad04 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -101,6 +101,7 @@ func TestValidator_Validate(t *testing.T) { require.NoError(t, err) post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) + initialPost := fullPost(post, info, shared.ZeroChallenge) challenge := types.RandomHash() nb, err := activation.NewNIPostBuilder( @@ -114,7 +115,7 @@ func TestValidator_Validate(t *testing.T) { ) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), post, info, shared.ZeroChallenge) + certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), initialPost) certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) require.NoError(t, err) diff --git a/systest/tests/distributed_post_verification_test.go b/systest/tests/distributed_post_verification_test.go index fb04b8a52a..69b7be119f 100644 --- a/systest/tests/distributed_post_verification_test.go +++ b/systest/tests/distributed_post_verification_test.go @@ -35,6 +35,7 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" + "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" "github.com/spacemeshos/go-spacemesh/systest/cluster" "github.com/spacemeshos/go-spacemesh/systest/testcontext" "github.com/spacemeshos/go-spacemesh/timesync" @@ -179,9 +180,8 @@ func TestPostMalfeasanceProof(t *testing.T) { // 2.1. Create initial POST var ( - challenge *types.NIPostChallenge - post *types.Post - postInfo *types.PostInfo + initialPost *nipost.Post + challenge *types.NIPostChallenge ) for { client, err := grpcPostService.Client(signer.NodeID()) @@ -191,7 +191,7 @@ func TestPostMalfeasanceProof(t *testing.T) { continue } ctx.Log.Info("poet service to connected") - post, postInfo, err = client.Proof(ctx, shared.ZeroChallenge) + post, postInfo, err := client.Proof(ctx, shared.ZeroChallenge) require.NoError(t, err) challenge = &types.NIPostChallenge{ @@ -204,7 +204,7 @@ func TestPostMalfeasanceProof(t *testing.T) { break } // 2.2 Certify initial POST - certClient := activation.NewCertifierClient(logger.Named("certifier"), post, postInfo, shared.ZeroChallenge) + certClient := activation.NewCertifierClient(logger.Named("certifier"), signer.NodeID(), initialPost) certifier := activation.NewCertifier(localsql.InMemory(), logger, certClient) certifier.CertifyAll(context.Background(), []activation.PoetClient{poetClient}) diff --git a/systest/tests/poets_test.go b/systest/tests/poets_test.go index 2c09705b82..8f19b5fae2 100644 --- a/systest/tests/poets_test.go +++ b/systest/tests/poets_test.go @@ -211,6 +211,7 @@ func TestNodesUsingDifferentPoets(t *testing.T) { // - supporting PoW only // - supporting certificates // TODO: When PoW support is removed, convert this test to verify only the cert path. +// https://github.com/spacemeshos/go-spacemesh/issues/5212 func TestRegisteringInPoetWithPowAndCert(t *testing.T) { t.Parallel() tctx := testcontext.New(t, testcontext.Labels("sanity")) From 601df9df82956ec3bc3e407a2771026a15cce6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 18 Apr 2024 12:59:11 +0200 Subject: [PATCH 42/55] More review fixes --- activation/activation_test.go | 4 +++ activation/certifier.go | 32 ++------------------- activation/certifier_test.go | 8 +++--- activation/e2e/poet_test.go | 2 +- activation/interface.go | 9 ++---- activation/mocks.go | 52 +++++++++++++++++------------------ activation/nipost.go | 28 ++++++++++--------- activation/nipost_test.go | 32 ++++++++++----------- activation/poet.go | 10 +++++-- 9 files changed, 78 insertions(+), 99 deletions(-) diff --git a/activation/activation_test.go b/activation/activation_test.go index 53c4ec30a7..7e4d7d4876 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1444,6 +1444,10 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { case <-time.After(5 * time.Second): require.FailNow(t, "timed out waiting for activation event") } + + // state is cleaned up + _, err = nipost.Challenge(tab.localDB, sig.NodeID()) + require.ErrorIs(t, err, sql.ErrNotFound) } func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { diff --git a/activation/certifier.go b/activation/certifier.go index 15cfabebd0..7b10ee15b8 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -3,7 +3,6 @@ package activation import ( "bytes" "context" - "encoding/base64" "encoding/json" "errors" "fmt" @@ -34,33 +33,6 @@ type CertifierClientConfig struct { MaxRetries int `mapstructure:"max-retries"` } -type Base64Enc struct { - Inner []byte -} - -func (e Base64Enc) String() string { - return base64.RawStdEncoding.EncodeToString(e.Inner) -} - -// Set implements pflag.Value.Set. -func (e *Base64Enc) Set(value string) error { - return e.UnmarshalText([]byte(value)) -} - -// Type implements pflag.Value.Type. -func (Base64Enc) Type() string { - return "Base64Enc" -} - -func (e *Base64Enc) UnmarshalText(text []byte) error { - b, err := base64.StdEncoding.DecodeString(string(text)) - if err != nil { - return err - } - e.Inner = b - return nil -} - type CertifierConfig struct { Client CertifierClientConfig `mapstructure:"client"` } @@ -124,7 +96,7 @@ func NewCertifier( return c } -func (c *Certifier) GetCertificate(poet string) *certifier.PoetCert { +func (c *Certifier) Certificate(poet string) *certifier.PoetCert { cert, err := certifier.Certificate(c.db, c.client.Id(), poet) switch { case err == nil: @@ -160,7 +132,7 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri certs := make(map[string]*certifier.PoetCert) poetsToCertify := []PoetClient{} for _, poet := range poets { - if cert := c.GetCertificate(poet.Address()); cert != nil { + if cert := c.Certificate(poet.Address()); cert != nil { certs[poet.Address()] = cert } else { poetsToCertify = append(poetsToCertify, poet) diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 6a8f205f4f..cd3e392ebe 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -33,19 +33,19 @@ func TestPersistsCerts(t *testing.T) { Certify(gomock.Any(), &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). Return(cert, nil) - require.Nil(t, c.GetCertificate("http://poet")) + require.Nil(t, c.Certificate("http://poet")) got, err := c.Recertify(context.Background(), poetMock) require.NoError(t, err) require.Equal(t, cert, got) - got = c.GetCertificate("http://poet") + got = c.Certificate("http://poet") require.Equal(t, cert, got) - require.Nil(t, c.GetCertificate("http://other-poet")) + require.Nil(t, c.Certificate("http://other-poet")) } { // Create new certifier and check that it loads the certs back. c := activation.NewCertifier(db, zaptest.NewLogger(t), client) - got := c.GetCertificate("http://poet") + got := c.Certificate("http://poet") require.Equal(t, cert, got) } } diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index 138d6906b6..d7634fa81e 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -172,7 +172,7 @@ func TestHTTPPoet(t *testing.T) { signer.NodeID(), activation.PoetAuth{PoetCert: &certifier.PoetCert{Data: []byte("oops")}}, ) - require.ErrorIs(t, err, activation.ErrUnathorized) + require.ErrorIs(t, err, activation.ErrUnauthorized) }) } diff --git a/activation/interface.go b/activation/interface.go index 6780247ce3..0e43fb3c7c 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -121,11 +121,6 @@ type SmeshingProvider interface { SetCoinbase(coinbase types.Address) } -type PoetAuth struct { - *PoetPoW - *certifier.PoetCert -} - // PoetClient servers as an interface to communicate with a PoET server. // It is used to submit challenges and fetch proofs. type PoetClient interface { @@ -161,11 +156,11 @@ type certifierClient interface { Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) } -// certifierService is used to certify nodeID for registerting in the poet. +// certifierService is used to certify nodeID for registering in the poet. // It holds the certificates and can recertify if needed. type certifierService interface { // Acquire a certificate for the given poet. - GetCertificate(poet string) *certifier.PoetCert + Certificate(poet string) *certifier.PoetCert // Recertify the nodeID and return a certificate confirming that // it is verified. The certificate can be later used to submit in poet. Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) diff --git a/activation/mocks.go b/activation/mocks.go index 15860d2603..36714157c4 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1831,78 +1831,78 @@ func (m *MockcertifierService) EXPECT() *MockcertifierServiceMockRecorder { return m.recorder } -// CertifyAll mocks base method. -func (m *MockcertifierService) CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert { +// Certificate mocks base method. +func (m *MockcertifierService) Certificate(poet string) *certifier.PoetCert { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CertifyAll", ctx, poets) - ret0, _ := ret[0].(map[string]*certifier.PoetCert) + ret := m.ctrl.Call(m, "Certificate", poet) + ret0, _ := ret[0].(*certifier.PoetCert) return ret0 } -// CertifyAll indicates an expected call of CertifyAll. -func (mr *MockcertifierServiceMockRecorder) CertifyAll(ctx, poets any) *MockcertifierServiceCertifyAllCall { +// Certificate indicates an expected call of Certificate. +func (mr *MockcertifierServiceMockRecorder) Certificate(poet any) *MockcertifierServiceCertificateCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifyAll", reflect.TypeOf((*MockcertifierService)(nil).CertifyAll), ctx, poets) - return &MockcertifierServiceCertifyAllCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certificate", reflect.TypeOf((*MockcertifierService)(nil).Certificate), poet) + return &MockcertifierServiceCertificateCall{Call: call} } -// MockcertifierServiceCertifyAllCall wrap *gomock.Call -type MockcertifierServiceCertifyAllCall struct { +// MockcertifierServiceCertificateCall wrap *gomock.Call +type MockcertifierServiceCertificateCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockcertifierServiceCertifyAllCall) Return(arg0 map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertificateCall) Return(arg0 *certifier.PoetCert) *MockcertifierServiceCertificateCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceCertifyAllCall) Do(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertificateCall) Do(f func(string) *certifier.PoetCert) *MockcertifierServiceCertificateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertificateCall) DoAndReturn(f func(string) *certifier.PoetCert) *MockcertifierServiceCertificateCall { c.Call = c.Call.DoAndReturn(f) return c } -// GetCertificate mocks base method. -func (m *MockcertifierService) GetCertificate(poet string) *certifier.PoetCert { +// CertifyAll mocks base method. +func (m *MockcertifierService) CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCertificate", poet) - ret0, _ := ret[0].(*certifier.PoetCert) + ret := m.ctrl.Call(m, "CertifyAll", ctx, poets) + ret0, _ := ret[0].(map[string]*certifier.PoetCert) return ret0 } -// GetCertificate indicates an expected call of GetCertificate. -func (mr *MockcertifierServiceMockRecorder) GetCertificate(poet any) *MockcertifierServiceGetCertificateCall { +// CertifyAll indicates an expected call of CertifyAll. +func (mr *MockcertifierServiceMockRecorder) CertifyAll(ctx, poets any) *MockcertifierServiceCertifyAllCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificate", reflect.TypeOf((*MockcertifierService)(nil).GetCertificate), poet) - return &MockcertifierServiceGetCertificateCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifyAll", reflect.TypeOf((*MockcertifierService)(nil).CertifyAll), ctx, poets) + return &MockcertifierServiceCertifyAllCall{Call: call} } -// MockcertifierServiceGetCertificateCall wrap *gomock.Call -type MockcertifierServiceGetCertificateCall struct { +// MockcertifierServiceCertifyAllCall wrap *gomock.Call +type MockcertifierServiceCertifyAllCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockcertifierServiceGetCertificateCall) Return(arg0 *certifier.PoetCert) *MockcertifierServiceGetCertificateCall { +func (c *MockcertifierServiceCertifyAllCall) Return(arg0 map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceGetCertificateCall) Do(f func(string) *certifier.PoetCert) *MockcertifierServiceGetCertificateCall { +func (c *MockcertifierServiceCertifyAllCall) Do(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceGetCertificateCall) DoAndReturn(f func(string) *certifier.PoetCert) *MockcertifierServiceGetCertificateCall { +func (c *MockcertifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index d59f6fd10b..74454baa5b 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -348,7 +348,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( // FIXME: remove support for deprecated poet PoW auth := PoetAuth{ - PoetCert: certifier.GetCertificate(client.Address()), + PoetCert: certifier.Certificate(client.Address()), } if auth.PoetCert == nil { logger.Info("missing poet cert - falling back to PoW") @@ -388,21 +388,23 @@ func (nb *NIPostBuilder) submitPoetChallenge( err error ) - for try := 0; try < 2; try++ { - round, err = client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, auth) - if err == nil { - break + round, err = client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, auth) + switch { + case errors.Is(err, ErrUnauthorized): + logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) + auth.PoetCert, err = certifier.Recertify(ctx, client) + if err != nil { + return &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} } - if errors.Is(err, ErrUnathorized) && try == 0 { - logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) - auth.PoetCert, err = certifier.Recertify(ctx, client) - if err != nil { - return &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} - } - } else { - return &PoetSvcUnstableError{msg: "failed to submit challenge to poet service", source: err} + round, err = client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, auth) + if err != nil { + return &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} } + case err != nil: + return &PoetSvcUnstableError{msg: "failed to submit challenge to poet service", source: err} + default: // err == nil } + logger.Info("challenge submitted to poet proving service", zap.String("round", round.ID)) return nipost.AddPoetRegistration(nb.localDB, nodeID, nipost.PoETRegistration{ ChallengeHash: types.Hash32(challenge), diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 5b96c713da..6011471028 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -409,7 +409,7 @@ func Test_NIPostBuilder_WithMocks(t *testing.T) { require.NoError(t, err) mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) require.NoError(t, err) @@ -451,7 +451,7 @@ func TestPostSetup(t *testing.T) { require.NoError(t, err) mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().GetCertificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) require.NoError(t, err) @@ -511,7 +511,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { mCertifier := NewMockcertifierService(ctrl) mCertifier.EXPECT(). - GetCertificate(poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) + Certificate(poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challengeHash, mCertifier) require.NoError(t, err) require.NotNil(t, nipost) @@ -632,8 +632,8 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. require.NoError(t, err) mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().GetCertificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - mCertifier.EXPECT().GetCertificate(poets[1].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(poets[1].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) // Act nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) @@ -704,9 +704,9 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { require.NoError(t, err) mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().GetCertificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) // No certs - fallback to PoW - mCertifier.EXPECT().GetCertificate(poets[1].Address()).Return(nil) + mCertifier.EXPECT().Certificate(poets[1].Address()).Return(nil) // Act nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) @@ -752,7 +752,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) + certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) @@ -793,7 +793,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) + certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) require.ErrorIs(t, err, ErrPoetServiceUnstable) @@ -820,7 +820,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) + certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) require.ErrorIs(t, err, ErrPoetProofNotReceived) @@ -850,7 +850,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) + certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) require.ErrorIs(t, err, ErrPoetProofNotReceived) @@ -893,7 +893,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { mCertifier := NewMockcertifierService(ctrl) invalid := &certifier.PoetCert{} - getInvalid := mCertifier.EXPECT().GetCertificate("http://localhost:9999").Return(invalid) + getInvalid := mCertifier.EXPECT().Certificate("http://localhost:9999").Return(invalid) submitFailed := poetProver.EXPECT(). Submit( gomock.Any(), @@ -905,7 +905,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { PoetAuth{PoetCert: invalid}, ). After(getInvalid.Call). - Return(nil, ErrUnathorized) + Return(nil, ErrUnauthorized) valid := &certifier.PoetCert{Data: []byte("valid")} recertify := mCertifier.EXPECT().Recertify(gomock.Any(), poetProver).After(submitFailed).Return(valid, nil) @@ -1135,7 +1135,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { require.NoError(t, err) certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) + certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) // Act nipost, err := nb.BuildNIPost(buildCtx, sig, postGenesisEpoch+2, challenge, certifier) @@ -1147,7 +1147,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&types.PoetRound{}, nil) - certifier.EXPECT().GetCertificate("http://localhost:9999").Return(cert) + certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) nipost, err = nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) require.NoError(t, err) @@ -1355,7 +1355,7 @@ func TestNIPostBuilder_Close(t *testing.T) { challenge := types.RandomHash() mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().GetCertificate("http://localhost:9999").Return(&certifier.PoetCert{}) + mCertifier.EXPECT().Certificate("http://localhost:9999").Return(&certifier.PoetCert{}) _, err = nb.BuildNIPost(ctx, sig, postGenesisEpoch+2, challenge, mCertifier) require.Error(t, err) diff --git a/activation/poet.go b/activation/poet.go index 961de06a89..8b1c4351db 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -18,11 +18,12 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) var ( ErrInvalidRequest = errors.New("invalid request") - ErrUnathorized = errors.New("unauthorized") + ErrUnauthorized = errors.New("unauthorized") ) type PoetPowParams struct { @@ -35,6 +36,11 @@ type PoetPoW struct { Params PoetPowParams } +type PoetAuth struct { + *PoetPoW + *certifier.PoetCert +} + // HTTPPoetClient implements PoetProvingServiceClient interface. type HTTPPoetClient struct { baseURL *url.URL @@ -288,7 +294,7 @@ func (c *HTTPPoetClient) req(ctx context.Context, method, path string, reqBody, case http.StatusBadRequest: return fmt.Errorf("%w: response status code: %s, body: %s", ErrInvalidRequest, res.Status, string(data)) case http.StatusUnauthorized: - return fmt.Errorf("%w: response status code: %s, body: %s", ErrUnathorized, res.Status, string(data)) + return fmt.Errorf("%w: response status code: %s, body: %s", ErrUnauthorized, res.Status, string(data)) default: return fmt.Errorf("unrecognized error: status code: %s, body: %s", res.Status, string(data)) } From af1e89a869cef2c51f7ea2756f5c0fdb96bb9900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 18 Apr 2024 13:10:58 +0200 Subject: [PATCH 43/55] Update poet and post-rs --- Makefile-libs.Inc | 2 +- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- systest/Makefile | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Makefile-libs.Inc b/Makefile-libs.Inc index a223cedab8..e97b656095 100644 --- a/Makefile-libs.Inc +++ b/Makefile-libs.Inc @@ -50,7 +50,7 @@ else endif endif -POSTRS_SETUP_REV = 0.7.6 +POSTRS_SETUP_REV = 0.7.7 POSTRS_SETUP_ZIP = libpost-$(platform)-v$(POSTRS_SETUP_REV).zip POSTRS_SETUP_URL_ZIP ?= https://github.com/spacemeshos/post-rs/releases/download/v$(POSTRS_SETUP_REV)/$(POSTRS_SETUP_ZIP) diff --git a/go.mod b/go.mod index afe0b00380..33c7d647b5 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/spacemeshos/fixed v0.1.1 github.com/spacemeshos/go-scale v1.2.0 github.com/spacemeshos/merkle-tree v0.2.3 - github.com/spacemeshos/poet v0.10.3-0.20240325083037-7ba2761c0a35 + github.com/spacemeshos/poet v0.10.3 github.com/spacemeshos/post v0.12.6 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 @@ -205,9 +205,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.19.0 // indirect @@ -216,7 +216,7 @@ require ( google.golang.org/api v0.170.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 65dfd7978f..588375b9f3 100644 --- a/go.sum +++ b/go.sum @@ -566,8 +566,8 @@ github.com/spacemeshos/go-scale v1.2.0 h1:ZlA2L1ILym2gmyJUwUdLTiyP1ZIG0U4xE9nFVF github.com/spacemeshos/go-scale v1.2.0/go.mod h1:HV6e3/X5h9u2aFpYKJxt7PY/fBuLBegEKWgeZJ+/5jE= github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas= github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww= -github.com/spacemeshos/poet v0.10.3-0.20240325083037-7ba2761c0a35 h1:jtnlrIPValFEIxrksCDQkZi0H0PhjeqGwuxvw155yVw= -github.com/spacemeshos/poet v0.10.3-0.20240325083037-7ba2761c0a35/go.mod h1:+H37RV00jlztjRvKML/cBp/DIAGCweOgjNGaI814+W8= +github.com/spacemeshos/poet v0.10.3 h1:ZDPqihukDphdM+Jr/3xgn7vXadheaRMa6wF70Zsv4fg= +github.com/spacemeshos/poet v0.10.3/go.mod h1:TPZ/aX+YIgIqs/bvYTcJIwUWEUzvZw6jueFPxdhCGpY= github.com/spacemeshos/post v0.12.6 h1:BtKK4n8qa7d0APtQZ2KBx9fQpx1B5LSt2OD7XIgE8g4= github.com/spacemeshos/post v0.12.6/go.mod h1:NEstvZ4BKHuiGTcb+H+cQsZiNSh0G7GOLjZv6jjnHxM= github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI= @@ -720,8 +720,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -770,8 +770,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= @@ -840,8 +840,8 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= diff --git a/systest/Makefile b/systest/Makefile index a7d9de49e4..c980c6df2e 100644 --- a/systest/Makefile +++ b/systest/Makefile @@ -5,9 +5,9 @@ tmpfile := $(shell mktemp /tmp/systest-XXX) test_name ?= TestSmeshing org ?= spacemeshos image_name ?= $(org)/systest:$(version_info) -certifier_image ?= spacemeshos/certifier-service:51e174bbdb417bca21d4158ac04b48ebcb291fe1 -poet_image ?= $(org)/poet:v0.10.2-42-gceab169 -post_service_image ?= $(org)/post-service:v0.7.6 +certifier_image ?= spacemeshos/certifier-service:v0.7.7 +poet_image ?= $(org)/poet:v0.10.3 +post_service_image ?= $(org)/post-service:v0.7.7 post_init_image ?= $(org)/postcli:v0.12.5 smesher_image ?= $(org)/go-spacemesh-dev:$(version_info) bs_image ?= $(org)/go-spacemesh-dev-bs:$(version_info) From b7426fa3349586c6d0567eb5d4e5e8658b92a622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 19 Apr 2024 09:00:13 +0200 Subject: [PATCH 44/55] update test --- activation/activation_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activation/activation_test.go b/activation/activation_test.go index 7e4d7d4876..f9e119751c 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -1487,7 +1487,7 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), post.CommitmentATX, initialPost, meta, post.NumUnits). Return(nil) - _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) + _, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) posEpoch := postGenesisEpoch + 1 From af2962419131985b7d09b51d1aa85e7a41e4312b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 23 Apr 2024 09:48:53 +0200 Subject: [PATCH 45/55] Fix UTs --- activation/activation.go | 12 ++-- activation/activation_multi_test.go | 4 +- activation/activation_test.go | 91 ++++++++--------------------- activation/validation.go | 17 +----- sql/atxs/atxs.go | 14 +++++ 5 files changed, 49 insertions(+), 89 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 64b46c244e..02fb977472 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -385,7 +385,8 @@ func (b *Builder) obtainPostFromLastAtx(ctx context.Context, nodeId types.NodeID if err != nil { return nil, fmt.Errorf("failed to retrieve ATX: %w", err) } - if atx.NIPost == nil { + atxNipost, err := atxs.Nipost(ctx, b.db, atxid) + if err != nil { return nil, errors.New("no NIPoST found in last ATX") } if atx.CommitmentATX == nil { @@ -405,10 +406,10 @@ func (b *Builder) obtainPostFromLastAtx(ctx context.Context, nodeId types.NodeID b.log.Info("found POST in an existing ATX", zap.String("atx_id", atxid.Hash32().ShortString())) return &nipost.Post{ - Nonce: atx.NIPost.Post.Nonce, - Indices: atx.NIPost.Post.Indices, - Pow: atx.NIPost.Post.Pow, - Challenge: atx.NIPost.PostMetadata.Challenge, + Nonce: atxNipost.Post.Nonce, + Indices: atxNipost.Post.Indices, + Pow: atxNipost.Post.Pow, + Challenge: atxNipost.PostMetadata.Challenge, NumUnits: atx.NumUnits, CommitmentATX: *atx.CommitmentATX, VRFNonce: *atx.VRFNonce, @@ -748,7 +749,6 @@ func (b *Builder) createAtx( sig *signing.EdSigner, challenge *wire.NIPostChallengeV1, ) (*wire.ActivationTxV1, error) { - b.smeshingMutex.Lock() certifier := b.certifiers[sig.NodeID()] b.smeshingMutex.Unlock() diff --git a/activation/activation_multi_test.go b/activation/activation_multi_test.go index a54b95d5e4..8f80cb4bf1 100644 --- a/activation/activation_multi_test.go +++ b/activation/activation_multi_test.go @@ -397,7 +397,9 @@ func Test_Builder_Multi_HappyPath(t *testing.T) { VRFNonce: types.VRFPostIndex(rand.Uint64()), } nipostState[sig.NodeID()] = state - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), sig, ref.PublishEpoch, ref.Hash(), gomock.Any()).Return(state, nil) + tab.mnipost.EXPECT(). + BuildNIPost(gomock.Any(), sig, ref.PublishEpoch, ref.Hash(), gomock.Any()). + Return(state, nil) // awaiting atx publication epoch log tab.mclock.EXPECT().CurrentLayer().DoAndReturn( diff --git a/activation/activation_test.go b/activation/activation_test.go index 81682ce5b2..89644ec9dc 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -16,6 +16,7 @@ import ( "go.uber.org/mock/gomock" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest/observer" "golang.org/x/exp/maps" "golang.org/x/sync/errgroup" @@ -80,7 +81,11 @@ type testAtxBuilder struct { func newTestBuilder(tb testing.TB, numSigners int, opts ...BuilderOption) *testAtxBuilder { observer, observedLogs := observer.New(zapcore.DebugLevel) - logger := zap.New(observer) + logger := zaptest.NewLogger(tb, zaptest.WrapOptions( + zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewTee(core, observer) + }), + )) ctrl := gomock.NewController(tb) tab := &testAtxBuilder{ @@ -394,18 +399,9 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { // current layer is too late to be able to build a nipost on time currLayer := (postGenesisEpoch + 1).FirstLayer() - ch := types.NIPostChallenge{ - Sequence: 1, - PrevATXID: types.EmptyATXID, - PublishEpoch: postGenesisEpoch, - PositioningATX: tab.goldenATXID, - CommitmentATX: &tab.goldenATXID, - } - prevAtx := newAtx(ch, 2, types.Address{}) - require.NoError(t, SignAndFinalizeAtx(sig, prevAtx)) - vPrevAtx, err := prevAtx.Verify(0, 1) - require.NoError(t, err) - require.NoError(t, atxs.Add(tab.db, vPrevAtx)) + prevAtx := newInitialATXv1(t, tab.goldenATXID) + prevAtx.Sign(sig) + require.NoError(t, atxs.Add(tab.db, toVerifiedAtx(t, prevAtx))) tab.mclock.EXPECT().CurrentLayer().Return(currLayer).AnyTimes() tab.mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( @@ -441,7 +437,7 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { require.NoError(t, eg.Wait()) // state is cleaned up - _, err = nipost.Challenge(tab.localDB, sig.NodeID()) + _, err := nipost.Challenge(tab.localDB, sig.NodeID()) require.ErrorIs(t, err, sql.ErrNotFound) } @@ -1200,23 +1196,11 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { WithPoetRetryInterval(retryInterval), ) sig := maps.Values(tab.signers)[0] - posEpoch := types.EpochID(0) - challenge := types.NIPostChallenge{ - Sequence: 1, - PrevATXID: types.ATXID{1, 2, 3}, - PublishEpoch: posEpoch, - PositioningATX: types.ATXID{1, 2, 3}, - CommitmentATX: &types.ATXID{'c', 'a', 't', 'x'}, - } - poetBytes := []byte("66666") - prevAtx := newAtx(challenge, 2, types.Address{}) - require.NoError(t, SignAndFinalizeAtx(sig, prevAtx)) - vPrevAtx, err := prevAtx.Verify(0, 1) - require.NoError(t, err) - require.NoError(t, atxs.Add(tab.db, vPrevAtx)) + prevAtx := newInitialATXv1(t, tab.goldenATXID) + prevAtx.Sign(sig) + require.NoError(t, atxs.Add(tab.db, toVerifiedAtx(t, prevAtx))) - publishEpoch := posEpoch + 1 - currLayer := posEpoch.FirstLayer() + currLayer := prevAtx.PublishEpoch.FirstLayer() tab.mclock.EXPECT().CurrentLayer().DoAndReturn(func() types.LayerID { return currLayer }).AnyTimes() tab.mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { @@ -1226,7 +1210,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { }).AnyTimes() done := make(chan struct{}) close(done) - tab.mclock.EXPECT().AwaitLayer(publishEpoch.FirstLayer()).DoAndReturn( + tab.mclock.EXPECT().AwaitLayer(prevAtx.PublishEpoch.Add(1).FirstLayer()).DoAndReturn( func(got types.LayerID) <-chan struct{} { // advance to publish layer if currLayer.Before(got) { @@ -1257,7 +1241,7 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { return nil, ErrPoetServiceUnstable } close(builderConfirmation) - return newNIPostWithPoet(t, poetBytes), nil + return newNIPostWithPoet(t, []byte("poet")), nil }, ) @@ -1431,43 +1415,18 @@ func TestBuilder_obtainPost(t *testing.T) { t.Run("initial POST unavailable but ATX exists", func(t *testing.T) { tab := newTestBuilder(t, 1) sig := maps.Values(tab.signers)[0] - commitmentAtxId := types.RandomATXID() - challenge := types.NIPostChallenge{ - PublishEpoch: 2, - Sequence: 0, - PrevATXID: types.EmptyATXID, - PositioningATX: types.RandomATXID(), - CommitmentATX: &commitmentAtxId, - InitialPost: &types.Post{}, - } - nipost := &types.NIPost{ - Post: &types.Post{ - Nonce: 5, - Indices: []byte{1, 2, 3, 4}, - Pow: 7, - }, - PostMetadata: &types.PostMetadata{ - Challenge: []byte("66666"), - LabelsPerUnit: 777, - }, - } - vrf := types.VRFPostIndex(1234) - atx := types.NewActivationTx(challenge, types.Address{}, nipost, 2, &vrf) - atx.SetEffectiveNumUnits(2) - atx.SetReceived(time.Now()) - require.NoError(t, SignAndFinalizeAtx(sig, atx)) - vAtx, err := atx.Verify(0, 1) - require.NoError(t, err) - require.NoError(t, atxs.Add(tab.db, vAtx)) + atx := newInitialATXv1(t, tab.goldenATXID) + atx.Sign(sig) + require.NoError(t, atxs.Add(tab.db, toVerifiedAtx(t, atx))) post, err := tab.obtainPost(context.Background(), sig.NodeID()) require.NoError(t, err) - require.Equal(t, commitmentAtxId, post.CommitmentATX) - require.Equal(t, uint32(2), post.NumUnits) - require.Equal(t, nipost.PostMetadata.Challenge, post.Challenge) - require.Equal(t, nipost.Post.Indices, post.Indices) - require.Equal(t, nipost.Post.Pow, post.Pow) - require.Equal(t, nipost.Post.Nonce, post.Nonce) + require.Equal(t, atx.CommitmentATXID, &post.CommitmentATX) + require.Equal(t, atx.NumUnits, post.NumUnits) + require.Equal(t, atx.NIPost.PostMetadata.Challenge, post.Challenge) + require.Equal(t, atx.NIPost.Post.Indices, post.Indices) + require.Equal(t, atx.NIPost.Post.Pow, post.Pow) + require.Equal(t, atx.NIPost.Post.Nonce, post.Nonce) }) } diff --git a/activation/validation.go b/activation/validation.go index 7580489c6b..012e8f809e 100644 --- a/activation/validation.go +++ b/activation/validation.go @@ -15,7 +15,6 @@ import ( "github.com/spacemeshos/go-spacemesh/activation/metrics" "github.com/spacemeshos/go-spacemesh/activation/wire" - "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -365,20 +364,6 @@ func (v *Validator) VerifyChain(ctx context.Context, id, goldenATXID types.ATXID return v.verifyChainWithOpts(ctx, id, goldenATXID, options) } -func (v *Validator) getNipost(ctx context.Context, id types.ATXID) (*types.NIPost, error) { - var blob sql.Blob - if err := atxs.LoadBlob(ctx, v.db, id.Bytes(), &blob); err != nil { - return nil, fmt.Errorf("getting blob for %s: %w", id, err) - } - - // TODO: decide about version based on publish epoch - var atx wire.ActivationTxV1 - if err := codec.Decode(blob.Bytes, &atx); err != nil { - return nil, fmt.Errorf("decoding ATX blob: %w", err) - } - return wire.NiPostFromWireV1(atx.NIPost), nil -} - func (v *Validator) verifyChainWithOpts( ctx context.Context, id, goldenATXID types.ATXID, @@ -420,7 +405,7 @@ func (v *Validator) verifyChainWithOpts( commitmentAtxId = &atxId } } - nipost, err := v.getNipost(ctx, id) + nipost, err := atxs.Nipost(ctx, v.db, id) if err != nil { return fmt.Errorf("getting NIPost for ATX %s: %w", id, err) } diff --git a/sql/atxs/atxs.go b/sql/atxs/atxs.go index 8ff95eb95a..a19f7e06b0 100644 --- a/sql/atxs/atxs.go +++ b/sql/atxs/atxs.go @@ -811,3 +811,17 @@ func PoetProofRef(ctx context.Context, db sql.Executor, id types.ATXID) (types.P return types.PoetProofRef(atx.NIPost.PostMetadata.Challenge), nil } + +func Nipost(ctx context.Context, db sql.Executor, id types.ATXID) (*types.NIPost, error) { + var blob sql.Blob + if err := LoadBlob(ctx, db, id.Bytes(), &blob); err != nil { + return nil, fmt.Errorf("getting blob for %s: %w", id, err) + } + + // TODO: decide about version based on the `version` column + var atx wire.ActivationTxV1 + if err := codec.Decode(blob.Bytes, &atx); err != nil { + return nil, fmt.Errorf("decoding ATX blob: %w", err) + } + return wire.NiPostFromWireV1(atx.NIPost), nil +} From 0ec2d978bb1b45667d23a4408f87082bddf7ba85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 23 Apr 2024 11:40:07 +0200 Subject: [PATCH 46/55] Refactor certifier to lookup PoST on its own --- activation/activation.go | 154 ++++-------------- activation/activation_multi_test.go | 6 +- activation/activation_test.go | 140 +++------------- activation/certifier.go | 144 ++++++++++++---- activation/certifier_test.go | 80 +++++++-- activation/e2e/certifier_client_test.go | 26 +-- activation/e2e/nipost_test.go | 23 +-- activation/e2e/validation_test.go | 11 +- activation/interface.go | 15 +- activation/mocks.go | 98 ++++------- activation/nipost.go | 19 ++- activation/nipost_test.go | 120 ++++++-------- node/node.go | 16 +- .../distributed_post_verification_test.go | 33 ++-- 14 files changed, 410 insertions(+), 475 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 02fb977472..569b55a675 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -75,8 +75,8 @@ type Builder struct { localDB *localsql.Database publisher pubsub.Publisher nipostBuilder nipostBuilder + certifier certifierService validator nipostValidator - certifierConfig CertifierConfig layerClock layerClock syncer syncer log *zap.Logger @@ -94,7 +94,6 @@ type Builder struct { // since they (can) modify the fields below. smeshingMutex sync.Mutex signers map[types.NodeID]*signing.EdSigner - certifiers map[types.NodeID]certifierService eg errgroup.Group stop context.CancelFunc } @@ -147,9 +146,9 @@ func WithPostStates(ps PostStates) BuilderOption { } } -func WithCertifierConfig(c CertifierConfig) BuilderOption { +func WithPoetCertifier(c certifierService) BuilderOption { return func(b *Builder) { - b.certifierConfig = c + b.certifier = c } } @@ -173,14 +172,13 @@ func NewBuilder( localDB: localDB, publisher: publisher, nipostBuilder: nipostBuilder, + certifier: &disabledCertifier{}, layerClock: layerClock, syncer: syncer, log: log, poetRetryInterval: defaultPoetRetryInterval, postValidityDelay: 12 * time.Hour, postStates: NewPostStates(log), - certifiers: make(map[types.NodeID]certifierService), - certifierConfig: DefaultCertifierConfig(), } for _, opt := range opts { opt(b) @@ -320,18 +318,34 @@ func (b *Builder) SmesherIDs() []types.NodeID { return maps.Keys(b.signers) } -// Create the initial post and save it. -func (b *Builder) buildInitialPost(ctx context.Context, nodeID types.NodeID) (*types.Post, *types.PostInfo, error) { +func (b *Builder) buildInitialPost(ctx context.Context, nodeID types.NodeID) error { + // Generate the initial POST if we don't have an ATX... + if _, err := atxs.GetLastIDByNodeID(b.db, nodeID); err == nil { + return nil + } + // ...and if we haven't stored an initial post yet. + _, err := nipost.GetPost(b.localDB, nodeID) + switch { + case err == nil: + b.log.Info("load initial post from db") + return nil + case errors.Is(err, sql.ErrNotFound): + b.log.Info("creating initial post") + default: + return fmt.Errorf("get initial post: %w", err) + } + + // Create the initial post and save it. startTime := time.Now() post, postInfo, err := b.nipostBuilder.Proof(ctx, nodeID, shared.ZeroChallenge) if err != nil { - return nil, nil, fmt.Errorf("post execution: %w", err) + return fmt.Errorf("post execution: %w", err) } if postInfo.Nonce == nil { b.log.Error("initial PoST is invalid: missing VRF nonce. Check your PoST data", log.ZShortStringer("smesherID", nodeID), ) - return nil, nil, errors.New("nil VRF nonce") + return errors.New("nil VRF nonce") } initialPost := nipost.Post{ Nonce: post.Nonce, @@ -349,128 +363,36 @@ func (b *Builder) buildInitialPost(ctx context.Context, nodeID types.NodeID) (*t }, postInfo.NumUnits) if err != nil { b.log.Error("initial POST is invalid", log.ZShortStringer("smesherID", nodeID), zap.Error(err)) - return nil, nil, fmt.Errorf("initial POST is invalid: %w", err) - } - - if err := nipost.AddPost(b.localDB, nodeID, initialPost); err != nil { - b.log.Error("failed to save initial post", zap.Error(err)) + if err := nipost.RemovePost(b.localDB, nodeID); err != nil { + b.log.Fatal("failed to remove initial post", log.ZShortStringer("smesherID", nodeID), zap.Error(err)) + } + return fmt.Errorf("initial POST is invalid: %w", err) } metrics.PostDuration.Set(float64(time.Since(startTime).Nanoseconds())) public.PostSeconds.Set(float64(time.Since(startTime))) b.log.Info("created the initial post") - return post, postInfo, nil -} - -// Obtain certificates for the poets. -// We want to certify immediately after the startup or creating the initial POST -// to avoid all nodes spamming the certifier at the same time when -// submitting to the poets. -func (b *Builder) certifyPost(ctx context.Context, nodeID types.NodeID, post *nipost.Post) { - client := NewCertifierClient(b.log, nodeID, post, WithCertifierClientConfig(b.certifierConfig.Client)) - certifier := NewCertifier(b.localDB, b.log, client) - certifier.CertifyAll(ctx, b.poets) - b.smeshingMutex.Lock() - b.certifiers[nodeID] = certifier - b.smeshingMutex.Unlock() + return nipost.AddPost(b.localDB, nodeID, initialPost) } -func (b *Builder) obtainPostFromLastAtx(ctx context.Context, nodeId types.NodeID) (*nipost.Post, error) { - atxid, err := atxs.GetLastIDByNodeID(b.db, nodeId) - if err != nil { - return nil, fmt.Errorf("no existing ATX found: %w", err) - } - atx, err := atxs.Get(b.db, atxid) - if err != nil { - return nil, fmt.Errorf("failed to retrieve ATX: %w", err) - } - atxNipost, err := atxs.Nipost(ctx, b.db, atxid) - if err != nil { - return nil, errors.New("no NIPoST found in last ATX") - } - if atx.CommitmentATX == nil { - if commitmentAtx, err := atxs.CommitmentATX(b.db, nodeId); err != nil { - return nil, fmt.Errorf("failed to retrieve commitment ATX: %w", err) - } else { - atx.CommitmentATX = &commitmentAtx - } - } - if atx.VRFNonce == nil { - if nonce, err := atxs.VRFNonce(b.db, nodeId, b.layerClock.CurrentLayer().GetEpoch()); err != nil { - return nil, fmt.Errorf("failed to retrieve VRF nonce: %w", err) - } else { - atx.VRFNonce = &nonce - } - } - - b.log.Info("found POST in an existing ATX", zap.String("atx_id", atxid.Hash32().ShortString())) - return &nipost.Post{ - Nonce: atxNipost.Post.Nonce, - Indices: atxNipost.Post.Indices, - Pow: atxNipost.Post.Pow, - Challenge: atxNipost.PostMetadata.Challenge, - NumUnits: atx.NumUnits, - CommitmentATX: *atx.CommitmentATX, - VRFNonce: *atx.VRFNonce, - }, nil -} - -func (b *Builder) obtainPost(ctx context.Context, nodeID types.NodeID) (*nipost.Post, error) { - b.log.Info("looking for POST for poet certification") - post, err := nipost.GetPost(b.localDB, nodeID) - switch { - case err == nil: - b.log.Info("found POST in local DB") - return post, nil - case errors.Is(err, sql.ErrNotFound): - // no post found - default: - return nil, fmt.Errorf("loading initial post from db: %w", err) - } - - b.log.Info("POST not found in local DB. Trying to obtain POST from an existing ATX") - if post, err := b.obtainPostFromLastAtx(ctx, nodeID); err == nil { - b.log.Info("found POST in an existing ATX") - if err := nipost.AddPost(b.localDB, nodeID, *post); err != nil { - b.log.Error("failed to save post", zap.Error(err)) - } - return post, nil - } +func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { + defer b.log.Info("atx builder stopped") - b.log.Info("POST not found in existing ATXs. Generating the initial POST") for { - post, postInfo, err := b.buildInitialPost(ctx, nodeID) + err := b.buildInitialPost(ctx, sig.NodeID()) if err == nil { - return &nipost.Post{ - Nonce: post.Nonce, - Indices: post.Indices, - Pow: post.Pow, - Challenge: shared.ZeroChallenge, - NumUnits: postInfo.NumUnits, - CommitmentATX: postInfo.CommitmentATX, - VRFNonce: *postInfo.Nonce, - }, nil + break } b.log.Error("failed to generate initial proof:", zap.Error(err)) currentLayer := b.layerClock.CurrentLayer() select { case <-ctx.Done(): - return nil, ctx.Err() + return case <-b.layerClock.AwaitLayer(currentLayer.Add(1)): } } -} - -func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { - defer b.log.Info("atx builder stopped") - - post, err := b.obtainPost(ctx, sig.NodeID()) - if err != nil { - b.log.Error("failed to obtain post for certification", zap.Error(err)) - return - } - b.certifyPost(ctx, sig.NodeID(), post) + b.certifier.CertifyAll(ctx, sig.NodeID(), b.poets) for { err := b.PublishActivationTx(ctx, sig) @@ -749,11 +671,7 @@ func (b *Builder) createAtx( sig *signing.EdSigner, challenge *wire.NIPostChallengeV1, ) (*wire.ActivationTxV1, error) { - b.smeshingMutex.Lock() - certifier := b.certifiers[sig.NodeID()] - b.smeshingMutex.Unlock() - - nipostState, err := b.nipostBuilder.BuildNIPost(ctx, sig, challenge.PublishEpoch, challenge.Hash(), certifier) + nipostState, err := b.nipostBuilder.BuildNIPost(ctx, sig, challenge.PublishEpoch, challenge.Hash()) if err != nil { return nil, fmt.Errorf("build NIPost: %w", err) } diff --git a/activation/activation_multi_test.go b/activation/activation_multi_test.go index 8f80cb4bf1..e9916bd770 100644 --- a/activation/activation_multi_test.go +++ b/activation/activation_multi_test.go @@ -250,11 +250,11 @@ func Test_Builder_Multi_InitialPost(t *testing.T) { }, nil, ) - _, err := tab.obtainPost(context.Background(), sig.NodeID()) + err := tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) // postClient.Proof() should not be called again - _, err = tab.obtainPost(context.Background(), sig.NodeID()) + err = tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) return nil }) @@ -398,7 +398,7 @@ func Test_Builder_Multi_HappyPath(t *testing.T) { } nipostState[sig.NodeID()] = state tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), sig, ref.PublishEpoch, ref.Hash(), gomock.Any()). + BuildNIPost(gomock.Any(), sig, ref.PublishEpoch, ref.Hash()). Return(state, nil) // awaiting atx publication epoch log diff --git a/activation/activation_test.go b/activation/activation_test.go index 89644ec9dc..5564291656 100644 --- a/activation/activation_test.go +++ b/activation/activation_test.go @@ -165,14 +165,8 @@ func publishAtx( NumUnits: DefaultPostSetupOpts().NumUnits, LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func( - _ context.Context, - _ *signing.EdSigner, - _ types.EpochID, - _ types.Hash32, - _ certifierService, - ) (*nipost.NIPostState, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { *currLayer = currLayer.Add(buildNIPostLayerDuration) return newNIPostWithPoet(tb, []byte("66666")), nil }) @@ -412,7 +406,7 @@ func TestBuilder_Loop_WaitsOnStaleChallenge(t *testing.T) { }).AnyTimes() tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(nil, ErrATXChallengeExpired) tab.mnipost.EXPECT().ResetState(sig.NodeID()).Return(nil) @@ -478,14 +472,8 @@ func TestBuilder_PublishActivationTx_FaultyNet(t *testing.T) { NumUnits: DefaultPostSetupOpts().NumUnits, LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func( - _ context.Context, - _ *signing.EdSigner, - _ types.EpochID, - _ types.Hash32, - _ certifierService, - ) (*nipost.NIPostState, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { currLayer = currLayer.Add(layersPerEpoch) return newNIPostWithPoet(t, []byte("66666")), nil }) @@ -572,14 +560,8 @@ func TestBuilder_PublishActivationTx_UsesExistingChallengeOnLatePublish(t *testi NumUnits: DefaultPostSetupOpts().NumUnits, LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func( - _ context.Context, - _ *signing.EdSigner, - _ types.EpochID, - _ types.Hash32, - _ certifierService, - ) (*nipost.NIPostState, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { currLayer = currLayer.Add(1) return newNIPostWithPoet(t, []byte("66666")), nil }) @@ -655,14 +637,8 @@ func TestBuilder_PublishActivationTx_RebuildNIPostWhenTargetEpochPassed(t *testi genesis := time.Now().Add(-time.Duration(currLayer) * layerDuration) return genesis.Add(layerDuration * time.Duration(got)) }).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func( - _ context.Context, - _ *signing.EdSigner, - _ types.EpochID, - _ types.Hash32, - _ certifierService, - ) (*nipost.NIPostState, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { currLayer = currLayer.Add(layersPerEpoch) return newNIPostWithPoet(t, []byte("66666")), nil }) @@ -842,7 +818,7 @@ func TestBuilder_PublishActivationTx_NoPrevATX_PublishFails_InitialPost_preserve return genesis.Add(layerDuration * time.Duration(got)) }).AnyTimes() tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(nil, ErrATXChallengeExpired) tab.mnipost.EXPECT().ResetState(sig.NodeID()).Return(nil) ch := make(chan struct{}) @@ -958,15 +934,9 @@ func TestBuilder_PublishActivationTx_PrevATXWithoutPrevATX(t *testing.T) { }, nil).AnyTimes() tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn( - func( - _ context.Context, - _ *signing.EdSigner, - _ types.EpochID, - _ types.Hash32, - _ certifierService, - ) (*nipost.NIPostState, error) { + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { currentLayer = currentLayer.Add(5) return newNIPostWithPoet(t, poetBytes), nil }) @@ -1055,14 +1025,8 @@ func TestBuilder_PublishActivationTx_TargetsEpochBasedOnPosAtx(t *testing.T) { LabelsPerUnit: DefaultPostConfig().LabelsPerUnit, }, nil).AnyTimes() - tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( - func( - _ context.Context, - _ *signing.EdSigner, - _ types.EpochID, - _ types.Hash32, - _ certifierService, - ) (*nipost.NIPostState, error) { + tab.mnipost.EXPECT().BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { currentLayer = currentLayer.Add(layersPerEpoch) return newNIPostWithPoet(t, poetBytes), nil }) @@ -1144,7 +1108,7 @@ func TestBuilder_PublishActivationTx_FailsWhenNIPostBuilderFails(t *testing.T) { }).AnyTimes() nipostErr := errors.New("NIPost builder error") tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), sig, gomock.Any(), gomock.Any(), gomock.Any()). + BuildNIPost(gomock.Any(), sig, gomock.Any(), gomock.Any()). Return(nil, nipostErr) tab.mValidator.EXPECT().VerifyChain(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()) require.ErrorIs(t, tab.PublishActivationTx(context.Background(), sig), nipostErr) @@ -1225,11 +1189,11 @@ func TestBuilder_RetryPublishActivationTx(t *testing.T) { var last time.Time builderConfirmation := make(chan struct{}) tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Times(expectedTries). DoAndReturn( // nolint:lll - func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32, _ certifierService) (*nipost.NIPostState, error) { + func(_ context.Context, _ *signing.EdSigner, _ types.EpochID, _ types.Hash32) (*nipost.NIPostState, error) { now := time.Now() if now.Sub(last) < retryInterval { require.FailNow(t, "retry interval not respected") @@ -1338,7 +1302,7 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { ) tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), post.CommitmentATX, initialPost, metadata, post.NumUnits) - _, err := tab.obtainPost(context.Background(), sig.NodeID()) + err := tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) posEpoch := postGenesisEpoch + 1 @@ -1370,66 +1334,10 @@ func TestBuilder_InitialProofGeneratedOnce(t *testing.T) { require.Equal(t, vPrevAtx.PublishEpoch+1, atx.PublishEpoch) // postClient.Proof() should not be called again - _, err = tab.obtainPost(context.Background(), sig.NodeID()) + err = tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) } -func TestBuilder_obtainPost(t *testing.T) { - t.Run("no POST or ATX - generate", func(t *testing.T) { - tab := newTestBuilder(t, 1) - sig := maps.Values(tab.signers)[0] - post := types.Post{Indices: make([]byte, 10)} - postInfo := types.PostInfo{ - CommitmentATX: types.RandomATXID(), - Nonce: new(types.VRFPostIndex), - } - tab.mnipost.EXPECT(). - Proof(gomock.Any(), sig.NodeID(), shared.ZeroChallenge). - Return(&post, &postInfo, nil) - tab.mValidator.EXPECT(). - Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) - _, err := tab.obtainPost(context.Background(), sig.NodeID()) - require.NoError(t, err) - }) - t.Run("initial POST available", func(t *testing.T) { - tab := newTestBuilder(t, 1) - sig := maps.Values(tab.signers)[0] - post := types.Post{Indices: make([]byte, 10)} - postInfo := types.PostInfo{ - CommitmentATX: types.RandomATXID(), - Nonce: new(types.VRFPostIndex), - } - tab.mnipost.EXPECT(). - Proof(gomock.Any(), sig.NodeID(), shared.ZeroChallenge). - Return(&post, &postInfo, nil) - tab.mValidator.EXPECT(). - Post(gomock.Any(), sig.NodeID(), postInfo.CommitmentATX, &post, gomock.Any(), gomock.Any()) - - _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) - require.NoError(t, err) - - gotPost, err := tab.obtainPost(context.Background(), sig.NodeID()) - require.NoError(t, err) - require.EqualValues(t, shared.ZeroChallenge, gotPost.Challenge) - }) - t.Run("initial POST unavailable but ATX exists", func(t *testing.T) { - tab := newTestBuilder(t, 1) - sig := maps.Values(tab.signers)[0] - atx := newInitialATXv1(t, tab.goldenATXID) - atx.Sign(sig) - require.NoError(t, atxs.Add(tab.db, toVerifiedAtx(t, atx))) - - post, err := tab.obtainPost(context.Background(), sig.NodeID()) - require.NoError(t, err) - require.Equal(t, atx.CommitmentATXID, &post.CommitmentATX) - require.Equal(t, atx.NumUnits, post.NumUnits) - require.Equal(t, atx.NIPost.PostMetadata.Challenge, post.Challenge) - require.Equal(t, atx.NIPost.Post.Indices, post.Indices) - require.Equal(t, atx.NIPost.Post.Pow, post.Pow) - require.Equal(t, atx.NIPost.Post.Nonce, post.Nonce) - }) -} - func TestBuilder_InitialPostIsPersisted(t *testing.T) { tab := newTestBuilder(t, 1, WithPoetConfig(PoetConfig{PhaseShift: layerDuration * 4})) sig := maps.Values(tab.signers)[0] @@ -1459,11 +1367,11 @@ func TestBuilder_InitialPostIsPersisted(t *testing.T) { LabelsPerUnit: tab.conf.LabelsPerUnit, } tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), commitmentATX, initialPost, metadata, numUnits) - _, err := tab.obtainPost(context.Background(), sig.NodeID()) + err := tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) // postClient.Proof() should not be called again - _, err = tab.obtainPost(context.Background(), sig.NodeID()) + err = tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) } @@ -1494,7 +1402,7 @@ func TestBuilder_InitialPostLogErrorMissingVRFNonce(t *testing.T) { LabelsPerUnit: tab.conf.LabelsPerUnit, } tab.mValidator.EXPECT().Post(gomock.Any(), sig.NodeID(), commitmentATX, initialPost, metadata, numUnits) - _, _, err := tab.buildInitialPost(context.Background(), sig.NodeID()) + err := tab.buildInitialPost(context.Background(), sig.NodeID()) require.ErrorContains(t, err, "nil VRF nonce") observedLogs := tab.observedLogs.FilterLevelExact(zapcore.ErrorLevel) @@ -1517,7 +1425,7 @@ func TestBuilder_InitialPostLogErrorMissingVRFNonce(t *testing.T) { }, nil, ) - _, _, err = tab.buildInitialPost(context.Background(), sig.NodeID()) + err = tab.buildInitialPost(context.Background(), sig.NodeID()) require.NoError(t, err) } @@ -1550,7 +1458,7 @@ func TestWaitPositioningAtx(t *testing.T) { tab.mpostClient.EXPECT().Info(gomock.Any()).Return(&types.PostInfo{}, nil).AnyTimes() tab.mnipost.EXPECT().ResetState(sig.NodeID()).Return(nil) tab.mnipost.EXPECT(). - BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + BuildNIPost(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&nipost.NIPostState{}, nil) closed := make(chan struct{}) close(closed) diff --git a/activation/certifier.go b/activation/certifier.go index 7b10ee15b8..e17b7498e6 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -17,8 +17,8 @@ import ( "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" @@ -96,8 +96,8 @@ func NewCertifier( return c } -func (c *Certifier) Certificate(poet string) *certifier.PoetCert { - cert, err := certifier.Certificate(c.db, c.client.Id(), poet) +func (c *Certifier) Certificate(id types.NodeID, poet string) *certifier.PoetCert { + cert, err := certifier.Certificate(c.db, id, poet) switch { case err == nil: return cert @@ -107,17 +107,17 @@ func (c *Certifier) Certificate(poet string) *certifier.PoetCert { return nil } -func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) { +func (c *Certifier) Recertify(ctx context.Context, id types.NodeID, poet PoetClient) (*certifier.PoetCert, error) { url, pubkey, err := poet.CertifierInfo(ctx) if err != nil { return nil, fmt.Errorf("querying certifier info: %w", err) } - cert, err := c.client.Certify(ctx, url, pubkey) + cert, err := c.client.Certify(ctx, id, url, pubkey) if err != nil { return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), url, err) } - if err := certifier.AddCertificate(c.db, c.client.Id(), *cert, poet.Address()); err != nil { + if err := certifier.AddCertificate(c.db, id, *cert, poet.Address()); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } @@ -128,11 +128,15 @@ func (c *Certifier) Recertify(ctx context.Context, poet PoetClient) (*certifier. // It optimizes the number of certification requests by taking a unique set of // certifiers among the given poets and sending a single request to each of them. // It returns a map of a poet address to a certificate for it. -func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert { +func (c *Certifier) CertifyAll( + ctx context.Context, + id types.NodeID, + poets []PoetClient, +) map[string]*certifier.PoetCert { certs := make(map[string]*certifier.PoetCert) poetsToCertify := []PoetClient{} for _, poet := range poets { - if cert := c.Certificate(poet.Address()); cert != nil { + if cert := c.Certificate(id, poet.Address()); cert != nil { certs[poet.Address()] = cert } else { poetsToCertify = append(poetsToCertify, poet) @@ -196,7 +200,7 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri zap.Strings("poets", svc.poets), ) - cert, err := c.client.Certify(ctx, svc.url, svc.pubkey) + cert, err := c.client.Certify(ctx, id, svc.url, svc.pubkey) if err != nil { c.logger.Warn("failed to certify", zap.Error(err), zap.Stringer("certifier", svc.url)) continue @@ -208,7 +212,7 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri zap.Binary("cert signature", cert.Signature), ) for _, poet := range svc.poets { - if err := certifier.AddCertificate(c.db, c.client.Id(), *cert, poet); err != nil { + if err := certifier.AddCertificate(c.db, id, *cert, poet); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } certs[poet] = cert @@ -218,10 +222,10 @@ func (c *Certifier) CertifyAll(ctx context.Context, poets []PoetClient) map[stri } type CertifierClient struct { - client *retryablehttp.Client - post *nipost.Post - nodeID types.NodeID - logger *zap.Logger + client *retryablehttp.Client + logger *zap.Logger + db sql.Executor + localDb *localsql.Database } type certifierClientOpts func(*CertifierClient) @@ -235,16 +239,16 @@ func WithCertifierClientConfig(cfg CertifierClientConfig) certifierClientOpts { } func NewCertifierClient( + db sql.Executor, + localDb *localsql.Database, logger *zap.Logger, - nodeID types.NodeID, - post *nipost.Post, opts ...certifierClientOpts, ) *CertifierClient { c := &CertifierClient{ - client: retryablehttp.NewClient(), - logger: logger.With(log.ZShortStringer("smesherID", nodeID)), - post: post, - nodeID: nodeID, + client: retryablehttp.NewClient(), + logger: logger, + db: db, + localDb: localDb, } config := DefaultCertifierClientConfig() c.client.RetryMax = config.MaxRetries @@ -262,22 +266,86 @@ func NewCertifierClient( return c } -func (c *CertifierClient) Id() types.NodeID { - return c.nodeID +func (c *CertifierClient) obtainPostFromLastAtx(ctx context.Context, nodeId types.NodeID) (*nipost.Post, error) { + atxid, err := atxs.GetLastIDByNodeID(c.db, nodeId) + if err != nil { + return nil, fmt.Errorf("no existing ATX found: %w", err) + } + atx, err := atxs.Get(c.db, atxid) + if err != nil { + return nil, fmt.Errorf("failed to retrieve ATX: %w", err) + } + atxNipost, err := atxs.Nipost(ctx, c.db, atxid) + if err != nil { + return nil, errors.New("no NIPoST found in last ATX") + } + if atx.CommitmentATX == nil { + if commitmentAtx, err := atxs.CommitmentATX(c.db, nodeId); err != nil { + return nil, fmt.Errorf("failed to retrieve commitment ATX: %w", err) + } else { + atx.CommitmentATX = &commitmentAtx + } + } + + c.logger.Info("found POST in an existing ATX", zap.String("atx_id", atxid.Hash32().ShortString())) + return &nipost.Post{ + Nonce: atxNipost.Post.Nonce, + Indices: atxNipost.Post.Indices, + Pow: atxNipost.Post.Pow, + Challenge: atxNipost.PostMetadata.Challenge, + NumUnits: atx.NumUnits, + CommitmentATX: *atx.CommitmentATX, + // VRF nonce is not needed + }, nil } -func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) { +func (c *CertifierClient) obtainPost(ctx context.Context, id types.NodeID) (*nipost.Post, error) { + c.logger.Info("looking for POST for poet certification") + post, err := nipost.GetPost(c.localDb, id) + switch { + case err == nil: + c.logger.Info("found POST in local DB") + return post, nil + case errors.Is(err, sql.ErrNotFound): + // no post found + default: + return nil, fmt.Errorf("loading initial post from db: %w", err) + } + + c.logger.Info("POST not found in local DB. Trying to obtain POST from an existing ATX") + if post, err := c.obtainPostFromLastAtx(ctx, id); err == nil { + c.logger.Info("found POST in an existing ATX") + if err := nipost.AddPost(c.localDb, id, *post); err != nil { + c.logger.Error("failed to save post", zap.Error(err)) + } + return post, nil + } + + return nil, errors.New("PoST not found") +} + +func (c *CertifierClient) Certify( + ctx context.Context, + id types.NodeID, + url *url.URL, + pubkey []byte, +) (*certifier.PoetCert, error) { + post, err := c.obtainPost(ctx, id) + if err != nil { + return nil, fmt.Errorf("obtaining PoST: %w", err) + } + request := CertifyRequest{ Proof: ProofToCertify{ - Pow: c.post.Pow, - Nonce: c.post.Nonce, - Indices: c.post.Indices, + Pow: post.Pow, + Nonce: post.Nonce, + Indices: post.Indices, }, Metadata: ProofToCertifyMetadata{ - NodeId: c.nodeID[:], - CommitmentAtxId: c.post.CommitmentATX[:], - NumUnits: c.post.NumUnits, - Challenge: c.post.Challenge, + NodeId: id[:], + CommitmentAtxId: post.CommitmentATX[:], + NumUnits: post.NumUnits, + Challenge: post.Challenge, }, } @@ -319,7 +387,7 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by Signature: certResponse.Signature, } - cert, err := shared.VerifyCertificate(opaqueCert, pubkey, c.Id().Bytes()) + cert, err := shared.VerifyCertificate(opaqueCert, pubkey, id.Bytes()) if err != nil { return nil, fmt.Errorf("verifying certificate: %w", err) } @@ -336,3 +404,17 @@ func (c *CertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []by Signature: opaqueCert.Signature, }, nil } + +type disabledCertifier struct{} + +func (d *disabledCertifier) Certificate(types.NodeID, string) *certifier.PoetCert { + return nil +} + +func (d *disabledCertifier) Recertify(context.Context, types.NodeID, PoetClient) (*certifier.PoetCert, error) { + return nil, errors.New("certifier disabled") +} + +func (d *disabledCertifier) CertifyAll(context.Context, types.NodeID, []PoetClient) map[string]*certifier.PoetCert { + return nil +} diff --git a/activation/certifier_test.go b/activation/certifier_test.go index cd3e392ebe..f0bb7d8be5 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -1,4 +1,4 @@ -package activation_test +package activation import ( "context" @@ -9,43 +9,97 @@ import ( "go.uber.org/mock/gomock" "go.uber.org/zap/zaptest" - "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/sql" + "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" + "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) func TestPersistsCerts(t *testing.T) { - client := activation.NewMockcertifierClient(gomock.NewController(t)) - client.EXPECT().Id().AnyTimes().Return(types.RandomNodeID()) + client := NewMockcertifierClient(gomock.NewController(t)) + id := types.RandomNodeID() db := localsql.InMemory() cert := &certifier.PoetCert{Data: []byte("cert"), Signature: []byte("sig")} { - c := activation.NewCertifier(db, zaptest.NewLogger(t), client) + c := NewCertifier(db, zaptest.NewLogger(t), client) - poetMock := activation.NewMockPoetClient(gomock.NewController(t)) + poetMock := NewMockPoetClient(gomock.NewController(t)) poetMock.EXPECT().Address().Return("http://poet") poetMock.EXPECT(). CertifierInfo(gomock.Any()). Return(&url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey"), nil) client.EXPECT(). - Certify(gomock.Any(), &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). + Certify(gomock.Any(), id, &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). Return(cert, nil) - require.Nil(t, c.Certificate("http://poet")) - got, err := c.Recertify(context.Background(), poetMock) + require.Nil(t, c.Certificate(id, "http://poet")) + got, err := c.Recertify(context.Background(), id, poetMock) require.NoError(t, err) require.Equal(t, cert, got) - got = c.Certificate("http://poet") + got = c.Certificate(id, "http://poet") require.Equal(t, cert, got) - require.Nil(t, c.Certificate("http://other-poet")) + require.Nil(t, c.Certificate(id, "http://other-poet")) } { // Create new certifier and check that it loads the certs back. - c := activation.NewCertifier(db, zaptest.NewLogger(t), client) - got := c.Certificate("http://poet") + c := NewCertifier(db, zaptest.NewLogger(t), client) + got := c.Certificate(id, "http://poet") require.Equal(t, cert, got) } } + +func TestObtainingPost(t *testing.T) { + id := types.RandomNodeID() + + t.Run("no POST or ATX", func(t *testing.T) { + db := sql.InMemory() + localDb := localsql.InMemory() + + certifier := NewCertifierClient(db, localDb, zaptest.NewLogger(t)) + _, err := certifier.obtainPost(context.Background(), id) + require.ErrorContains(t, err, "PoST not found") + }) + t.Run("initial POST available", func(t *testing.T) { + db := sql.InMemory() + localDb := localsql.InMemory() + + post := nipost.Post{ + Nonce: 30, + Indices: types.RandomBytes(20), + Pow: 17, + Challenge: types.RandomBytes(32), + NumUnits: 2, + CommitmentATX: types.RandomATXID(), + VRFNonce: 15, + } + err := nipost.AddPost(localDb, id, post) + require.NoError(t, err) + + certifier := NewCertifierClient(db, localDb, zaptest.NewLogger(t)) + got, err := certifier.obtainPost(context.Background(), id) + require.NoError(t, err) + require.Equal(t, post, *got) + }) + t.Run("initial POST unavailable but ATX exists", func(t *testing.T) { + db := sql.InMemory() + localDb := localsql.InMemory() + + atx := newInitialATXv1(t, types.RandomATXID()) + atx.SmesherID = id + require.NoError(t, atxs.Add(db, toVerifiedAtx(t, atx))) + + certifier := NewCertifierClient(db, localDb, zaptest.NewLogger(t)) + got, err := certifier.obtainPost(context.Background(), id) + require.NoError(t, err) + require.Equal(t, atx.NIPost.Post.Indices, got.Indices) + require.Equal(t, atx.NIPost.Post.Nonce, got.Nonce) + require.Equal(t, atx.NIPost.Post.Pow, got.Pow) + require.Equal(t, atx.NIPost.PostMetadata.Challenge, got.Challenge) + require.Equal(t, atx.NumUnits, got.NumUnits) + require.Equal(t, atx.CommitmentATXID, &got.CommitmentATX) + }) +} diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 025f3e7fb8..996e666f43 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -39,6 +39,7 @@ func TestCertification(t *testing.T) { logger := zaptest.NewLogger(t) cfg := activation.DefaultPostConfig() cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) + localDb := localsql.InMemory() syncer := activation.NewMocksyncer(gomock.NewController(t)) synced := make(chan struct{}) @@ -75,7 +76,7 @@ func TestCertification(t *testing.T) { post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) - fullPost := &nipost.Post{ + fullPost := nipost.Post{ Nonce: post.Nonce, Indices: post.Indices, Pow: post.Pow, @@ -84,6 +85,8 @@ func TestCertification(t *testing.T) { CommitmentATX: info.CommitmentATX, VRFNonce: *info.Nonce, } + err = nipost.AddPost(localDb, sig.NodeID(), fullPost) + require.NoError(t, err) t.Run("certify all poets", func(t *testing.T) { poets := []activation.PoetClient{} @@ -123,9 +126,9 @@ func TestCertification(t *testing.T) { require.NoError(t, err) poets = append(poets, client) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) - certifier := activation.NewCertifier(localsql.InMemory(), zaptest.NewLogger(t), certifierClient) - certs := certifier.CertifyAll(context.Background(), poets) + certifierClient := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + certifier := activation.NewCertifier(localDb, zaptest.NewLogger(t), certifierClient) + certs := certifier.CertifyAll(context.Background(), sig.NodeID(), poets) require.Len(t, certs, 3) require.Contains(t, certs, poets[0].Address()) require.Contains(t, certs, poets[1].Address()) @@ -134,8 +137,9 @@ func TestCertification(t *testing.T) { t.Run("certify accepts valid cert", func(t *testing.T) { pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) - _, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + client := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + _, err := client. + Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.NoError(t, err) }) t.Run("certify rejects invalid cert (expired)", func(t *testing.T) { @@ -148,8 +152,9 @@ func TestCertification(t *testing.T) { } pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) - cert, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + client := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + cert, err := client. + Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) }) @@ -159,8 +164,9 @@ func TestCertification(t *testing.T) { } pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), fullPost) - cert, err := client.Certify(context.Background(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + client := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + cert, err := client. + Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) }) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 93b0aff360..41d43f2393 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -136,6 +136,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { cfg := activation.DefaultPostConfig() db := sql.InMemory() cdb := datastore.NewCachedDB(db, log.NewFromLog(logger)) + localDb := localsql.InMemory() syncer := activation.NewMocksyncer(ctrl) syncer.EXPECT().RegisterForATXSynced().AnyTimes().DoAndReturn(func() <-chan struct{} { @@ -210,7 +211,8 @@ func TestNIPostBuilderWithClients(t *testing.T) { post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) require.NoError(t, err) - initialPost := fullPost(post, info, shared.ZeroChallenge) + err = nipost.AddPost(localDb, sig.NodeID(), *fullPost(post, info, shared.ZeroChallenge)) + require.NoError(t, err) client, err := activation.NewHTTPPoetClient( types.PoetServer{Address: poetProver.RestURL().String()}, @@ -219,9 +221,9 @@ func TestNIPostBuilderWithClients(t *testing.T) { ) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), initialPost) - certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) - certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) + certifierClient := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) + certifier := activation.NewCertifier(localDb, logger, certifierClient) + certifier.CertifyAll(context.Background(), sig.NodeID(), []activation.PoetClient{client}) localDB := localsql.InMemory() nb, err := activation.NewNIPostBuilder( @@ -236,7 +238,7 @@ func TestNIPostBuilderWithClients(t *testing.T) { require.NoError(t, err) challenge := types.RandomHash() - nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challenge) require.NoError(t, err) v := activation.NewValidator(nil, poetDb, cfg, opts.Scrypt, verifier) @@ -361,12 +363,13 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { eg.Go(func() error { post, info, err := nb.Proof(context.Background(), sig.NodeID(), shared.ZeroChallenge) require.NoError(t, err) - initialPost := fullPost(post, info, shared.ZeroChallenge) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), initialPost) - certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) - certifier.CertifyAll(context.Background(), []activation.PoetClient{client}) + err = nipost.AddPost(localDB, sig.NodeID(), *fullPost(post, info, shared.ZeroChallenge)) + require.NoError(t, err) + certifierClient := activation.NewCertifierClient(db, localDB, zaptest.NewLogger(t)) + certifier := activation.NewCertifier(localDB, logger, certifierClient) + certifier.CertifyAll(context.Background(), sig.NodeID(), []activation.PoetClient{client}) - nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challenge) require.NoError(t, err) v := activation.NewValidator(nil, poetDb, cfg, opts.Scrypt, verifier) diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index 633878ad04..0052317269 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/spacemeshos/post/initialization" - "github.com/spacemeshos/post/shared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -97,12 +96,6 @@ func TestValidator_Validate(t *testing.T) { return err == nil }, 10*time.Second, 100*time.Millisecond, "timed out waiting for connection") - postClient, err := svc.Client(sig.NodeID()) - require.NoError(t, err) - post, info, err := postClient.Proof(context.Background(), shared.ZeroChallenge) - require.NoError(t, err) - initialPost := fullPost(post, info, shared.ZeroChallenge) - challenge := types.RandomHash() nb, err := activation.NewNIPostBuilder( localsql.InMemory(), @@ -115,9 +108,7 @@ func TestValidator_Validate(t *testing.T) { ) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(zaptest.NewLogger(t), sig.NodeID(), initialPost) - certifier := activation.NewCertifier(localsql.InMemory(), logger, certifierClient) - nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) v := activation.NewValidator(cdb, poetDb, cfg, opts.Scrypt, verifier) diff --git a/activation/interface.go b/activation/interface.go index 7b9f4545c0..83b4c5d639 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -86,7 +86,6 @@ type nipostBuilder interface { sig *signing.EdSigner, publish types.EpochID, challenge types.Hash32, - certifier certifierService, ) (*nipost.NIPostState, error) Proof(ctx context.Context, nodeID types.NodeID, challenge []byte) (*types.Post, *types.PostInfo, error) ResetState(types.NodeID) error @@ -149,25 +148,23 @@ type PoetClient interface { // The implementation can use any method to obtain the certificate, // for example, POST verification. type certifierClient interface { - // The ID for which this client certifies. - Id() types.NodeID // Certify the ID in the given certifier. - Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) + Certify(ctx context.Context, id types.NodeID, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) } // certifierService is used to certify nodeID for registering in the poet. // It holds the certificates and can recertify if needed. type certifierService interface { // Acquire a certificate for the given poet. - Certificate(poet string) *certifier.PoetCert - // Recertify the nodeID and return a certificate confirming that + Certificate(id types.NodeID, poet string) *certifier.PoetCert + // Recertify the ID and return a certificate confirming that // it is verified. The certificate can be later used to submit in poet. - Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) + Recertify(ctx context.Context, id types.NodeID, poet PoetClient) (*certifier.PoetCert, error) - // Certify the nodeID for all given poets. + // Certify the ID for all given poets. // It won't recertify poets that already have a certificate. // It returns a map of a poet address to a certificate for it. - CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert + CertifyAll(ctx context.Context, id types.NodeID, poets []PoetClient) map[string]*certifier.PoetCert } type poetDbAPI interface { diff --git a/activation/mocks.go b/activation/mocks.go index b2d00a3696..d4d5991be1 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -789,18 +789,18 @@ func (m *MocknipostBuilder) EXPECT() *MocknipostBuilderMockRecorder { } // BuildNIPost mocks base method. -func (m *MocknipostBuilder) BuildNIPost(ctx context.Context, sig *signing.EdSigner, publish types.EpochID, challenge types.Hash32, certifier certifierService) (*nipost.NIPostState, error) { +func (m *MocknipostBuilder) BuildNIPost(ctx context.Context, sig *signing.EdSigner, publish types.EpochID, challenge types.Hash32) (*nipost.NIPostState, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BuildNIPost", ctx, sig, publish, challenge, certifier) + ret := m.ctrl.Call(m, "BuildNIPost", ctx, sig, publish, challenge) ret0, _ := ret[0].(*nipost.NIPostState) ret1, _ := ret[1].(error) return ret0, ret1 } // BuildNIPost indicates an expected call of BuildNIPost. -func (mr *MocknipostBuilderMockRecorder) BuildNIPost(ctx, sig, publish, challenge, certifier any) *MocknipostBuilderBuildNIPostCall { +func (mr *MocknipostBuilderMockRecorder) BuildNIPost(ctx, sig, publish, challenge any) *MocknipostBuilderBuildNIPostCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildNIPost", reflect.TypeOf((*MocknipostBuilder)(nil).BuildNIPost), ctx, sig, publish, challenge, certifier) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildNIPost", reflect.TypeOf((*MocknipostBuilder)(nil).BuildNIPost), ctx, sig, publish, challenge) return &MocknipostBuilderBuildNIPostCall{Call: call} } @@ -816,13 +816,13 @@ func (c *MocknipostBuilderBuildNIPostCall) Return(arg0 *nipost.NIPostState, arg1 } // Do rewrite *gomock.Call.Do -func (c *MocknipostBuilderBuildNIPostCall) Do(f func(context.Context, *signing.EdSigner, types.EpochID, types.Hash32, certifierService) (*nipost.NIPostState, error)) *MocknipostBuilderBuildNIPostCall { +func (c *MocknipostBuilderBuildNIPostCall) Do(f func(context.Context, *signing.EdSigner, types.EpochID, types.Hash32) (*nipost.NIPostState, error)) *MocknipostBuilderBuildNIPostCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MocknipostBuilderBuildNIPostCall) DoAndReturn(f func(context.Context, *signing.EdSigner, types.EpochID, types.Hash32, certifierService) (*nipost.NIPostState, error)) *MocknipostBuilderBuildNIPostCall { +func (c *MocknipostBuilderBuildNIPostCall) DoAndReturn(f func(context.Context, *signing.EdSigner, types.EpochID, types.Hash32) (*nipost.NIPostState, error)) *MocknipostBuilderBuildNIPostCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1695,18 +1695,18 @@ func (m *MockcertifierClient) EXPECT() *MockcertifierClientMockRecorder { } // Certify mocks base method. -func (m *MockcertifierClient) Certify(ctx context.Context, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) { +func (m *MockcertifierClient) Certify(ctx context.Context, id types.NodeID, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Certify", ctx, url, pubkey) + ret := m.ctrl.Call(m, "Certify", ctx, id, url, pubkey) ret0, _ := ret[0].(*certifier.PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } // Certify indicates an expected call of Certify. -func (mr *MockcertifierClientMockRecorder) Certify(ctx, url, pubkey any) *MockcertifierClientCertifyCall { +func (mr *MockcertifierClientMockRecorder) Certify(ctx, id, url, pubkey any) *MockcertifierClientCertifyCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certify", reflect.TypeOf((*MockcertifierClient)(nil).Certify), ctx, url, pubkey) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certify", reflect.TypeOf((*MockcertifierClient)(nil).Certify), ctx, id, url, pubkey) return &MockcertifierClientCertifyCall{Call: call} } @@ -1722,51 +1722,13 @@ func (c *MockcertifierClientCertifyCall) Return(arg0 *certifier.PoetCert, arg1 e } // Do rewrite *gomock.Call.Do -func (c *MockcertifierClientCertifyCall) Do(f func(context.Context, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { +func (c *MockcertifierClientCertifyCall) Do(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierClientCertifyCall) DoAndReturn(f func(context.Context, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - -// Id mocks base method. -func (m *MockcertifierClient) Id() types.NodeID { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Id") - ret0, _ := ret[0].(types.NodeID) - return ret0 -} - -// Id indicates an expected call of Id. -func (mr *MockcertifierClientMockRecorder) Id() *MockcertifierClientIdCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockcertifierClient)(nil).Id)) - return &MockcertifierClientIdCall{Call: call} -} - -// MockcertifierClientIdCall wrap *gomock.Call -type MockcertifierClientIdCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockcertifierClientIdCall) Return(arg0 types.NodeID) *MockcertifierClientIdCall { - c.Call = c.Call.Return(arg0) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockcertifierClientIdCall) Do(f func() types.NodeID) *MockcertifierClientIdCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierClientIdCall) DoAndReturn(f func() types.NodeID) *MockcertifierClientIdCall { +func (c *MockcertifierClientCertifyCall) DoAndReturn(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1795,17 +1757,17 @@ func (m *MockcertifierService) EXPECT() *MockcertifierServiceMockRecorder { } // Certificate mocks base method. -func (m *MockcertifierService) Certificate(poet string) *certifier.PoetCert { +func (m *MockcertifierService) Certificate(id types.NodeID, poet string) *certifier.PoetCert { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Certificate", poet) + ret := m.ctrl.Call(m, "Certificate", id, poet) ret0, _ := ret[0].(*certifier.PoetCert) return ret0 } // Certificate indicates an expected call of Certificate. -func (mr *MockcertifierServiceMockRecorder) Certificate(poet any) *MockcertifierServiceCertificateCall { +func (mr *MockcertifierServiceMockRecorder) Certificate(id, poet any) *MockcertifierServiceCertificateCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certificate", reflect.TypeOf((*MockcertifierService)(nil).Certificate), poet) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certificate", reflect.TypeOf((*MockcertifierService)(nil).Certificate), id, poet) return &MockcertifierServiceCertificateCall{Call: call} } @@ -1821,29 +1783,29 @@ func (c *MockcertifierServiceCertificateCall) Return(arg0 *certifier.PoetCert) * } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceCertificateCall) Do(f func(string) *certifier.PoetCert) *MockcertifierServiceCertificateCall { +func (c *MockcertifierServiceCertificateCall) Do(f func(types.NodeID, string) *certifier.PoetCert) *MockcertifierServiceCertificateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceCertificateCall) DoAndReturn(f func(string) *certifier.PoetCert) *MockcertifierServiceCertificateCall { +func (c *MockcertifierServiceCertificateCall) DoAndReturn(f func(types.NodeID, string) *certifier.PoetCert) *MockcertifierServiceCertificateCall { c.Call = c.Call.DoAndReturn(f) return c } // CertifyAll mocks base method. -func (m *MockcertifierService) CertifyAll(ctx context.Context, poets []PoetClient) map[string]*certifier.PoetCert { +func (m *MockcertifierService) CertifyAll(ctx context.Context, id types.NodeID, poets []PoetClient) map[string]*certifier.PoetCert { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CertifyAll", ctx, poets) + ret := m.ctrl.Call(m, "CertifyAll", ctx, id, poets) ret0, _ := ret[0].(map[string]*certifier.PoetCert) return ret0 } // CertifyAll indicates an expected call of CertifyAll. -func (mr *MockcertifierServiceMockRecorder) CertifyAll(ctx, poets any) *MockcertifierServiceCertifyAllCall { +func (mr *MockcertifierServiceMockRecorder) CertifyAll(ctx, id, poets any) *MockcertifierServiceCertifyAllCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifyAll", reflect.TypeOf((*MockcertifierService)(nil).CertifyAll), ctx, poets) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifyAll", reflect.TypeOf((*MockcertifierService)(nil).CertifyAll), ctx, id, poets) return &MockcertifierServiceCertifyAllCall{Call: call} } @@ -1859,30 +1821,30 @@ func (c *MockcertifierServiceCertifyAllCall) Return(arg0 map[string]*certifier.P } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceCertifyAllCall) Do(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertifyAllCall) Do(f func(context.Context, types.NodeID, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { +func (c *MockcertifierServiceCertifyAllCall) DoAndReturn(f func(context.Context, types.NodeID, []PoetClient) map[string]*certifier.PoetCert) *MockcertifierServiceCertifyAllCall { c.Call = c.Call.DoAndReturn(f) return c } // Recertify mocks base method. -func (m *MockcertifierService) Recertify(ctx context.Context, poet PoetClient) (*certifier.PoetCert, error) { +func (m *MockcertifierService) Recertify(ctx context.Context, id types.NodeID, poet PoetClient) (*certifier.PoetCert, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Recertify", ctx, poet) + ret := m.ctrl.Call(m, "Recertify", ctx, id, poet) ret0, _ := ret[0].(*certifier.PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } // Recertify indicates an expected call of Recertify. -func (mr *MockcertifierServiceMockRecorder) Recertify(ctx, poet any) *MockcertifierServiceRecertifyCall { +func (mr *MockcertifierServiceMockRecorder) Recertify(ctx, id, poet any) *MockcertifierServiceRecertifyCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recertify", reflect.TypeOf((*MockcertifierService)(nil).Recertify), ctx, poet) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recertify", reflect.TypeOf((*MockcertifierService)(nil).Recertify), ctx, id, poet) return &MockcertifierServiceRecertifyCall{Call: call} } @@ -1898,13 +1860,13 @@ func (c *MockcertifierServiceRecertifyCall) Return(arg0 *certifier.PoetCert, arg } // Do rewrite *gomock.Call.Do -func (c *MockcertifierServiceRecertifyCall) Do(f func(context.Context, PoetClient) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { +func (c *MockcertifierServiceRecertifyCall) Do(f func(context.Context, types.NodeID, PoetClient) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierServiceRecertifyCall) DoAndReturn(f func(context.Context, PoetClient) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { +func (c *MockcertifierServiceRecertifyCall) DoAndReturn(f func(context.Context, types.NodeID, PoetClient) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index 74454baa5b..5fe7fcdb95 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -53,6 +53,7 @@ type NIPostBuilder struct { poetCfg PoetConfig layerClock layerClock postStates PostStates + certifier certifierService } type NIPostBuilderOption func(*NIPostBuilder) @@ -72,6 +73,12 @@ func NipostbuilderWithPostStates(ps PostStates) NIPostBuilderOption { } } +func WithCertifier(certifier certifierService) NIPostBuilderOption { + return func(nb *NIPostBuilder) { + nb.certifier = certifier + } +} + // NewNIPostBuilder returns a NIPostBuilder. func NewNIPostBuilder( db *localsql.Database, @@ -91,6 +98,7 @@ func NewNIPostBuilder( poetCfg: poetCfg, layerClock: layerClock, postStates: NewPostStates(lg), + certifier: &disabledCertifier{}, } for _, opt := range opts { @@ -168,7 +176,6 @@ func (nb *NIPostBuilder) BuildNIPost( signer *signing.EdSigner, publishEpoch types.EpochID, challenge types.Hash32, - certifier certifierService, ) (*nipost.NIPostState, error) { logger := nb.log.With(log.ZContext(ctx), log.ZShortStringer("smesherID", signer.NodeID())) // Note: to avoid missing next PoET round, we need to publish the ATX before the next PoET round starts. @@ -223,7 +230,7 @@ func (nb *NIPostBuilder) BuildNIPost( submitCtx, cancel := context.WithDeadline(ctx, poetRoundStart) defer cancel() - err := nb.submitPoetChallenges(submitCtx, signer, poetProofDeadline, challenge.Bytes(), certifier) + err := nb.submitPoetChallenges(submitCtx, signer, poetProofDeadline, challenge.Bytes()) if err != nil { return nil, fmt.Errorf("submitting to poets: %w", err) } @@ -338,7 +345,6 @@ func (nb *NIPostBuilder) submitPoetChallenge( client PoetClient, prefix, challenge []byte, signature types.EdSignature, - certifier certifierService, ) error { logger := nb.log.With( log.ZContext(ctx), @@ -348,7 +354,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( // FIXME: remove support for deprecated poet PoW auth := PoetAuth{ - PoetCert: certifier.Certificate(client.Address()), + PoetCert: nb.certifier.Certificate(nodeID, client.Address()), } if auth.PoetCert == nil { logger.Info("missing poet cert - falling back to PoW") @@ -392,7 +398,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( switch { case errors.Is(err, ErrUnauthorized): logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) - auth.PoetCert, err = certifier.Recertify(ctx, client) + auth.PoetCert, err = nb.certifier.Recertify(ctx, nodeID, client) if err != nil { return &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} } @@ -420,7 +426,6 @@ func (nb *NIPostBuilder) submitPoetChallenges( signer *signing.EdSigner, deadline time.Time, challenge []byte, - certifier certifierService, ) error { signature := signer.Sign(signing.POET, challenge) prefix := bytes.Join([][]byte{signer.Prefix(), {byte(signing.POET)}}, nil) @@ -429,7 +434,7 @@ func (nb *NIPostBuilder) submitPoetChallenges( errChan := make(chan error, len(nb.poetProvers)) for _, client := range nb.poetProvers { g.Go(func() error { - errChan <- nb.submitPoetChallenge(ctx, nodeID, deadline, client, prefix, challenge, signature, certifier) + errChan <- nb.submitPoetChallenge(ctx, nodeID, deadline, client, prefix, challenge, signature) return nil }) } diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 6011471028..a7763da970 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -408,10 +408,7 @@ func Test_NIPostBuilder_WithMocks(t *testing.T) { ) require.NoError(t, err) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - - nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) require.NotNil(t, nipost) } @@ -438,6 +435,10 @@ func TestPostSetup(t *testing.T) { }, nil) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT(). + Certificate(sig.NodeID(), poetProvider.Address()). + Return(&certifier.PoetCert{Data: []byte("cert")}) nb, err := NewNIPostBuilder( localsql.InMemory(), @@ -447,13 +448,11 @@ func TestPostSetup(t *testing.T) { PoetConfig{}, mclock, WithPoetClients(poetProvider), + WithCertifier(mCertifier), ) require.NoError(t, err) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(poetProvider.Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - - nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) require.NotNil(t, nipost) } @@ -497,6 +496,9 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { }, nil).Times(1) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil).AnyTimes() + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT(). + Certificate(sig.NodeID(), poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) nb, err := NewNIPostBuilder( db, @@ -506,13 +508,11 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { PoetConfig{}, mclock, WithPoetClients(poetProver), + WithCertifier(mCertifier), ) require.NoError(t, err) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT(). - Certificate(poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) - nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challengeHash, mCertifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challengeHash) require.NoError(t, err) require.NotNil(t, nipost) @@ -528,13 +528,14 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { PoetConfig{}, mclock, WithPoetClients(poetProver), + WithCertifier(mCertifier), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(nil, nil, errors.New("error")) // check that proof ref is not called again - nipost, err = nb.BuildNIPost(context.Background(), sig, 7, challengeHash, mCertifier) + nipost, err = nb.BuildNIPost(context.Background(), sig, 7, challengeHash) require.Nil(t, nipost) require.Error(t, err) @@ -547,6 +548,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { PoetConfig{}, mclock, WithPoetClients(poetProver), + WithCertifier(mCertifier), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return( @@ -558,7 +560,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { ) // check that proof ref is not called again - nipost, err = nb.BuildNIPost(context.Background(), sig, 7, challengeHash, mCertifier) + nipost, err = nb.BuildNIPost(context.Background(), sig, 7, challengeHash) require.NoError(t, err) require.NotNil(t, nipost) } @@ -620,6 +622,10 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().Certificate(sig.NodeID(), poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(sig.NodeID(), poets[1].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + nb, err := NewNIPostBuilder( localsql.InMemory(), poetDb, @@ -628,15 +634,12 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. poetCfg, mclock, WithPoetClients(poets...), + WithCertifier(mCertifier), ) require.NoError(t, err) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - mCertifier.EXPECT().Certificate(poets[1].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - // Act - nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) // Verify @@ -692,6 +695,8 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) + mCertifier := NewMockcertifierService(ctrl) + nb, err := NewNIPostBuilder( localsql.InMemory(), poetDb, @@ -700,16 +705,16 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { PoetConfig{}, mclock, WithPoetClients(poets...), + WithCertifier(mCertifier), ) require.NoError(t, err) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) + mCertifier.EXPECT().Certificate(sig.NodeID(), poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) // No certs - fallback to PoW - mCertifier.EXPECT().Certificate(poets[1].Address()).Return(nil) + mCertifier.EXPECT().Certificate(sig.NodeID(), poets[1].Address()).Return(nil) // Act - nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, mCertifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) // Verify @@ -739,6 +744,8 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { Return(nil, errors.New("test")) poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().Certificate(sig.NodeID(), poetProver.Address()).Return(cert).AnyTimes() nb, err := NewNIPostBuilder( localsql.InMemory(), @@ -748,13 +755,11 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetCfg, mclock, WithPoetClients(poetProver), + WithCertifier(mCertifier), ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) - - nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) + nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.ErrorIs(t, err, ErrPoetServiceUnstable) require.Nil(t, nipst) }) @@ -780,6 +785,8 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { }) poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().Certificate(sig.NodeID(), poetProver.Address()).Return(cert).AnyTimes() nb, err := NewNIPostBuilder( localsql.InMemory(), @@ -789,13 +796,11 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetCfg, mclock, WithPoetClients(poetProver), + WithCertifier(mCertifier), ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) - - nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) + nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.ErrorIs(t, err, ErrPoetServiceUnstable) require.Nil(t, nipst) }) @@ -819,10 +824,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) - - nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) + nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.ErrorIs(t, err, ErrPoetProofNotReceived) require.Nil(t, nipst) }) @@ -849,10 +851,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) - - nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) + nipst, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.ErrorIs(t, err, ErrPoetProofNotReceived) require.Nil(t, nipst) }) @@ -878,6 +877,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { }, nil) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) + mCertifier := NewMockcertifierService(ctrl) nb, err := NewNIPostBuilder( localsql.InMemory(), @@ -887,13 +887,12 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { PoetConfig{PhaseShift: layerDuration}, mclock, WithPoetClients(poetProver), + WithCertifier(mCertifier), ) require.NoError(t, err) - mCertifier := NewMockcertifierService(ctrl) - invalid := &certifier.PoetCert{} - getInvalid := mCertifier.EXPECT().Certificate("http://localhost:9999").Return(invalid) + getInvalid := mCertifier.EXPECT().Certificate(sig.NodeID(), "http://localhost:9999").Return(invalid) submitFailed := poetProver.EXPECT(). Submit( gomock.Any(), @@ -908,7 +907,8 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { Return(nil, ErrUnauthorized) valid := &certifier.PoetCert{Data: []byte("valid")} - recertify := mCertifier.EXPECT().Recertify(gomock.Any(), poetProver).After(submitFailed).Return(valid, nil) + recertify := mCertifier.EXPECT(). + Recertify(gomock.Any(), sig.NodeID(), poetProver).After(submitFailed).Return(valid, nil) submitOK := poetProver.EXPECT(). Submit( gomock.Any(), @@ -928,7 +928,7 @@ func TestNIPostBuilder_RecertifyPoet(t *testing.T) { After(submitOK). Return(&types.PoetProofMessage{}, []types.Hash32{challenge}, nil) - _, err = nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+1, challenge, mCertifier) + _, err = nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+1, challenge) require.NoError(t, err) } @@ -968,8 +968,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - nipost, err := nb.BuildNIPost(context.Background(), sig, currLayer.GetEpoch(), types.RandomHash(), certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, currLayer.GetEpoch(), types.RandomHash()) require.ErrorIs(t, err, ErrATXChallengeExpired) require.ErrorContains(t, err, "poet round has already started") require.Nil(t, nipost) @@ -1012,9 +1011,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { }) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - - nipost, err := nb.BuildNIPost(context.Background(), sig, challenge.PublishEpoch, challengeHash, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, challenge.PublishEpoch, challengeHash) require.ErrorIs(t, err, ErrATXChallengeExpired) require.ErrorContains(t, err, "poet proof for pub epoch") require.Nil(t, nipost) @@ -1061,9 +1058,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { err = nipost.UpdatePoetProofRef(db, sig.NodeID(), [32]byte{1, 2, 3}, &types.MerkleProof{}) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - - nipost, err := nb.BuildNIPost(context.Background(), sig, challenge.PublishEpoch, challengeHash, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, challenge.PublishEpoch, challengeHash) require.ErrorIs(t, err, ErrATXChallengeExpired) require.ErrorContains(t, err, "deadline to publish ATX for pub epoch") require.Nil(t, nipost) @@ -1122,6 +1117,8 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { }, nil) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT().Certificate(sig.NodeID(), poet.Address()).Return(cert).AnyTimes() nb, err := NewNIPostBuilder( localsql.InMemory(), @@ -1131,14 +1128,12 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { poetCfg, mclock, WithPoetClients(poet), + WithCertifier(mCertifier), ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) - // Act - nipost, err := nb.BuildNIPost(buildCtx, sig, postGenesisEpoch+2, challenge, certifier) + nipost, err := nb.BuildNIPost(buildCtx, sig, postGenesisEpoch+2, challenge) require.ErrorIs(t, err, context.Canceled) require.Nil(t, nipost) @@ -1147,8 +1142,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&types.PoetRound{}, nil) - certifier.EXPECT().Certificate("http://localhost:9999").Return(cert) - nipost, err = nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge, certifier) + nipost, err = nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) // Verify @@ -1279,10 +1273,7 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { ) require.NoError(t, err) - certifier := NewMockcertifierService(ctrl) - certifier.EXPECT().CertifyAll(gomock.Any(), gomock.Any()).Return(nil) - - nipost, err := nb.BuildNIPost(context.Background(), sig, tc.epoch, challenge, certifier) + nipost, err := nb.BuildNIPost(context.Background(), sig, tc.epoch, challenge) require.NoError(t, err) require.NotNil(t, nipost) }) @@ -1354,9 +1345,6 @@ func TestNIPostBuilder_Close(t *testing.T) { cancel() challenge := types.RandomHash() - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate("http://localhost:9999").Return(&certifier.PoetCert{}) - - _, err = nb.BuildNIPost(ctx, sig, postGenesisEpoch+2, challenge, mCertifier) + _, err = nb.BuildNIPost(ctx, sig, postGenesisEpoch+2, challenge) require.Error(t, err) } diff --git a/node/node.go b/node/node.go index 0d10f8eb22..5fe70da4cc 100644 --- a/node/node.go +++ b/node/node.go @@ -1025,15 +1025,25 @@ func (app *App) initServices(ctx context.Context) error { poetClients = append(poetClients, client) } + nipostLogger := app.addLogger(NipostBuilderLogger, lg).Zap() + client := activation.NewCertifierClient( + app.db, + app.localDB, + nipostLogger, + activation.WithCertifierClientConfig(app.Config.Certifier.Client), + ) + certifier := activation.NewCertifier(app.localDB, nipostLogger, client) + nipostBuilder, err := activation.NewNIPostBuilder( app.localDB, poetDb, grpcPostService.(*grpcserver.PostService), - app.addLogger(NipostBuilderLogger, lg).Zap(), + nipostLogger, app.Config.POET, app.clock, activation.NipostbuilderWithPostStates(postStates), activation.WithPoetClients(poetClients...), + activation.WithCertifier(certifier), ) if err != nil { return fmt.Errorf("create nipost builder: %w", err) @@ -1058,10 +1068,10 @@ func (app *App) initServices(ctx context.Context) error { // TODO(dshulyak) makes no sense. how we ended using it? activation.WithPoetRetryInterval(app.Config.HARE3.PreroundDelay), activation.WithValidator(app.validator), - activation.WithPoets(poetClients...), - activation.WithCertifierConfig(app.Config.Certifier), activation.WithPostValidityDelay(app.Config.PostValidDelay), activation.WithPostStates(postStates), + activation.WithPoetCertifier(certifier), + activation.WithPoets(poetClients...), ) if len(app.signers) > 1 || app.signers[0].Name() != supervisedIDKeyFileName { // in a remote setup we register eagerly so the atxBuilder can warn about missing connections asap. diff --git a/systest/tests/distributed_post_verification_test.go b/systest/tests/distributed_post_verification_test.go index 7e68dddc18..a1704de374 100644 --- a/systest/tests/distributed_post_verification_test.go +++ b/systest/tests/distributed_post_verification_test.go @@ -159,22 +159,26 @@ func TestPostMalfeasanceProof(t *testing.T) { }, cfg.POET) require.NoError(t, err) + db := sql.InMemory() + localDb := localsql.InMemory() + + certClient := activation.NewCertifierClient(db, localDb, logger.Named("certifier")) + certifier := activation.NewCertifier(localDb, logger, certClient) + nipostBuilder, err := activation.NewNIPostBuilder( - localsql.InMemory(), - activation.NewPoetDb(sql.InMemory(), log.NewNop()), + localDb, + activation.NewPoetDb(db, log.NewNop()), grpcPostService, logger.Named("nipostBuilder"), cfg.POET, clock, activation.WithPoetClients(poetClient), + activation.WithCertifier(certifier), ) require.NoError(t, err) // 2.1. Create initial POST - var ( - initialPost *nipost.Post - challenge *wire.NIPostChallengeV1 - ) + var challenge *wire.NIPostChallengeV1 for { client, err := grpcPostService.Client(signer.NodeID()) if err != nil { @@ -186,6 +190,17 @@ func TestPostMalfeasanceProof(t *testing.T) { post, postInfo, err := client.Proof(ctx, shared.ZeroChallenge) require.NoError(t, err) + err = nipost.AddPost(localDb, signer.NodeID(), nipost.Post{ + Nonce: post.Nonce, + Indices: post.Indices, + Pow: post.Pow, + Challenge: shared.ZeroChallenge, + NumUnits: postInfo.NumUnits, + CommitmentATX: postInfo.CommitmentATX, + VRFNonce: *postInfo.Nonce, + }) + require.NoError(t, err) + challenge = &wire.NIPostChallengeV1{ PrevATXID: types.EmptyATXID, PublishEpoch: 2, @@ -199,12 +214,8 @@ func TestPostMalfeasanceProof(t *testing.T) { } break } - // 2.2 Certify initial POST - certClient := activation.NewCertifierClient(logger.Named("certifier"), signer.NodeID(), initialPost) - certifier := activation.NewCertifier(localsql.InMemory(), logger, certClient) - certifier.CertifyAll(context.Background(), []activation.PoetClient{poetClient}) - nipost, err := nipostBuilder.BuildNIPost(ctx, signer, challenge.PublishEpoch, challenge.Hash(), certifier) + nipost, err := nipostBuilder.BuildNIPost(ctx, signer, challenge.PublishEpoch, challenge.Hash()) require.NoError(t, err) // 2.2 Create ATX with invalid POST From 845c12d1f319559da3244c77197522812cca8285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Tue, 23 Apr 2024 13:25:10 +0200 Subject: [PATCH 47/55] Fix systest --- systest/tests/poets_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systest/tests/poets_test.go b/systest/tests/poets_test.go index 8f19b5fae2..b35e64c045 100644 --- a/systest/tests/poets_test.go +++ b/systest/tests/poets_test.go @@ -261,14 +261,14 @@ func TestRegisteringInPoetWithPowAndCert(t *testing.T) { metricsEndpoint := cluster.MakePoetMetricsEndpoint(tctx.Namespace, 0) powRegs, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_pow_total", valid) require.NoError(t, err) - require.GreaterOrEqual(t, float64(cl.Smeshers()*epoch), powRegs) + require.GreaterOrEqual(t, powRegs, float64(cl.Smeshers()*epoch)) powRegsInvalid, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_pow_total", invalid) require.ErrorIs(t, err, errMetricNotFound, "metric for invalid PoW registrations value: %v", powRegsInvalid) metricsEndpoint = cluster.MakePoetMetricsEndpoint(tctx.Namespace, 1) certRegs, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_cert_total", valid) require.NoError(t, err) - require.GreaterOrEqual(t, float64(cl.Smeshers()*epoch), certRegs) + require.GreaterOrEqual(t, certRegs, float64(cl.Smeshers()*epoch)) certRegsInvalid, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_cert_total", invalid) require.ErrorIs(t, err, errMetricNotFound, "metric for invalid cert registrations value: %v", certRegsInvalid) From c0b306ccf98b6fc1c2fbbe50e284b754e0977c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 26 Apr 2024 09:03:39 +0200 Subject: [PATCH 48/55] Review feedback --- activation/certifier.go | 19 ++++++++++++++++++- sql/atxs/atxs.go | 14 -------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index e17b7498e6..c581004c36 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -16,6 +16,8 @@ import ( "go.uber.org/zap" "golang.org/x/sync/errgroup" + "github.com/spacemeshos/go-spacemesh/activation/wire" + "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" @@ -275,7 +277,7 @@ func (c *CertifierClient) obtainPostFromLastAtx(ctx context.Context, nodeId type if err != nil { return nil, fmt.Errorf("failed to retrieve ATX: %w", err) } - atxNipost, err := atxs.Nipost(ctx, c.db, atxid) + atxNipost, err := loadNipost(ctx, c.db, atxid) if err != nil { return nil, errors.New("no NIPoST found in last ATX") } @@ -418,3 +420,18 @@ func (d *disabledCertifier) Recertify(context.Context, types.NodeID, PoetClient) func (d *disabledCertifier) CertifyAll(context.Context, types.NodeID, []PoetClient) map[string]*certifier.PoetCert { return nil } + +// load NIPoST for the given ATX from the database. +func loadNipost(ctx context.Context, db sql.Executor, id types.ATXID) (*types.NIPost, error) { + var blob sql.Blob + if err := atxs.LoadBlob(ctx, db, id.Bytes(), &blob); err != nil { + return nil, fmt.Errorf("getting blob for %s: %w", id, err) + } + + // TODO: decide about version based on the `version` column + var atx wire.ActivationTxV1 + if err := codec.Decode(blob.Bytes, &atx); err != nil { + return nil, fmt.Errorf("decoding ATX blob: %w", err) + } + return wire.NiPostFromWireV1(atx.NIPost), nil +} diff --git a/sql/atxs/atxs.go b/sql/atxs/atxs.go index c85b144aff..4de0cb246d 100644 --- a/sql/atxs/atxs.go +++ b/sql/atxs/atxs.go @@ -796,17 +796,3 @@ func IterateForGrading( } return nil } - -func Nipost(ctx context.Context, db sql.Executor, id types.ATXID) (*types.NIPost, error) { - var blob sql.Blob - if err := LoadBlob(ctx, db, id.Bytes(), &blob); err != nil { - return nil, fmt.Errorf("getting blob for %s: %w", id, err) - } - - // TODO: decide about version based on the `version` column - var atx wire.ActivationTxV1 - if err := codec.Decode(blob.Bytes, &atx); err != nil { - return nil, fmt.Errorf("decoding ATX blob: %w", err) - } - return wire.NiPostFromWireV1(atx.NIPost), nil -} From f4c358d308540d00911cfefb2d90740611f978e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 22 May 2024 13:26:04 +0200 Subject: [PATCH 49/55] Move certification to the poet client --- activation/activation.go | 17 +- activation/certifier.go | 172 +++---------- activation/certifier_test.go | 33 +-- activation/e2e/activation_test.go | 8 +- activation/e2e/certifier_client_test.go | 66 +---- activation/e2e/nipost_test.go | 26 +- activation/e2e/poet_test.go | 16 +- activation/e2e/validation_test.go | 11 +- activation/interface.go | 33 +-- activation/mocks.go | 226 +++++++++++++++--- activation/nipost.go | 71 +----- activation/nipost_test.go | 195 +++------------ activation/poet.go | 133 +++++++++-- activation/poet_client_test.go | 173 +++++++++++++- activation/post_states_test.go | 1 - node/node.go | 31 ++- sql/localsql/certifier/db.go | 16 +- sql/localsql/certifier/db_test.go | 18 +- sql/migrations/local/0008_next.sql | 10 +- .../distributed_post_verification_test.go | 17 +- 20 files changed, 669 insertions(+), 604 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index c57e2ff22b..6cd219e488 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -31,6 +31,8 @@ import ( "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) +var ErrNotFound = errors.New("not found") + // PoetConfig is the configuration to interact with the poet server. type PoetConfig struct { PhaseShift time.Duration `mapstructure:"phase-shift"` @@ -77,7 +79,6 @@ type Builder struct { localDB *localsql.Database publisher pubsub.Publisher nipostBuilder nipostBuilder - certifier certifierService validator nipostValidator layerClock layerClock syncer syncer @@ -158,12 +159,6 @@ func WithPostStates(ps PostStates) BuilderOption { } } -func WithPoetCertifier(c certifierService) BuilderOption { - return func(b *Builder) { - b.certifier = c - } -} - // NewBuilder returns an atx builder that will start a routine that will attempt to create an atx upon each new layer. func NewBuilder( conf Config, @@ -186,7 +181,6 @@ func NewBuilder( localDB: localDB, publisher: publisher, nipostBuilder: nipostBuilder, - certifier: &disabledCertifier{}, layerClock: layerClock, syncer: syncer, log: log, @@ -406,7 +400,12 @@ func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { case <-b.layerClock.AwaitLayer(currentLayer.Add(1)): } } - b.certifier.CertifyAll(ctx, sig.NodeID(), b.poets) + for _, poet := range b.poets { + _, err := poet.Certify(ctx, sig.NodeID()) + if err != nil { + b.log.Warn("failed to certify poet", zap.Error(err), log.ZShortStringer("smesherID", sig.NodeID())) + } + } for { err := b.PublishActivationTx(ctx, sig) diff --git a/activation/certifier.go b/activation/certifier.go index c581004c36..baef2225dd 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/go-retryablehttp" "github.com/spacemeshos/poet/shared" "go.uber.org/zap" - "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/codec" @@ -22,7 +21,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" - "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" + certifierdb "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) @@ -98,131 +97,40 @@ func NewCertifier( return c } -func (c *Certifier) Certificate(id types.NodeID, poet string) *certifier.PoetCert { - cert, err := certifier.Certificate(c.db, id, poet) +func (c *Certifier) Certificate( + ctx context.Context, + id types.NodeID, + certifier *url.URL, + pubkey []byte, +) (*certifierdb.PoetCert, error) { + // TODO: avoid parallel certifications for a (id, certifier) pair + cert, err := certifierdb.Certificate(c.db, id, pubkey) switch { case err == nil: - return cert + return cert, nil case !errors.Is(err, sql.ErrNotFound): - c.logger.Warn("failed to get certificate", zap.Error(err)) + return nil, fmt.Errorf("getting certificate from DB for: %w", err) } - return nil + return c.Recertify(ctx, id, certifier, pubkey) } -func (c *Certifier) Recertify(ctx context.Context, id types.NodeID, poet PoetClient) (*certifier.PoetCert, error) { - url, pubkey, err := poet.CertifierInfo(ctx) - if err != nil { - return nil, fmt.Errorf("querying certifier info: %w", err) - } - cert, err := c.client.Certify(ctx, id, url, pubkey) +func (c *Certifier) Recertify( + ctx context.Context, + id types.NodeID, + certifier *url.URL, + pubkey []byte, +) (*certifierdb.PoetCert, error) { + cert, err := c.client.Certificate(ctx, id, certifier, pubkey) if err != nil { - return nil, fmt.Errorf("certifying POST for %s at %v: %w", poet.Address(), url, err) + return nil, fmt.Errorf("certifying POST at %v: %w", certifier, err) } - if err := certifier.AddCertificate(c.db, id, *cert, poet.Address()); err != nil { + if err := certifierdb.AddCertificate(c.db, id, *cert, pubkey); err != nil { c.logger.Warn("failed to persist poet cert", zap.Error(err)) } - return cert, nil } -// CertifyAll certifies the nodeID for all poets that require a certificate. -// It optimizes the number of certification requests by taking a unique set of -// certifiers among the given poets and sending a single request to each of them. -// It returns a map of a poet address to a certificate for it. -func (c *Certifier) CertifyAll( - ctx context.Context, - id types.NodeID, - poets []PoetClient, -) map[string]*certifier.PoetCert { - certs := make(map[string]*certifier.PoetCert) - poetsToCertify := []PoetClient{} - for _, poet := range poets { - if cert := c.Certificate(id, poet.Address()); cert != nil { - certs[poet.Address()] = cert - } else { - poetsToCertify = append(poetsToCertify, poet) - } - } - if len(poetsToCertify) == 0 { - return certs - } - - type certInfo struct { - url *url.URL - pubkey []byte - poet string - } - - certifierInfos := make([]*certInfo, len(poetsToCertify)) - var eg errgroup.Group - for i, poet := range poetsToCertify { - eg.Go(func() error { - url, pubkey, err := poet.CertifierInfo(ctx) - if err != nil { - c.logger.Warn("failed to query for certifier info", zap.Error(err), zap.String("poet", poet.Address())) - return nil - } - certifierInfos[i] = &certInfo{ - url: url, - pubkey: pubkey, - poet: poet.Address(), - } - return nil - }) - } - eg.Wait() - - type certService struct { - url *url.URL - pubkey []byte - poets []string - } - certSvcs := make(map[string]*certService) - for _, info := range certifierInfos { - if info == nil { - continue - } - - if svc, ok := certSvcs[string(info.pubkey)]; !ok { - certSvcs[string(info.pubkey)] = &certService{ - url: info.url, - pubkey: info.pubkey, - poets: []string{info.poet}, - } - } else { - svc.poets = append(svc.poets, info.poet) - } - } - - for _, svc := range certSvcs { - c.logger.Info( - "certifying for poets", - zap.Stringer("certifier", svc.url), - zap.Strings("poets", svc.poets), - ) - - cert, err := c.client.Certify(ctx, id, svc.url, svc.pubkey) - if err != nil { - c.logger.Warn("failed to certify", zap.Error(err), zap.Stringer("certifier", svc.url)) - continue - } - c.logger.Info( - "successfully obtained certificate", - zap.Stringer("certifier", svc.url), - zap.Binary("cert data", cert.Data), - zap.Binary("cert signature", cert.Signature), - ) - for _, poet := range svc.poets { - if err := certifier.AddCertificate(c.db, id, *cert, poet); err != nil { - c.logger.Warn("failed to persist poet cert", zap.Error(err)) - } - certs[poet] = cert - } - } - return certs -} - type CertifierClient struct { client *retryablehttp.Client logger *zap.Logger @@ -326,12 +234,12 @@ func (c *CertifierClient) obtainPost(ctx context.Context, id types.NodeID) (*nip return nil, errors.New("PoST not found") } -func (c *CertifierClient) Certify( +func (c *CertifierClient) Certificate( ctx context.Context, id types.NodeID, url *url.URL, pubkey []byte, -) (*certifier.PoetCert, error) { +) (*certifierdb.PoetCert, error) { post, err := c.obtainPost(ctx, id) if err != nil { return nil, fmt.Errorf("obtaining PoST: %w", err) @@ -401,37 +309,29 @@ func (c *CertifierClient) Certify( } } - return &certifier.PoetCert{ + return &certifierdb.PoetCert{ Data: opaqueCert.Data, Signature: opaqueCert.Signature, }, nil } -type disabledCertifier struct{} - -func (d *disabledCertifier) Certificate(types.NodeID, string) *certifier.PoetCert { - return nil -} - -func (d *disabledCertifier) Recertify(context.Context, types.NodeID, PoetClient) (*certifier.PoetCert, error) { - return nil, errors.New("certifier disabled") -} - -func (d *disabledCertifier) CertifyAll(context.Context, types.NodeID, []PoetClient) map[string]*certifier.PoetCert { - return nil -} - // load NIPoST for the given ATX from the database. func loadNipost(ctx context.Context, db sql.Executor, id types.ATXID) (*types.NIPost, error) { var blob sql.Blob - if err := atxs.LoadBlob(ctx, db, id.Bytes(), &blob); err != nil { + version, err := atxs.LoadBlob(ctx, db, id.Bytes(), &blob) + if err != nil { return nil, fmt.Errorf("getting blob for %s: %w", id, err) } - // TODO: decide about version based on the `version` column - var atx wire.ActivationTxV1 - if err := codec.Decode(blob.Bytes, &atx); err != nil { - return nil, fmt.Errorf("decoding ATX blob: %w", err) + switch version { + case types.AtxV1: + var atx wire.ActivationTxV1 + if err := codec.Decode(blob.Bytes, &atx); err != nil { + return nil, fmt.Errorf("decoding ATX blob: %w", err) + } + return wire.NiPostFromWireV1(atx.NIPost), nil + case types.AtxV2: + // TODO: support ATX V2 } - return wire.NiPostFromWireV1(atx.NIPost), nil + panic("unsupported ATX version") } diff --git a/activation/certifier_test.go b/activation/certifier_test.go index f0bb7d8be5..1ec798e05b 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -13,7 +13,7 @@ import ( "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/atxs" "github.com/spacemeshos/go-spacemesh/sql/localsql" - "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" + certdb "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) @@ -21,33 +21,34 @@ func TestPersistsCerts(t *testing.T) { client := NewMockcertifierClient(gomock.NewController(t)) id := types.RandomNodeID() db := localsql.InMemory() - cert := &certifier.PoetCert{Data: []byte("cert"), Signature: []byte("sig")} + cert := &certdb.PoetCert{Data: []byte("cert"), Signature: []byte("sig")} + certifierAddress := &url.URL{Scheme: "http", Host: "certifier.org"} + pubkey := []byte("pubkey") { c := NewCertifier(db, zaptest.NewLogger(t), client) - - poetMock := NewMockPoetClient(gomock.NewController(t)) - poetMock.EXPECT().Address().Return("http://poet") - poetMock.EXPECT(). - CertifierInfo(gomock.Any()). - Return(&url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey"), nil) - client.EXPECT(). - Certify(gomock.Any(), id, &url.URL{Scheme: "http", Host: "certifier.org"}, []byte("pubkey")). + Certificate(gomock.Any(), id, certifierAddress, pubkey). Return(cert, nil) - require.Nil(t, c.Certificate(id, "http://poet")) - got, err := c.Recertify(context.Background(), id, poetMock) + _, err := certdb.Certificate(db, id, pubkey) + require.ErrorIs(t, err, sql.ErrNotFound) + got, err := c.Certificate(context.Background(), id, certifierAddress, pubkey) require.NoError(t, err) require.Equal(t, cert, got) - got = c.Certificate(id, "http://poet") + got, err = c.Certificate(context.Background(), id, certifierAddress, pubkey) + require.NoError(t, err) + require.Equal(t, cert, got) + + got, err = certdb.Certificate(db, id, pubkey) + require.NoError(t, err) require.Equal(t, cert, got) - require.Nil(t, c.Certificate(id, "http://other-poet")) } { // Create new certifier and check that it loads the certs back. c := NewCertifier(db, zaptest.NewLogger(t), client) - got := c.Certificate(id, "http://poet") + got, err := c.Certificate(context.Background(), id, certifierAddress, pubkey) + require.NoError(t, err) require.Equal(t, cert, got) } } @@ -90,7 +91,7 @@ func TestObtainingPost(t *testing.T) { atx := newInitialATXv1(t, types.RandomATXID()) atx.SmesherID = id - require.NoError(t, atxs.Add(db, toVerifiedAtx(t, atx))) + require.NoError(t, atxs.Add(db, toAtx(t, atx))) certifier := NewCertifierClient(db, localDb, zaptest.NewLogger(t)) got, err := certifier.obtainPost(context.Background(), id) diff --git a/activation/e2e/activation_test.go b/activation/e2e/activation_test.go index 37ec809941..5604b3376c 100644 --- a/activation/e2e/activation_test.go +++ b/activation/e2e/activation_test.go @@ -110,25 +110,23 @@ func Test_BuilderWithMultipleClients(t *testing.T) { WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap), ) - client, err := poetProver.Client(poetCfg) + poetDb := activation.NewPoetDb(db, log.NewFromLog(logger).Named("poetDb")) + client, err := poetProver.Client(poetDb, poetCfg, logger) require.NoError(t, err) clock, err := timesync.NewClock( timesync.WithGenesisTime(genesis), timesync.WithLayerDuration(layerDuration), timesync.WithTickInterval(100*time.Millisecond), - timesync.WithLogger(logger), + timesync.WithLogger(zap.NewNop()), ) require.NoError(t, err) t.Cleanup(clock.Close) - poetDb := activation.NewPoetDb(db, log.NewFromLog(logger).Named("poetDb")) - postStates := activation.NewMockPostStates(ctrl) localDB := localsql.InMemory() nb, err := activation.NewNIPostBuilder( localDB, - poetDb, svc, logger.Named("nipostBuilder"), poetCfg, diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 996e666f43..79da084a35 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - "github.com/spacemeshos/poet/registration" poetShared "github.com/spacemeshos/poet/shared" "github.com/spacemeshos/post/initialization" "github.com/spacemeshos/post/shared" @@ -23,9 +22,8 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/api/grpcserver" + "github.com/spacemeshos/go-spacemesh/atxsdata" "github.com/spacemeshos/go-spacemesh/common/types" - "github.com/spacemeshos/go-spacemesh/datastore" - "github.com/spacemeshos/go-spacemesh/log" "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" @@ -38,7 +36,7 @@ func TestCertification(t *testing.T) { logger := zaptest.NewLogger(t) cfg := activation.DefaultPostConfig() - cdb := datastore.NewCachedDB(sql.InMemory(), log.NewFromLog(logger)) + db := sql.InMemory() localDb := localsql.InMemory() syncer := activation.NewMocksyncer(gomock.NewController(t)) @@ -52,7 +50,7 @@ func TestCertification(t *testing.T) { AnyTimes() validator.EXPECT().VerifyChain(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() - mgr, err := activation.NewPostSetupManager(cfg, logger, cdb, types.ATXID{2, 3, 4}, syncer, validator) + mgr, err := activation.NewPostSetupManager(cfg, logger, db, atxsdata.New(), types.ATXID{2, 3, 4}, syncer, validator) require.NoError(t, err) opts := activation.DefaultPostSetupOpts() @@ -88,58 +86,12 @@ func TestCertification(t *testing.T) { err = nipost.AddPost(localDb, sig.NodeID(), fullPost) require.NoError(t, err) - t.Run("certify all poets", func(t *testing.T) { - poets := []activation.PoetClient{} - // Spawn certifier and 2 poets using it - pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) - certifierCfg := ®istration.CertifierConfig{ - URL: "http://" + addr.String(), - PubKey: registration.Base64Enc(pubKey), - } - - for i := 0; i < 2; i++ { - poet := spawnPoet(t, WithCertifier(certifierCfg)) - address := poet.RestURL().String() - cfg := activation.DefaultPoetConfig() - client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, cfg) - require.NoError(t, err) - poets = append(poets, client) - } - - // Spawn another certifier and 1 poet using it - pubKey, addr = spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) - certifierCfg = ®istration.CertifierConfig{ - URL: "http://" + addr.String(), - PubKey: registration.Base64Enc(pubKey), - } - - poet := spawnPoet(t, WithCertifier(certifierCfg)) - address := poet.RestURL().String() - client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) - require.NoError(t, err) - poets = append(poets, client) - - // poet not using certifier - poet = spawnPoet(t) - address = poet.RestURL().String() - client, err = activation.NewHTTPPoetClient(types.PoetServer{Address: address}, activation.DefaultPoetConfig()) - require.NoError(t, err) - poets = append(poets, client) - - certifierClient := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) - certifier := activation.NewCertifier(localDb, zaptest.NewLogger(t), certifierClient) - certs := certifier.CertifyAll(context.Background(), sig.NodeID(), poets) - require.Len(t, certs, 3) - require.Contains(t, certs, poets[0].Address()) - require.Contains(t, certs, poets[1].Address()) - require.Contains(t, certs, poets[2].Address()) - }) t.Run("certify accepts valid cert", func(t *testing.T) { pubKey, addr := spawnTestCertifier(t, cfg, nil, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + client := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) _, err := client. - Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + Certificate(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.NoError(t, err) }) t.Run("certify rejects invalid cert (expired)", func(t *testing.T) { @@ -152,9 +104,9 @@ func TestCertification(t *testing.T) { } pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + client := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) cert, err := client. - Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + Certificate(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) }) @@ -164,9 +116,9 @@ func TestCertification(t *testing.T) { } pubKey, addr := spawnTestCertifier(t, cfg, makeCert, verifying.WithLabelScryptParams(opts.Scrypt)) - client := activation.NewCertifierClient(cdb, localDb, zaptest.NewLogger(t)) + client := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) cert, err := client. - Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + Certificate(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) }) diff --git a/activation/e2e/nipost_test.go b/activation/e2e/nipost_test.go index 76e0c2bf83..2522daf673 100644 --- a/activation/e2e/nipost_test.go +++ b/activation/e2e/nipost_test.go @@ -215,21 +215,17 @@ func TestNIPostBuilderWithClients(t *testing.T) { err = nipost.AddPost(localDb, sig.NodeID(), *fullPost(post, info, shared.ZeroChallenge)) require.NoError(t, err) - client, err := activation.NewHTTPPoetClient( - types.PoetServer{Address: poetProver.RestURL().String()}, + client, err := activation.NewPoetClient( + poetDb, + poetProver.ServerCfg(), poetCfg, - activation.WithLogger(logger), + logger, ) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) - certifier := activation.NewCertifier(localDb, logger, certifierClient) - certifier.CertifyAll(context.Background(), sig.NodeID(), []activation.PoetClient{client}) - localDB := localsql.InMemory() nb, err := activation.NewNIPostBuilder( localDB, - poetDb, svc, logger.Named("nipostBuilder"), poetCfg, @@ -326,10 +322,12 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { WithCycleGap(poetCfg.CycleGap), ) - client, err := activation.NewHTTPPoetClient( - types.PoetServer{Address: poetProver.RestURL().String()}, + poetDb := activation.NewPoetDb(db, log.NewFromLog(logger).Named("poetDb")) + client, err := activation.NewPoetClient( + poetDb, + poetProver.ServerCfg(), poetCfg, - activation.WithLogger(logger), + logger, ) require.NoError(t, err) @@ -344,12 +342,9 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, verifier.Close()) }) - poetDb := activation.NewPoetDb(db, log.NewFromLog(logger).Named("poetDb")) - localDB := localsql.InMemory() nb, err := activation.NewNIPostBuilder( localDB, - poetDb, svc, logger.Named("nipostBuilder"), poetCfg, @@ -365,9 +360,6 @@ func Test_NIPostBuilderWithMultipleClients(t *testing.T) { require.NoError(t, err) err = nipost.AddPost(localDB, sig.NodeID(), *fullPost(post, info, shared.ZeroChallenge)) require.NoError(t, err) - certifierClient := activation.NewCertifierClient(db, localDB, zaptest.NewLogger(t)) - certifier := activation.NewCertifier(localDB, logger, certifierClient) - certifier.CertifyAll(context.Background(), sig.NodeID(), []activation.PoetClient{client}) nipost, err := nb.BuildNIPost(context.Background(), sig, 7, challenge) require.NoError(t, err) diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index d7634fa81e..22adc8e4dc 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -13,6 +13,7 @@ import ( "github.com/spacemeshos/poet/server" "github.com/spacemeshos/poet/shared" "github.com/stretchr/testify/require" + "go.uber.org/zap" "go.uber.org/zap/zaptest" "golang.org/x/sync/errgroup" @@ -36,16 +37,23 @@ func (h *HTTPPoetTestHarness) RestURL() *url.URL { } func (h *HTTPPoetTestHarness) Client( + db *activation.PoetDb, cfg activation.PoetConfig, + logger *zap.Logger, opts ...activation.PoetClientOpts, -) (*activation.HTTPPoetClient, error) { - return activation.NewHTTPPoetClient( - types.PoetServer{Address: h.RestURL().String()}, +) (activation.PoetClient, error) { + return activation.NewPoetClient( + db, + h.ServerCfg(), cfg, - opts..., + logger, ) } +func (h *HTTPPoetTestHarness) ServerCfg() types.PoetServer { + return types.PoetServer{Pubkey: types.NewBase64Enc(h.Service.PublicKey()), Address: h.RestURL().String()} +} + type HTTPPoetOpt func(*server.Config) func WithGenesis(genesis time.Time) HTTPPoetOpt { diff --git a/activation/e2e/validation_test.go b/activation/e2e/validation_test.go index bd27c900d4..94feca41a6 100644 --- a/activation/e2e/validation_test.go +++ b/activation/e2e/validation_test.go @@ -68,7 +68,13 @@ func TestValidator_Validate(t *testing.T) { WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap), ) - client, err := activation.NewHTTPPoetClient(types.PoetServer{Address: poetProver.RestURL().String()}, poetCfg) + poetDb := activation.NewPoetDb(sql.InMemory(), log.NewFromLog(logger).Named("poetDb")) + client, err := activation.NewPoetClient( + poetDb, + types.PoetServer{Address: poetProver.RestURL().String()}, + poetCfg, + logger, + ) require.NoError(t, err) mclock := activation.NewMocklayerClock(ctrl) @@ -82,8 +88,6 @@ func TestValidator_Validate(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, verifier.Close()) }) - poetDb := activation.NewPoetDb(sql.InMemory(), log.NewFromLog(logger).Named("poetDb")) - svc := grpcserver.NewPostService(logger) svc.AllowConnections(true) grpcCfg, cleanup := launchServer(t, svc) @@ -99,7 +103,6 @@ func TestValidator_Validate(t *testing.T) { challenge := types.RandomHash() nb, err := activation.NewNIPostBuilder( localsql.InMemory(), - poetDb, svc, logger.Named("nipostBuilder"), poetCfg, diff --git a/activation/interface.go b/activation/interface.go index 1778ab17bd..33b18f37e2 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -122,9 +122,8 @@ type SmeshingProvider interface { // PoetClient servers as an interface to communicate with a PoET server. // It is used to submit challenges and fetch proofs. type PoetClient interface { - Address() string + Address() *url.URL - // FIXME: remove support for deprecated poet PoW // Submit registers a challenge in the proving service current open round. Submit( ctx context.Context, @@ -134,7 +133,7 @@ type PoetClient interface { nodeID types.NodeID, ) (*types.PoetRound, error) - CertifierInfo(context.Context) (*url.URL, []byte, error) + Certify(ctx context.Context, id types.NodeID) (*certifier.PoetCert, error) // Proof returns the proof for the given round ID. Proof(ctx context.Context, roundID string) (*types.PoetProof, []types.Hash32, error) @@ -144,23 +143,27 @@ type PoetClient interface { // The implementation can use any method to obtain the certificate, // for example, POST verification. type certifierClient interface { - // Certify the ID in the given certifier. - Certify(ctx context.Context, id types.NodeID, url *url.URL, pubkey []byte) (*certifier.PoetCert, error) + // Acquire a certificate for the ID in the given certifier. + // The certificate confirms that the ID is verified and it can be later used to submit in poet. + Certificate( + ctx context.Context, + id types.NodeID, + certifierAddress *url.URL, + pubkey []byte, + ) (*certifier.PoetCert, error) } // certifierService is used to certify nodeID for registering in the poet. // It holds the certificates and can recertify if needed. type certifierService interface { - // Acquire a certificate for the given poet. - Certificate(id types.NodeID, poet string) *certifier.PoetCert - // Recertify the ID and return a certificate confirming that - // it is verified. The certificate can be later used to submit in poet. - Recertify(ctx context.Context, id types.NodeID, poet PoetClient) (*certifier.PoetCert, error) - - // Certify the ID for all given poets. - // It won't recertify poets that already have a certificate. - // It returns a map of a poet address to a certificate for it. - CertifyAll(ctx context.Context, id types.NodeID, poets []PoetClient) map[string]*certifier.PoetCert + certifierClient + + Recertify( + ctx context.Context, + id types.NodeID, + certifierAddress *url.URL, + pubkey []byte, + ) (*certifier.PoetCert, error) } type poetDbAPI interface { diff --git a/activation/mocks.go b/activation/mocks.go index b473d257f1..dd8e80ef35 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1476,10 +1476,10 @@ func (m *MockPoetClient) EXPECT() *MockPoetClientMockRecorder { } // Address mocks base method. -func (m *MockPoetClient) Address() string { +func (m *MockPoetClient) Address() *url.URL { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Address") - ret0, _ := ret[0].(string) + ret0, _ := ret[0].(*url.URL) return ret0 } @@ -1496,65 +1496,64 @@ type MockPoetClientAddressCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockPoetClientAddressCall) Return(arg0 string) *MockPoetClientAddressCall { +func (c *MockPoetClientAddressCall) Return(arg0 *url.URL) *MockPoetClientAddressCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockPoetClientAddressCall) Do(f func() string) *MockPoetClientAddressCall { +func (c *MockPoetClientAddressCall) Do(f func() *url.URL) *MockPoetClientAddressCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockPoetClientAddressCall) DoAndReturn(f func() string) *MockPoetClientAddressCall { +func (c *MockPoetClientAddressCall) DoAndReturn(f func() *url.URL) *MockPoetClientAddressCall { c.Call = c.Call.DoAndReturn(f) return c } -// CertifierInfo mocks base method. -func (m *MockPoetClient) CertifierInfo(arg0 context.Context) (*url.URL, []byte, error) { +// Certify mocks base method. +func (m *MockPoetClient) Certify(ctx context.Context, id types.NodeID) (*certifier.PoetCert, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CertifierInfo", arg0) - ret0, _ := ret[0].(*url.URL) - ret1, _ := ret[1].([]byte) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret := m.ctrl.Call(m, "Certify", ctx, id) + ret0, _ := ret[0].(*certifier.PoetCert) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// CertifierInfo indicates an expected call of CertifierInfo. -func (mr *MockPoetClientMockRecorder) CertifierInfo(arg0 any) *MockPoetClientCertifierInfoCall { +// Certify indicates an expected call of Certify. +func (mr *MockPoetClientMockRecorder) Certify(ctx, id any) *MockPoetClientCertifyCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CertifierInfo", reflect.TypeOf((*MockPoetClient)(nil).CertifierInfo), arg0) - return &MockPoetClientCertifierInfoCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certify", reflect.TypeOf((*MockPoetClient)(nil).Certify), ctx, id) + return &MockPoetClientCertifyCall{Call: call} } -// MockPoetClientCertifierInfoCall wrap *gomock.Call -type MockPoetClientCertifierInfoCall struct { +// MockPoetClientCertifyCall wrap *gomock.Call +type MockPoetClientCertifyCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockPoetClientCertifierInfoCall) Return(arg0 *url.URL, arg1 []byte, arg2 error) *MockPoetClientCertifierInfoCall { - c.Call = c.Call.Return(arg0, arg1, arg2) +func (c *MockPoetClientCertifyCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockPoetClientCertifyCall { + c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockPoetClientCertifierInfoCall) Do(f func(context.Context) (*url.URL, []byte, error)) *MockPoetClientCertifierInfoCall { +func (c *MockPoetClientCertifyCall) Do(f func(context.Context, types.NodeID) (*certifier.PoetCert, error)) *MockPoetClientCertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockPoetClientCertifierInfoCall) DoAndReturn(f func(context.Context) (*url.URL, []byte, error)) *MockPoetClientCertifierInfoCall { +func (c *MockPoetClientCertifyCall) DoAndReturn(f func(context.Context, types.NodeID) (*certifier.PoetCert, error)) *MockPoetClientCertifyCall { c.Call = c.Call.DoAndReturn(f) return c } // Proof mocks base method. -func (m *MockpoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProof, []types.Hash32, error) { +func (m *MockPoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProof, []types.Hash32, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Proof", ctx, roundID) ret0, _ := ret[0].(*types.PoetProof) @@ -1576,25 +1575,25 @@ type MockPoetClientProofCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockpoetClientProofCall) Return(arg0 *types.PoetProof, arg1 []types.Hash32, arg2 error) *MockpoetClientProofCall { +func (c *MockPoetClientProofCall) Return(arg0 *types.PoetProof, arg1 []types.Hash32, arg2 error) *MockPoetClientProofCall { c.Call = c.Call.Return(arg0, arg1, arg2) return c } // Do rewrite *gomock.Call.Do -func (c *MockpoetClientProofCall) Do(f func(context.Context, string) (*types.PoetProof, []types.Hash32, error)) *MockpoetClientProofCall { +func (c *MockPoetClientProofCall) Do(f func(context.Context, string) (*types.PoetProof, []types.Hash32, error)) *MockPoetClientProofCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockpoetClientProofCall) DoAndReturn(f func(context.Context, string) (*types.PoetProof, []types.Hash32, error)) *MockpoetClientProofCall { +func (c *MockPoetClientProofCall) DoAndReturn(f func(context.Context, string) (*types.PoetProof, []types.Hash32, error)) *MockPoetClientProofCall { c.Call = c.Call.DoAndReturn(f) return c } // Submit mocks base method. -func (m *MockpoetClient) Submit(ctx context.Context, deadline time.Time, prefix, challenge []byte, signature types.EdSignature, nodeID types.NodeID) (*types.PoetRound, error) { +func (m *MockPoetClient) Submit(ctx context.Context, deadline time.Time, prefix, challenge []byte, signature types.EdSignature, nodeID types.NodeID) (*types.PoetRound, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Submit", ctx, deadline, prefix, challenge, signature, nodeID) ret0, _ := ret[0].(*types.PoetRound) @@ -1603,10 +1602,10 @@ func (m *MockpoetClient) Submit(ctx context.Context, deadline time.Time, prefix, } // Submit indicates an expected call of Submit. -func (mr *MockpoetClientMockRecorder) Submit(ctx, deadline, prefix, challenge, signature, nodeID any) *MockpoetClientSubmitCall { +func (mr *MockPoetClientMockRecorder) Submit(ctx, deadline, prefix, challenge, signature, nodeID any) *MockPoetClientSubmitCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockpoetClient)(nil).Submit), ctx, deadline, prefix, challenge, signature, nodeID) - return &MockpoetClientSubmitCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockPoetClient)(nil).Submit), ctx, deadline, prefix, challenge, signature, nodeID) + return &MockPoetClientSubmitCall{Call: call} } // MockPoetClientSubmitCall wrap *gomock.Call @@ -1621,13 +1620,176 @@ func (c *MockPoetClientSubmitCall) Return(arg0 *types.PoetRound, arg1 error) *Mo } // Do rewrite *gomock.Call.Do -func (c *MockpoetClientSubmitCall) Do(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID) (*types.PoetRound, error)) *MockpoetClientSubmitCall { +func (c *MockPoetClientSubmitCall) Do(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID) (*types.PoetRound, error)) *MockPoetClientSubmitCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockPoetClientSubmitCall) DoAndReturn(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID) (*types.PoetRound, error)) *MockPoetClientSubmitCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockcertifierClient is a mock of certifierClient interface. +type MockcertifierClient struct { + ctrl *gomock.Controller + recorder *MockcertifierClientMockRecorder +} + +// MockcertifierClientMockRecorder is the mock recorder for MockcertifierClient. +type MockcertifierClientMockRecorder struct { + mock *MockcertifierClient +} + +// NewMockcertifierClient creates a new mock instance. +func NewMockcertifierClient(ctrl *gomock.Controller) *MockcertifierClient { + mock := &MockcertifierClient{ctrl: ctrl} + mock.recorder = &MockcertifierClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockcertifierClient) EXPECT() *MockcertifierClientMockRecorder { + return m.recorder +} + +// Certificate mocks base method. +func (m *MockcertifierClient) Certificate(ctx context.Context, id types.NodeID, certifierAddress *url.URL, pubkey []byte) (*certifier.PoetCert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Certificate", ctx, id, certifierAddress, pubkey) + ret0, _ := ret[0].(*certifier.PoetCert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Certificate indicates an expected call of Certificate. +func (mr *MockcertifierClientMockRecorder) Certificate(ctx, id, certifierAddress, pubkey any) *MockcertifierClientCertificateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certificate", reflect.TypeOf((*MockcertifierClient)(nil).Certificate), ctx, id, certifierAddress, pubkey) + return &MockcertifierClientCertificateCall{Call: call} +} + +// MockcertifierClientCertificateCall wrap *gomock.Call +type MockcertifierClientCertificateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockcertifierClientCertificateCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierClientCertificateCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockcertifierClientCertificateCall) Do(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertificateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockcertifierClientCertificateCall) DoAndReturn(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertificateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MockcertifierService is a mock of certifierService interface. +type MockcertifierService struct { + ctrl *gomock.Controller + recorder *MockcertifierServiceMockRecorder +} + +// MockcertifierServiceMockRecorder is the mock recorder for MockcertifierService. +type MockcertifierServiceMockRecorder struct { + mock *MockcertifierService +} + +// NewMockcertifierService creates a new mock instance. +func NewMockcertifierService(ctrl *gomock.Controller) *MockcertifierService { + mock := &MockcertifierService{ctrl: ctrl} + mock.recorder = &MockcertifierServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockcertifierService) EXPECT() *MockcertifierServiceMockRecorder { + return m.recorder +} + +// Certificate mocks base method. +func (m *MockcertifierService) Certificate(ctx context.Context, id types.NodeID, certifierAddress *url.URL, pubkey []byte) (*certifier.PoetCert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Certificate", ctx, id, certifierAddress, pubkey) + ret0, _ := ret[0].(*certifier.PoetCert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Certificate indicates an expected call of Certificate. +func (mr *MockcertifierServiceMockRecorder) Certificate(ctx, id, certifierAddress, pubkey any) *MockcertifierServiceCertificateCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certificate", reflect.TypeOf((*MockcertifierService)(nil).Certificate), ctx, id, certifierAddress, pubkey) + return &MockcertifierServiceCertificateCall{Call: call} +} + +// MockcertifierServiceCertificateCall wrap *gomock.Call +type MockcertifierServiceCertificateCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockcertifierServiceCertificateCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierServiceCertificateCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockcertifierServiceCertificateCall) Do(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierServiceCertificateCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockcertifierServiceCertificateCall) DoAndReturn(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierServiceCertificateCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Recertify mocks base method. +func (m *MockcertifierService) Recertify(ctx context.Context, id types.NodeID, certifierAddress *url.URL, pubkey []byte) (*certifier.PoetCert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Recertify", ctx, id, certifierAddress, pubkey) + ret0, _ := ret[0].(*certifier.PoetCert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Recertify indicates an expected call of Recertify. +func (mr *MockcertifierServiceMockRecorder) Recertify(ctx, id, certifierAddress, pubkey any) *MockcertifierServiceRecertifyCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recertify", reflect.TypeOf((*MockcertifierService)(nil).Recertify), ctx, id, certifierAddress, pubkey) + return &MockcertifierServiceRecertifyCall{Call: call} +} + +// MockcertifierServiceRecertifyCall wrap *gomock.Call +type MockcertifierServiceRecertifyCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockcertifierServiceRecertifyCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierServiceRecertifyCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockcertifierServiceRecertifyCall) Do(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockpoetClientSubmitCall) DoAndReturn(f func(context.Context, time.Time, []byte, []byte, types.EdSignature, types.NodeID) (*types.PoetRound, error)) *MockpoetClientSubmitCall { +func (c *MockcertifierServiceRecertifyCall) DoAndReturn(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierServiceRecertifyCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index ee8054744a..3eaffb2f92 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -52,7 +52,6 @@ type NIPostBuilder struct { poetCfg PoetConfig layerClock layerClock postStates PostStates - certifier certifierService } type NIPostBuilderOption func(*NIPostBuilder) @@ -61,7 +60,7 @@ func WithPoetClients(clients ...PoetClient) NIPostBuilderOption { return func(nb *NIPostBuilder) { nb.poetProvers = make(map[string]PoetClient, len(clients)) for _, client := range clients { - nb.poetProvers[client.Address()] = client + nb.poetProvers[client.Address().String()] = client } } } @@ -72,16 +71,9 @@ func NipostbuilderWithPostStates(ps PostStates) NIPostBuilderOption { } } -func WithCertifier(certifier certifierService) NIPostBuilderOption { - return func(nb *NIPostBuilder) { - nb.certifier = certifier - } -} - // NewNIPostBuilder returns a NIPostBuilder. func NewNIPostBuilder( db *localsql.Database, - poetDB poetDbAPI, postService postService, lg *zap.Logger, poetCfg PoetConfig, @@ -96,7 +88,6 @@ func NewNIPostBuilder( poetCfg: poetCfg, layerClock: layerClock, postStates: NewPostStates(lg), - certifier: &disabledCertifier{}, } for _, opt := range opts { @@ -346,73 +337,23 @@ func (nb *NIPostBuilder) submitPoetChallenge( ) error { logger := nb.log.With( log.ZContext(ctx), - zap.String("poet", client.Address()), + zap.String("poet", client.Address().String()), log.ZShortStringer("smesherID", nodeID), ) - // FIXME: remove support for deprecated poet PoW - auth := PoetAuth{ - PoetCert: nb.certifier.Certificate(nodeID, client.Address()), - } - if auth.PoetCert == nil { - logger.Info("missing poet cert - falling back to PoW") - powCtx, cancel := withConditionalTimeout(ctx, nb.poetCfg.RequestTimeout) - defer cancel() - powParams, err := client.PowParams(powCtx) - if err != nil { - return &PoetSvcUnstableError{msg: "failed to get PoW params", source: err} - } - logger.Debug("doing pow with params", zap.Any("pow_params", powParams)) - startTime := time.Now() - nonce, err := shared.FindSubmitPowNonce( - ctx, - powParams.Challenge, - challenge, - nodeID.Bytes(), - powParams.Difficulty, - ) - metrics.PoetPowDuration.Set(float64(time.Since(startTime).Nanoseconds())) - if err != nil { - return fmt.Errorf("running poet PoW: %w", err) - } - auth.PoetPoW = &PoetPoW{ - Nonce: nonce, - Params: *powParams, - } - } else { - logger.Info("registering with a certificate", zap.Binary("cert", auth.PoetCert.Data)) - } - logger.Debug("submitting challenge to poet proving service") submitCtx, cancel := withConditionalTimeout(ctx, nb.poetCfg.RequestTimeout) defer cancel() - var ( - round *types.PoetRound - err error - ) - round, err = client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, auth) - switch { - case errors.Is(err, ErrUnauthorized): - logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) - auth.PoetCert, err = nb.certifier.Recertify(ctx, nodeID, client) - if err != nil { - return &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} - } - round, err = client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, auth) - if err != nil { - return &PoetSvcUnstableError{msg: "failed to regenerate poet certificate", source: err} - } - case err != nil: + round, err := client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID) + if err != nil { return &PoetSvcUnstableError{msg: "failed to submit challenge to poet service", source: err} - default: // err == nil } - logger.Info("challenge submitted to poet proving service", zap.String("round", round.ID)) return nipost.AddPoetRegistration(nb.localDB, nodeID, nipost.PoETRegistration{ ChallengeHash: types.Hash32(challenge), - Address: client.Address(), + Address: client.Address().String(), RoundID: round.ID, RoundEnd: round.End, }) @@ -460,7 +401,7 @@ func (nb *NIPostBuilder) submitPoetChallenges( func (nb *NIPostBuilder) getPoetClient(ctx context.Context, address string) PoetClient { for _, client := range nb.poetProvers { - if address == client.Address() { + if address == client.Address().String() { return client } } diff --git a/activation/nipost_test.go b/activation/nipost_test.go index db2dd5380c..6485f162f3 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -3,6 +3,7 @@ package activation import ( "context" "errors" + "net/url" "testing" "time" @@ -21,17 +22,19 @@ import ( "github.com/spacemeshos/go-spacemesh/signing" "github.com/spacemeshos/go-spacemesh/sql" "github.com/spacemeshos/go-spacemesh/sql/localsql" - "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" "github.com/spacemeshos/go-spacemesh/sql/localsql/nipost" ) -func defaultPoetServiceMock(ctrl *gomock.Controller, address string) *MockPoetClient { +func defaultPoetServiceMock(t *testing.T, ctrl *gomock.Controller, address string) *MockPoetClient { + t.Helper() poetClient := NewMockPoetClient(ctrl) poetClient.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). AnyTimes(). Return(&types.PoetRound{}, nil) - poetClient.EXPECT().Address().AnyTimes().Return(address).AnyTimes() + url, err := url.Parse(address) + require.NoError(t, err) + poetClient.EXPECT().Address().AnyTimes().Return(url).AnyTimes() return poetClient } @@ -88,7 +91,6 @@ func newTestNIPostBuilder(tb testing.TB) *testNIPostBuilder { nb, err := NewNIPostBuilder( tnb.mDb, - tnb.mPoetDb, tnb.mPostService, tnb.mLogger, PoetConfig{}, @@ -327,7 +329,6 @@ func Test_NIPostBuilder_ResetState(t *testing.T) { require.NoError(t, err) ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) postService := NewMockpostService(ctrl) mclock := defaultLayerClockMock(ctrl) @@ -335,7 +336,6 @@ func Test_NIPostBuilder_ResetState(t *testing.T) { nb, err := NewNIPostBuilder( db, - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, @@ -376,10 +376,9 @@ func Test_NIPostBuilder_WithMocks(t *testing.T) { challenge := types.RandomHash() ctrl := gomock.NewController(t) - poetProvider := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poetProvider := defaultPoetServiceMock(t, ctrl, "http://localhost:9999") poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProof{}, []types.Hash32{challenge}, nil) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) sig, err := signing.NewEdSigner() @@ -395,7 +394,6 @@ func Test_NIPostBuilder_WithMocks(t *testing.T) { nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, @@ -415,10 +413,9 @@ func TestPostSetup(t *testing.T) { require.NoError(t, err) ctrl := gomock.NewController(t) - poetProvider := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poetProvider := defaultPoetServiceMock(t, ctrl, "http://localhost:9999") poetProvider.EXPECT().Proof(gomock.Any(), "").Return(&types.PoetProof{}, []types.Hash32{challenge}, nil) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) postClient := NewMockPostClient(ctrl) @@ -428,20 +425,14 @@ func TestPostSetup(t *testing.T) { }, nil) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT(). - Certificate(sig.NodeID(), poetProvider.Address()). - Return(&certifier.PoetCert{Data: []byte("cert")}) nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, mclock, WithPoetClients(poetProvider), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -464,7 +455,7 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { challengeHash := wire.NIPostChallengeToWireV1(&challenge).Hash() ctrl := gomock.NewController(t) - poetProver := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poetProver := defaultPoetServiceMock(t, ctrl, "http://localhost:9999") poetProver.EXPECT().Proof(gomock.Any(), "").AnyTimes().Return( &types.PoetProof{}, []types.Hash32{ challengeHash, @@ -473,7 +464,6 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { }, nil, ) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) postClient := NewMockPostClient(ctrl) @@ -486,19 +476,14 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { }, nil).Times(1) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil).AnyTimes() - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT(). - Certificate(sig.NodeID(), poetProver.Address()).AnyTimes().Return(&certifier.PoetCert{Data: []byte("cert")}) nb, err := NewNIPostBuilder( db, - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, mclock, WithPoetClients(poetProver), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -506,19 +491,15 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { require.NoError(t, err) require.NotNil(t, nipost) - poetDb = NewMockpoetDbAPI(ctrl) - // fail post exec require.NoError(t, nb.ResetState(sig.NodeID())) nb, err = NewNIPostBuilder( db, - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, mclock, WithPoetClients(poetProver), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -532,13 +513,11 @@ func TestNIPostBuilder_BuildNIPost(t *testing.T) { // successful post exec nb, err = NewNIPostBuilder( db, - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, mclock, WithPoetClients(poetProver), - WithCertifier(mCertifier), ) require.NoError(t, err) postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return( @@ -562,7 +541,6 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. proof := &types.PoetProof{} ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poets := make([]PoetClient, 0, 2) @@ -580,7 +558,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. <-ctx.Done() return nil, ctx.Err() }) - poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") + poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) poets = append(poets, poet) } { @@ -591,7 +569,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. poet.EXPECT(). Proof(gomock.Any(), gomock.Any()). Return(proof, []types.Hash32{challenge}, nil) - poet.EXPECT().Address().AnyTimes().Return("http://localhost:9998") + poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9998"}) poets = append(poets, poet) } @@ -610,19 +588,13 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(sig.NodeID(), poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - mCertifier.EXPECT().Certificate(sig.NodeID(), poets[1].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, mclock, WithPoetClients(poets...), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -644,21 +616,20 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { proofBetter := &types.PoetProof{LeafCount: 999} ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poets := make([]PoetClient, 0, 2) { poet := NewMockPoetClient(ctrl) poet.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&types.PoetRound{}, nil) - poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999").AnyTimes() + poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) poet.EXPECT().Proof(gomock.Any(), "").Return(proofWorse, []types.Hash32{challenge}, nil) poets = append(poets, poet) } { - poet := defaultPoetServiceMock(ctrl, "http://localhost:9998") + poet := defaultPoetServiceMock(t, ctrl, "http://localhost:9998") poet.EXPECT().Proof(gomock.Any(), "").Return(proofBetter, []types.Hash32{challenge}, nil) poets = append(poets, poet) } @@ -674,24 +645,16 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) - mCertifier := NewMockcertifierService(ctrl) - nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, mclock, WithPoetClients(poets...), - WithCertifier(mCertifier), ) require.NoError(t, err) - mCertifier.EXPECT().Certificate(sig.NodeID(), poets[0].Address()).Return(&certifier.PoetCert{Data: []byte("cert")}) - // No certs - fallback to PoW - mCertifier.EXPECT().Certificate(sig.NodeID(), poets[1].Address()).Return(nil) - // Act nipost, err := nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+2, challenge) require.NoError(t, err) @@ -707,33 +670,27 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetCfg := PoetConfig{ PhaseShift: layerDuration, } - cert := &certifier.PoetCert{Data: []byte("cert")} sig, err := signing.NewEdSigner() require.NoError(t, err) t.Run("Submit fails", func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID()). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID()). Return(nil, errors.New("test")) - poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") + poetProver.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) postService := NewMockpostService(ctrl) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(sig.NodeID(), poetProver.Address()).Return(cert).AnyTimes() nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, mclock, WithPoetClients(poetProver), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -744,12 +701,11 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { t.Run("Submit hangs", func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) poetProver := NewMockPoetClient(ctrl) poetProver.EXPECT(). - Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID()). + Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID()). DoAndReturn(func( ctx context.Context, _ time.Time, @@ -760,20 +716,16 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { <-ctx.Done() return nil, ctx.Err() }) - poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") + poetProver.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) postService := NewMockpostService(ctrl) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(sig.NodeID(), poetProver.Address()).Return(cert).AnyTimes() nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, mclock, WithPoetClients(poetProver), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -784,15 +736,13 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { t.Run("GetProof fails", func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) - poetProver := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poetProver := defaultPoetServiceMock(t, ctrl, "http://localhost:9999") poetProver.EXPECT().Proof(gomock.Any(), "").Return(nil, nil, errors.New("failed")) postService := NewMockpostService(ctrl) nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, @@ -808,9 +758,8 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { t.Run("Challenge is not included in proof members", func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) - poetProver := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poetProver := defaultPoetServiceMock(t, ctrl, "http://localhost:9999") poetProver.EXPECT(). Proof(gomock.Any(), ""). Return(&types.PoetProof{}, []types.Hash32{}, nil) @@ -818,7 +767,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, @@ -833,81 +781,6 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { }) } -func TestNIPostBuilder_RecertifyPoet(t *testing.T) { - t.Parallel() - sig, err := signing.NewEdSigner() - require.NoError(t, err) - - ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) - poetDb.EXPECT().ValidateAndStore(gomock.Any(), gomock.Any()).Return(nil) - mclock := defaultLayerClockMock(ctrl) - poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") - postClient := NewMockPostClient(ctrl) - nonce := types.VRFPostIndex(1) - postClient.EXPECT().Proof(gomock.Any(), gomock.Any()).Return(&types.Post{ - Indices: []byte{1, 2, 3}, - }, &types.PostInfo{ - Nonce: &nonce, - }, nil) - postService := NewMockpostService(ctrl) - postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) - mCertifier := NewMockcertifierService(ctrl) - - nb, err := NewNIPostBuilder( - localsql.InMemory(), - poetDb, - postService, - zaptest.NewLogger(t), - PoetConfig{PhaseShift: layerDuration}, - mclock, - WithPoetClients(poetProver), - WithCertifier(mCertifier), - ) - require.NoError(t, err) - - invalid := &certifier.PoetCert{} - getInvalid := mCertifier.EXPECT().Certificate(sig.NodeID(), "http://localhost:9999").Return(invalid) - submitFailed := poetProver.EXPECT(). - Submit( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - sig.NodeID(), - PoetAuth{PoetCert: invalid}, - ). - After(getInvalid.Call). - Return(nil, ErrUnauthorized) - - valid := &certifier.PoetCert{Data: []byte("valid")} - recertify := mCertifier.EXPECT(). - Recertify(gomock.Any(), sig.NodeID(), poetProver).After(submitFailed).Return(valid, nil) - submitOK := poetProver.EXPECT(). - Submit( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - sig.NodeID(), - PoetAuth{PoetCert: valid}, - ). - After(recertify). - Return(&types.PoetRound{}, nil) - - challenge := types.RandomHash() - poetProver.EXPECT(). - Proof(gomock.Any(), ""). - After(submitOK). - Return(&types.PoetProofMessage{}, []types.Hash32{challenge}, nil) - - _, err = nb.BuildNIPost(context.Background(), sig, postGenesisEpoch+1, challenge) - require.NoError(t, err) -} - // TestNIPoSTBuilder_StaleChallenge checks if // it properly detects that the challenge is stale and the poet round has already started. func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { @@ -922,10 +795,9 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { // Act & Verify t.Run("no requests, poet round started", func(t *testing.T) { ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().Return("http://localhost:9999") + poetProver.EXPECT().Address().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { return genesis.Add(layerDuration * time.Duration(got)) @@ -935,7 +807,6 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, @@ -951,10 +822,9 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { }) t.Run("no response before deadline", func(t *testing.T) { ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().Return("http://localhost:9999") + poetProver.EXPECT().Address().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { return genesis.Add(layerDuration * time.Duration(got)) @@ -964,7 +834,6 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { db := localsql.InMemory() nb, err := NewNIPostBuilder( db, - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, @@ -994,10 +863,9 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { }) t.Run("too late for proof generation", func(t *testing.T) { ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().Return("http://localhost:9999") + poetProver.EXPECT().Address().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { return genesis.Add(layerDuration * time.Duration(got)) @@ -1007,7 +875,6 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { db := localsql.InMemory() nb, err := NewNIPostBuilder( db, - poetDb, postService, zaptest.NewLogger(t), PoetConfig{}, @@ -1053,7 +920,6 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { proof := &types.PoetProof{LeafCount: 777} ctrl := gomock.NewController(t) - poetDb := NewMockpoetDbAPI(ctrl) mclock := defaultLayerClockMock(ctrl) buildCtx, cancel := context.WithCancel(context.Background()) @@ -1072,7 +938,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { return &types.PoetRound{}, context.Canceled }) poet.EXPECT().Proof(gomock.Any(), "").Return(proof, []types.Hash32{challenge}, nil) - poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") + poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) poetCfg := PoetConfig{ PhaseShift: layerDuration * layersPerEpoch / 2, @@ -1085,18 +951,14 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { }, nil) postService := NewMockpostService(ctrl) postService.EXPECT().Client(sig.NodeID()).Return(postClient, nil) - mCertifier := NewMockcertifierService(ctrl) - mCertifier.EXPECT().Certificate(sig.NodeID(), poet.Address()).Return(cert).AnyTimes() nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, mclock, WithPoetClients(poet), - WithCertifier(mCertifier), ) require.NoError(t, err) @@ -1165,8 +1027,8 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { tt := []struct { name string - from string - to string + from *url.URL + to *url.URL epoch types.EpochID }{ // no mitigation needed at the moment @@ -1204,7 +1066,6 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { poets = append(poets, poetProvider) } - poetDb := NewMockpoetDbAPI(ctrl) mclock := NewMocklayerClock(ctrl) genesis := time.Now().Add(-time.Duration(1) * layerDuration) mclock.EXPECT().LayerToTime(gomock.Any()).AnyTimes().DoAndReturn( @@ -1227,7 +1088,6 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { nb, err := NewNIPostBuilder( localsql.InMemory(), - poetDb, postService, zaptest.NewLogger(t), poetCfg, @@ -1288,14 +1148,13 @@ func TestNIPostBuilder_Close(t *testing.T) { sig, err := signing.NewEdSigner() require.NoError(t, err) - poet := defaultPoetServiceMock(ctrl, "http://localhost:9999") + poet := defaultPoetServiceMock(t, ctrl, "http://localhost:9999") poet.EXPECT().Proof(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn( func(ctx context.Context, _ string) (*types.PoetProofMessage, []types.Hash32, error) { return nil, nil, ctx.Err() }) nb, err := NewNIPostBuilder( localsql.InMemory(), - NewMockpoetDbAPI(ctrl), NewMockpostService(ctrl), zaptest.NewLogger(t), PoetConfig{}, diff --git a/activation/poet.go b/activation/poet.go index 5d29476581..6cc6f7800b 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -25,8 +25,9 @@ import ( ) var ( - ErrInvalidRequest = errors.New("invalid request") - ErrUnauthorized = errors.New("unauthorized") + ErrInvalidRequest = errors.New("invalid request") + ErrUnauthorized = errors.New("unauthorized") + errCertificatesNotSupported = errors.New("poet doesn't support certificates") ) type PoetPowParams struct { @@ -141,8 +142,8 @@ func NewHTTPPoetClient(server types.PoetServer, cfg PoetConfig, opts ...PoetClie return poetClient, nil } -func (c *HTTPPoetClient) Address() string { - return c.baseURL.String() +func (c *HTTPPoetClient) Address() *url.URL { + return c.baseURL } func (c *HTTPPoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) { @@ -164,7 +165,7 @@ func (c *HTTPPoetClient) CertifierInfo(ctx context.Context) (*url.URL, []byte, e } certifierInfo := info.GetCertifier() if certifierInfo == nil { - return nil, nil, errors.New("poet doesn't support certifier") + return nil, nil, errCertificatesNotSupported } url, err := url.Parse(certifierInfo.Url) if err != nil { @@ -305,9 +306,9 @@ func (c *HTTPPoetClient) req(ctx context.Context, method, path string, reqBody, return nil } -// PoetClient is a higher-level interface to communicate with a PoET service. +// poetClient is a higher-level interface to communicate with a PoET service. // It wraps the HTTP client, adding additional functionality. -type PoetClient struct { +type poetClient struct { db poetDbAPI id []byte logger *zap.Logger @@ -318,14 +319,30 @@ type PoetClient struct { gettingProof sync.Mutex // cached members of the last queried proof proofMembers map[string][]types.Hash32 + + certifier certifierService } -func newPoetClient(db poetDbAPI, server types.PoetServer, cfg PoetConfig, logger *zap.Logger) (*PoetClient, error) { +type PoetClientOpt func(*poetClient) + +func WithCertifier(certifier certifierService) PoetClientOpt { + return func(c *poetClient) { + c.certifier = certifier + } +} + +func NewPoetClient( + db poetDbAPI, + server types.PoetServer, + cfg PoetConfig, + logger *zap.Logger, + opts ...PoetClientOpt, +) (*poetClient, error) { client, err := NewHTTPPoetClient(server, cfg, WithLogger(logger)) if err != nil { return nil, fmt.Errorf("creating HTTP poet client: %w", err) } - poetClient := &PoetClient{ + poetClient := &poetClient{ db: db, id: server.Pubkey.Bytes(), logger: logger, @@ -334,25 +351,35 @@ func newPoetClient(db poetDbAPI, server types.PoetServer, cfg PoetConfig, logger proofMembers: make(map[string][]types.Hash32, 1), } + for _, opt := range opts { + opt(poetClient) + } + return poetClient, nil } -func (c *PoetClient) Address() string { +func (c *poetClient) Address() *url.URL { return c.client.Address() } -func (c *PoetClient) Submit( +func (c *poetClient) authorize( ctx context.Context, - deadline time.Time, - prefix, challenge []byte, - signature types.EdSignature, nodeID types.NodeID, -) (*types.PoetRound, error) { - logger := c.logger.With( - log.ZContext(ctx), - zap.String("poet", c.Address()), - log.ZShortStringer("smesherID", nodeID), - ) + challenge []byte, + logger *zap.Logger, +) (*PoetAuth, error) { + logger.Debug("certifying node") + cert, err := c.Certify(ctx, nodeID) + switch { + case err == nil: + return &PoetAuth{PoetCert: cert}, nil + case errors.Is(err, errCertificatesNotSupported): + logger.Debug("poet doesn't support certificates") + default: + logger.Warn("failed to certify", zap.Error(err)) + } + // Fallback to PoW + // TODO: remove this fallback once we migrate to certificates fully. logger.Debug("querying for poet pow parameters") powCtx, cancel := withConditionalTimeout(ctx, c.requestTimeout) @@ -376,17 +403,51 @@ func (c *PoetClient) Submit( return nil, fmt.Errorf("running poet PoW: %w", err) } + return &PoetAuth{PoetPoW: &PoetPoW{ + Nonce: nonce, + Params: *powParams, + }}, nil +} + +func (c *poetClient) Submit( + ctx context.Context, + deadline time.Time, + prefix, challenge []byte, + signature types.EdSignature, + nodeID types.NodeID, +) (*types.PoetRound, error) { + logger := c.logger.With( + log.ZContext(ctx), + zap.String("poet", c.Address().String()), + log.ZShortStringer("smesherID", nodeID), + ) + + // Try obtain a certificate + auth, err := c.authorize(ctx, nodeID, challenge, logger) + if err != nil { + return nil, fmt.Errorf("authorizing: %w", err) + } + logger.Debug("submitting challenge to poet proving service") submitCtx, cancel := withConditionalTimeout(ctx, c.requestTimeout) defer cancel() - return c.client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, PoetPoW{ - Nonce: nonce, - Params: *powParams, - }) + round, err := c.client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, *auth) + switch { + case err == nil: + return round, nil + case errors.Is(err, ErrUnauthorized): + logger.Warn("failed to submit challenge as unathorized - recertifying", zap.Error(err)) + auth.PoetCert, err = c.recertify(ctx, nodeID) + if err != nil { + return nil, fmt.Errorf("recertifying: %w", err) + } + return c.client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, *auth) + } + return nil, fmt.Errorf("submitting challenge: %w", err) } -func (c *PoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProof, []types.Hash32, error) { +func (c *poetClient) Proof(ctx context.Context, roundID string) (*types.PoetProof, []types.Hash32, error) { getProofsCtx, cancel := withConditionalTimeout(ctx, c.requestTimeout) defer cancel() @@ -416,3 +477,25 @@ func (c *PoetClient) Proof(ctx context.Context, roundID string) (*types.PoetProo return &proof.PoetProof, members, nil } + +func (c *poetClient) Certify(ctx context.Context, id types.NodeID) (*certifier.PoetCert, error) { + if c.certifier == nil { + return nil, errors.New("certifier not configured") + } + url, pubkey, err := c.client.CertifierInfo(ctx) + if err != nil { + return nil, fmt.Errorf("getting certifier info: %w", err) + } + return c.certifier.Certificate(ctx, id, url, pubkey) +} + +func (c *poetClient) recertify(ctx context.Context, id types.NodeID) (*certifier.PoetCert, error) { + if c.certifier == nil { + return nil, errors.New("certifier not configured") + } + url, pubkey, err := c.client.CertifierInfo(ctx) + if err != nil { + return nil, fmt.Errorf("getting certifier info: %w", err) + } + return c.certifier.Recertify(ctx, id, url, pubkey) +} diff --git a/activation/poet_client_test.go b/activation/poet_client_test.go index 0af8fb04e1..3dac5f3758 100644 --- a/activation/poet_client_test.go +++ b/activation/poet_client_test.go @@ -2,8 +2,10 @@ package activation import ( "context" + "io" "net/http" "net/http/httptest" + "net/url" "sync/atomic" "testing" "time" @@ -17,6 +19,8 @@ import ( "google.golang.org/protobuf/encoding/protojson" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/signing" + "github.com/spacemeshos/go-spacemesh/sql/localsql/certifier" ) func Test_HTTPPoetClient_ParsesURL(t *testing.T) { @@ -93,7 +97,7 @@ func Test_HTTPPoetClient_Address(t *testing.T) { }, withCustomHttpClient(ts.Client())) require.NoError(t, err) - require.Equal(t, ts.URL, client.Address()) + require.Equal(t, ts.URL, client.Address().String()) } func Test_HTTPPoetClient_Address_Mainnet(t *testing.T) { @@ -113,7 +117,7 @@ func Test_HTTPPoetClient_Address_Mainnet(t *testing.T) { CycleGap: poetCfg.CycleGap, }) require.NoError(t, err) - require.Equal(t, url, client.Address()) + require.Equal(t, url, client.Address().String()) }) } } @@ -169,7 +173,7 @@ func TestPoetClient_CachesProof(t *testing.T) { db.EXPECT().ValidateAndStore(ctx, gomock.Any()) db.EXPECT().ProofForRound(server.Pubkey.Bytes(), "1").Times(19) - poet, err := newPoetClient(db, server, DefaultPoetConfig(), zaptest.NewLogger(t)) + poet, err := NewPoetClient(db, server, DefaultPoetConfig(), zaptest.NewLogger(t)) require.NoError(t, err) poet.client.client.HTTPClient = ts.Client() @@ -206,7 +210,7 @@ func TestPoetClient_QueryProofTimeout(t *testing.T) { cfg := PoetConfig{ RequestTimeout: time.Millisecond * 100, } - poet, err := newPoetClient(nil, server, cfg, zaptest.NewLogger(t)) + poet, err := NewPoetClient(nil, server, cfg, zaptest.NewLogger(t)) require.NoError(t, err) poet.client.client.HTTPClient = ts.Client() @@ -222,3 +226,164 @@ func TestPoetClient_QueryProofTimeout(t *testing.T) { eg.Wait() require.WithinDuration(t, start.Add(cfg.RequestTimeout), time.Now(), time.Millisecond*300) } + +func TestPoetClient_Certify(t *testing.T) { + t.Parallel() + + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + certifierAddress := &url.URL{Scheme: "http", Host: "certifier"} + certifierPubKey := []byte("certifier-pubkey") + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/info": + resp, err := protojson.Marshal(&rpcapi.InfoResponse{ + ServicePubkey: []byte("pubkey"), + Certifier: &rpcapi.InfoResponse_Cerifier{ + Url: certifierAddress.String(), + Pubkey: certifierPubKey, + }, + }) + require.NoError(t, err) + w.Write(resp) + } + })) + defer ts.Close() + + server := types.PoetServer{ + Address: ts.URL, + Pubkey: types.NewBase64Enc([]byte("pubkey")), + } + cfg := PoetConfig{RequestTimeout: time.Millisecond * 100} + cert := certifier.PoetCert{Data: []byte("abc")} + ctrl := gomock.NewController(t) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT(). + Certificate(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey). + Return(&cert, nil) + + poet, err := NewPoetClient(nil, server, cfg, zaptest.NewLogger(t), WithCertifier(mCertifier)) + require.NoError(t, err) + poet.client.client.HTTPClient = ts.Client() + + got, err := poet.Certify(context.Background(), sig.NodeID()) + require.NoError(t, err) + require.Equal(t, cert, *got) +} + +func TestPoetClient_ObtainsCertOnSubmit(t *testing.T) { + t.Parallel() + + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + certifierAddress := &url.URL{Scheme: "http", Host: "certifier"} + certifierPubKey := []byte("certifier-pubkey") + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/info": + resp, err := protojson.Marshal(&rpcapi.InfoResponse{ + ServicePubkey: []byte("pubkey"), + Certifier: &rpcapi.InfoResponse_Cerifier{ + Url: certifierAddress.String(), + Pubkey: certifierPubKey, + }, + }) + require.NoError(t, err) + w.Write(resp) + case "/v1/submit": + resp, err := protojson.Marshal(&rpcapi.SubmitResponse{}) + require.NoError(t, err) + w.Write(resp) + } + })) + + defer ts.Close() + + server := types.PoetServer{ + Address: ts.URL, + Pubkey: types.NewBase64Enc([]byte("pubkey")), + } + cfg := PoetConfig{RequestTimeout: time.Millisecond * 100} + cert := certifier.PoetCert{Data: []byte("abc")} + ctrl := gomock.NewController(t) + mCertifier := NewMockcertifierService(ctrl) + mCertifier.EXPECT(). + Certificate(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey). + Return(&cert, nil) + + poet, err := NewPoetClient(nil, server, cfg, zaptest.NewLogger(t), WithCertifier(mCertifier)) + require.NoError(t, err) + poet.client.client.HTTPClient = ts.Client() + + _, err = poet.Submit(context.Background(), time.Time{}, nil, nil, types.RandomEdSignature(), sig.NodeID()) + require.NoError(t, err) +} + +func TestPoetClient_RecertifiesOnAuthFailure(t *testing.T) { + t.Parallel() + + sig, err := signing.NewEdSigner() + require.NoError(t, err) + + certifierAddress := &url.URL{Scheme: "http", Host: "certifier"} + certifierPubKey := []byte("certifier-pubkey") + submitCount := 0 + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v1/info": + resp, err := protojson.Marshal(&rpcapi.InfoResponse{ + ServicePubkey: []byte("pubkey"), + Certifier: &rpcapi.InfoResponse_Cerifier{ + Url: certifierAddress.String(), + Pubkey: certifierPubKey, + }, + }) + require.NoError(t, err) + w.Write(resp) + case "/v1/submit": + req := rpcapi.SubmitRequest{} + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + require.NoError(t, protojson.Unmarshal(body, &req)) + if submitCount == 0 { + require.EqualValues(t, "first", req.Certificate.Data) + w.WriteHeader(http.StatusUnauthorized) + } else { + require.EqualValues(t, "second", req.Certificate.Data) + resp, err := protojson.Marshal(&rpcapi.SubmitResponse{}) + require.NoError(t, err) + w.Write(resp) + } + submitCount++ + } + })) + + defer ts.Close() + + server := types.PoetServer{ + Address: ts.URL, + Pubkey: types.NewBase64Enc([]byte("pubkey")), + } + cfg := PoetConfig{RequestTimeout: time.Millisecond * 100} + + ctrl := gomock.NewController(t) + mCertifier := NewMockcertifierService(ctrl) + gomock.InOrder( + mCertifier.EXPECT(). + Certificate(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey). + Return(&certifier.PoetCert{Data: []byte("first")}, nil), + mCertifier.EXPECT(). + Recertify(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey). + Return(&certifier.PoetCert{Data: []byte("second")}, nil), + ) + + poet, err := NewPoetClient(nil, server, cfg, zaptest.NewLogger(t), WithCertifier(mCertifier)) + require.NoError(t, err) + poet.client.client.HTTPClient = ts.Client() + + _, err = poet.Submit(context.Background(), time.Time{}, nil, nil, types.RandomEdSignature(), sig.NodeID()) + require.NoError(t, err) + require.Equal(t, 2, submitCount) +} diff --git a/activation/post_states_test.go b/activation/post_states_test.go index 468daa0e64..b737f0fa43 100644 --- a/activation/post_states_test.go +++ b/activation/post_states_test.go @@ -48,7 +48,6 @@ func TestPostState_OnProof(t *testing.T) { mPostService := NewMockpostService(ctrl) mPostClient := NewMockPostClient(ctrl) nb, err := NewNIPostBuilder( - nil, nil, mPostService, zaptest.NewLogger(t), diff --git a/node/node.go b/node/node.go index 0a3033ef4d..4eb86e4b43 100644 --- a/node/node.go +++ b/node/node.go @@ -1001,19 +1001,6 @@ func (app *App) initServices(ctx context.Context) error { return fmt.Errorf("init post grpc service: %w", err) } - poetClients := make([]activation.PoetClient, 0, len(app.Config.PoetServers)) - for _, poet := range app.Config.PoetServers { - client, err := activation.NewHTTPPoetClient( - poet, - app.Config.POET, - activation.WithLogger(lg.Zap().Named("poet")), - ) - if err != nil { - app.log.Panic("failed to create poet client: %v", err) - } - poetClients = append(poetClients, client) - } - nipostLogger := app.addLogger(NipostBuilderLogger, lg).Zap() client := activation.NewCertifierClient( app.db, @@ -1023,16 +1010,29 @@ func (app *App) initServices(ctx context.Context) error { ) certifier := activation.NewCertifier(app.localDB, nipostLogger, client) + poetClients := make([]activation.PoetClient, 0, len(app.Config.PoetServers)) + for _, server := range app.Config.PoetServers { + client, err := activation.NewPoetClient( + poetDb, + server, + app.Config.POET, + lg.Zap().Named("poet"), + activation.WithCertifier(certifier), + ) + if err != nil { + app.log.Panic("failed to create poet client: %v", err) + } + poetClients = append(poetClients, client) + } + nipostBuilder, err := activation.NewNIPostBuilder( app.localDB, - poetDb, grpcPostService.(*grpcserver.PostService), nipostLogger, app.Config.POET, app.clock, activation.NipostbuilderWithPostStates(postStates), activation.WithPoetClients(poetClients...), - activation.WithCertifier(certifier), ) if err != nil { return fmt.Errorf("create nipost builder: %w", err) @@ -1060,7 +1060,6 @@ func (app *App) initServices(ctx context.Context) error { activation.WithValidator(app.validator), activation.WithPostValidityDelay(app.Config.PostValidDelay), activation.WithPostStates(postStates), - activation.WithPoetCertifier(certifier), activation.WithPoets(poetClients...), ) if len(app.signers) > 1 || app.signers[0].Name() != supervisedIDKeyFileName { diff --git a/sql/localsql/certifier/db.go b/sql/localsql/certifier/db.go index 7b732587fa..cf74c50c99 100644 --- a/sql/localsql/certifier/db.go +++ b/sql/localsql/certifier/db.go @@ -12,26 +12,26 @@ type PoetCert struct { Signature []byte } -func AddCertificate(db sql.Executor, nodeID types.NodeID, cert PoetCert, poetUrl string) error { +func AddCertificate(db sql.Executor, nodeID types.NodeID, cert PoetCert, cerifierID []byte) error { enc := func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) - stmt.BindBytes(2, []byte(poetUrl)) + stmt.BindBytes(2, cerifierID) stmt.BindBytes(3, cert.Data) stmt.BindBytes(4, cert.Signature) } if _, err := db.Exec(` - REPLACE INTO poet_certificates (node_id, poet_url, certificate, signature) + REPLACE INTO poet_certificates (node_id, certifier_id, certificate, signature) VALUES (?1, ?2, ?3, ?4);`, enc, nil, ); err != nil { - return fmt.Errorf("storing poet certificate for (%s; %s): %w", nodeID.ShortString(), poetUrl, err) + return fmt.Errorf("storing poet certificate for (%s; %x): %w", nodeID.ShortString(), cerifierID, err) } return nil } -func Certificate(db sql.Executor, nodeID types.NodeID, poetUrl string) (*PoetCert, error) { +func Certificate(db sql.Executor, nodeID types.NodeID, certifierID []byte) (*PoetCert, error) { enc := func(stmt *sql.Statement) { stmt.BindBytes(1, nodeID.Bytes()) - stmt.BindBytes(2, []byte(poetUrl)) + stmt.BindBytes(2, certifierID) } var cert PoetCert dec := func(stmt *sql.Statement) bool { @@ -43,11 +43,11 @@ func Certificate(db sql.Executor, nodeID types.NodeID, poetUrl string) (*PoetCer } rows, err := db.Exec(` select certificate, signature - from poet_certificates where node_id = ?1 and poet_url = ?2 limit 1;`, enc, dec, + from poet_certificates where node_id = ?1 and certifier_id = ?2 limit 1;`, enc, dec, ) switch { case err != nil: - return nil, fmt.Errorf("getting poet certificate for (%s; %s): %w", nodeID.ShortString(), poetUrl, err) + return nil, fmt.Errorf("getting poet certificate for (%s; %s): %w", nodeID.ShortString(), certifierID, err) case rows == 0: return nil, sql.ErrNotFound } diff --git a/sql/localsql/certifier/db_test.go b/sql/localsql/certifier/db_test.go index a8d0fc773e..d0611db432 100644 --- a/sql/localsql/certifier/db_test.go +++ b/sql/localsql/certifier/db_test.go @@ -16,18 +16,18 @@ func TestAddingCertificates(t *testing.T) { expCert := certifier.PoetCert{Data: []byte("data"), Signature: []byte("sig")} - require.NoError(t, certifier.AddCertificate(db, nodeId, expCert, "poet-0")) - cert, err := certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert, []byte("certifier-0"))) + cert, err := certifier.Certificate(db, nodeId, []byte("certifier-0")) require.NoError(t, err) require.Equal(t, &expCert, cert) expCert2 := certifier.PoetCert{Data: []byte("data2"), Signature: []byte("sig2")} - require.NoError(t, certifier.AddCertificate(db, nodeId, expCert2, "poet-1")) + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert2, []byte("certifier-1"))) - cert, err = certifier.Certificate(db, nodeId, "poet-1") + cert, err = certifier.Certificate(db, nodeId, []byte("certifier-1")) require.NoError(t, err) require.Equal(t, &expCert2, cert) - cert, err = certifier.Certificate(db, nodeId, "poet-0") + cert, err = certifier.Certificate(db, nodeId, []byte("certifier-0")) require.NoError(t, err) require.Equal(t, &expCert, cert) } @@ -37,14 +37,14 @@ func TestOverwritingCertificates(t *testing.T) { nodeId := types.RandomNodeID() expCert := certifier.PoetCert{Data: []byte("data"), Signature: []byte("sig")} - require.NoError(t, certifier.AddCertificate(db, nodeId, expCert, "poet-0")) - cert, err := certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert, []byte("certifier-0"))) + cert, err := certifier.Certificate(db, nodeId, []byte("certifier-0")) require.NoError(t, err) require.Equal(t, &expCert, cert) expCert2 := certifier.PoetCert{Data: []byte("data2"), Signature: []byte("sig2")} - require.NoError(t, certifier.AddCertificate(db, nodeId, expCert2, "poet-0")) - cert, err = certifier.Certificate(db, nodeId, "poet-0") + require.NoError(t, certifier.AddCertificate(db, nodeId, expCert2, []byte("certifier-0"))) + cert, err = certifier.Certificate(db, nodeId, []byte("certifier-0")) require.NoError(t, err) require.Equal(t, &expCert2, cert) } diff --git a/sql/migrations/local/0008_next.sql b/sql/migrations/local/0008_next.sql index 19c3249c1f..26d0bd1822 100644 --- a/sql/migrations/local/0008_next.sql +++ b/sql/migrations/local/0008_next.sql @@ -4,10 +4,10 @@ ALTER TABLE initial_post RENAME TO post; CREATE TABLE poet_certificates ( - node_id BLOB NOT NULL, - poet_url VARCHAR NOT NULL, - certificate BLOB NOT NULL, - signature BLOB NOT NULL + node_id BLOB NOT NULL, + certifier_id BLOB NOT NULL, + certificate BLOB NOT NULL, + signature BLOB NOT NULL ); -CREATE UNIQUE INDEX idx_poet_certificates ON poet_certificates (node_id, poet_url); +CREATE UNIQUE INDEX idx_poet_certificates ON poet_certificates (node_id, certifier_id); diff --git a/systest/tests/distributed_post_verification_test.go b/systest/tests/distributed_post_verification_test.go index a9d741e9e6..4ec6bd5cf8 100644 --- a/systest/tests/distributed_post_verification_test.go +++ b/systest/tests/distributed_post_verification_test.go @@ -156,26 +156,27 @@ func TestPostMalfeasanceProof(t *testing.T) { require.NoError(t, grpcPrivateServer.Start()) t.Cleanup(func() { assert.NoError(t, grpcPrivateServer.Close()) }) - poetClient, err := activation.NewHTTPPoetClient(types.PoetServer{ - Address: cluster.MakePoetGlobalEndpoint(ctx.Namespace, 0), - }, cfg.POET) - require.NoError(t, err) - db := sql.InMemory() localDb := localsql.InMemory() - certClient := activation.NewCertifierClient(db, localDb, logger.Named("certifier")) certifier := activation.NewCertifier(localDb, logger, certClient) + poetClient, err := activation.NewPoetClient( + activation.NewPoetDb(db, log.NewNop()), + types.PoetServer{ + Address: cluster.MakePoetGlobalEndpoint(ctx.Namespace, 0), + }, cfg.POET, + logger, + activation.WithCertifier(certifier), + ) + require.NoError(t, err) nipostBuilder, err := activation.NewNIPostBuilder( localDb, - activation.NewPoetDb(db, log.NewNop()), grpcPostService, logger.Named("nipostBuilder"), cfg.POET, clock, activation.WithPoetClients(poetClient), - activation.WithCertifier(certifier), ) require.NoError(t, err) From d1d597ad710fd26d98be8af0940cb218cc12662a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 22 May 2024 13:51:17 +0200 Subject: [PATCH 50/55] Extend E2E activation test to use poet certificates with expiration --- activation/e2e/activation_test.go | 26 ++++++++++++++++++++++++-- activation/e2e/poet_test.go | 5 +++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/activation/e2e/activation_test.go b/activation/e2e/activation_test.go index 5604b3376c..7bbdb3be8b 100644 --- a/activation/e2e/activation_test.go +++ b/activation/e2e/activation_test.go @@ -2,12 +2,16 @@ package activation_test import ( "context" + "net/url" "sync" "sync/atomic" "testing" "time" + "github.com/spacemeshos/poet/registration" + poetShared "github.com/spacemeshos/poet/shared" "github.com/spacemeshos/post/initialization" + "github.com/spacemeshos/post/verifying" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -50,6 +54,7 @@ func Test_BuilderWithMultipleClients(t *testing.T) { goldenATX := types.ATXID{2, 3, 4} cfg := activation.DefaultPostConfig() db := sql.InMemory() + localDB := localsql.InMemory() syncer := activation.NewMocksyncer(ctrl) syncer.EXPECT().RegisterForATXSynced().DoAndReturn(func() <-chan struct{} { @@ -103,15 +108,32 @@ func Test_BuilderWithMultipleClients(t *testing.T) { RequestRetryDelay: epoch / 50, MaxRequestRetries: 10, } + + pubkey, address := spawnTestCertifier( + t, + cfg, + func(id []byte) *poetShared.Cert { + exp := time.Now().Add(epoch) + return &poetShared.Cert{Pubkey: id, Expiration: &exp} + }, + verifying.WithLabelScryptParams(opts.Scrypt), + ) + poetProver := spawnPoet( t, WithGenesis(genesis), WithEpochDuration(epoch), WithPhaseShift(poetCfg.PhaseShift), WithCycleGap(poetCfg.CycleGap), + WithCertifier(®istration.CertifierConfig{ + URL: (&url.URL{Scheme: "http", Host: address.String()}).String(), + PubKey: registration.Base64Enc(pubkey), + }), ) + certClient := activation.NewCertifierClient(db, localDB, logger.Named("certifier")) + certifier := activation.NewCertifier(localDB, logger, certClient) poetDb := activation.NewPoetDb(db, log.NewFromLog(logger).Named("poetDb")) - client, err := poetProver.Client(poetDb, poetCfg, logger) + client, err := poetProver.Client(poetDb, poetCfg, logger, activation.WithCertifier(certifier)) require.NoError(t, err) clock, err := timesync.NewClock( @@ -124,7 +146,6 @@ func Test_BuilderWithMultipleClients(t *testing.T) { t.Cleanup(clock.Close) postStates := activation.NewMockPostStates(ctrl) - localDB := localsql.InMemory() nb, err := activation.NewNIPostBuilder( localDB, svc, @@ -188,6 +209,7 @@ func Test_BuilderWithMultipleClients(t *testing.T) { activation.WithPoetConfig(poetCfg), activation.WithValidator(v), activation.WithPostStates(postStates), + activation.WithPoets(client), ) for _, sig := range signers { gomock.InOrder( diff --git a/activation/e2e/poet_test.go b/activation/e2e/poet_test.go index 22adc8e4dc..6d873b3363 100644 --- a/activation/e2e/poet_test.go +++ b/activation/e2e/poet_test.go @@ -40,13 +40,14 @@ func (h *HTTPPoetTestHarness) Client( db *activation.PoetDb, cfg activation.PoetConfig, logger *zap.Logger, - opts ...activation.PoetClientOpts, + opts ...activation.PoetClientOpt, ) (activation.PoetClient, error) { return activation.NewPoetClient( db, h.ServerCfg(), cfg, logger, + opts..., ) } @@ -291,5 +292,5 @@ func TestNoCertifierInfo(t *testing.T) { require.NoError(t, err) _, _, err = client.CertifierInfo(context.Background()) - r.ErrorContains(err, "poet doesn't support certifier") + r.ErrorContains(err, "poet doesn't support certificates") } From 377c62d96fd186ae468261850148e88be8892c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 22 May 2024 13:59:58 +0200 Subject: [PATCH 51/55] Parallelize certifying initial post --- activation/activation.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/activation/activation.go b/activation/activation.go index 6cd219e488..f858875c99 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -400,12 +400,17 @@ func (b *Builder) run(ctx context.Context, sig *signing.EdSigner) { case <-b.layerClock.AwaitLayer(currentLayer.Add(1)): } } + var eg errgroup.Group for _, poet := range b.poets { - _, err := poet.Certify(ctx, sig.NodeID()) - if err != nil { - b.log.Warn("failed to certify poet", zap.Error(err), log.ZShortStringer("smesherID", sig.NodeID())) - } + eg.Go(func() error { + _, err := poet.Certify(ctx, sig.NodeID()) + if err != nil { + b.log.Warn("failed to certify poet", zap.Error(err), log.ZShortStringer("smesherID", sig.NodeID())) + } + return nil + }) } + eg.Wait() for { err := b.PublishActivationTx(ctx, sig) From 62ea324ba0b47d5c2d4c6c5f3f4bf8146da3dba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 22 May 2024 14:19:44 +0200 Subject: [PATCH 52/55] Avoid redundant certifications for (nodeID, pubkey) pairs --- activation/certifier.go | 31 +++++++++++++++++++++++++++---- activation/certifier_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index baef2225dd..36d9487364 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "net/url" + "sync" "time" "github.com/hashicorp/go-retryablehttp" @@ -81,6 +82,9 @@ type Certifier struct { logger *zap.Logger db *localsql.Database client certifierClient + + certificationsLock sync.Mutex + certifications map[string]chan struct{} } func NewCertifier( @@ -89,9 +93,10 @@ func NewCertifier( client certifierClient, ) *Certifier { c := &Certifier{ - client: client, - logger: logger, - db: db, + client: client, + logger: logger, + db: db, + certifications: make(map[string]chan struct{}), } return c @@ -103,7 +108,25 @@ func (c *Certifier) Certificate( certifier *url.URL, pubkey []byte, ) (*certifierdb.PoetCert, error) { - // TODO: avoid parallel certifications for a (id, certifier) pair + // We index certs in DB by node ID and pubkey. To avoid redundant queries, we allow only 1 + // request per (nodeID, pubkey) pair to be in flight at a time. + key := string(append(id.Bytes(), pubkey...)) + c.certificationsLock.Lock() + if ch, ok := c.certifications[key]; ok { + c.certificationsLock.Unlock() + <-ch + } else { + ch := make(chan struct{}) + c.certifications[key] = ch + c.certificationsLock.Unlock() + defer func() { + c.certificationsLock.Lock() + close(ch) + delete(c.certifications, key) + c.certificationsLock.Unlock() + }() + } + cert, err := certifierdb.Certificate(c.db, id, pubkey) switch { case err == nil: diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 1ec798e05b..0593401446 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "go.uber.org/zap/zaptest" + "golang.org/x/sync/errgroup" "github.com/spacemeshos/go-spacemesh/common/types" "github.com/spacemeshos/go-spacemesh/sql" @@ -53,6 +54,35 @@ func TestPersistsCerts(t *testing.T) { } } +func TestAvoidsRedundantQueries(t *testing.T) { + client := NewMockcertifierClient(gomock.NewController(t)) + id := types.RandomNodeID() + db := localsql.InMemory() + cert := &certdb.PoetCert{Data: []byte("cert"), Signature: []byte("sig")} + certifierAddress := &url.URL{Scheme: "http", Host: "certifier.org"} + pubkey := []byte("pubkey") + + c := NewCertifier(db, zaptest.NewLogger(t), client) + client.EXPECT(). + Certificate(gomock.Any(), id, certifierAddress, pubkey). + Return(cert, nil) + + var eg errgroup.Group + for i := 0; i < 100; i++ { + eg.Go(func() error { + got, err := c.Certificate(context.Background(), id, certifierAddress, pubkey) + require.NoError(t, err) + require.Equal(t, cert, got) + return nil + }) + } + eg.Wait() + + got, err := certdb.Certificate(db, id, pubkey) + require.NoError(t, err) + require.Equal(t, cert, got) +} + func TestObtainingPost(t *testing.T) { id := types.RandomNodeID() From 879db0360a314a421ca148e5bce6496c666210b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 23 May 2024 11:29:32 +0200 Subject: [PATCH 53/55] Review feedback --- activation/certifier.go | 51 ++++++++++--------------- activation/certifier_test.go | 50 ++++++++++++++++++------ activation/e2e/certifier_client_test.go | 6 +-- activation/interface.go | 16 +++++--- activation/mocks.go | 34 ++++++++--------- activation/nipost.go | 8 ++-- activation/nipost_test.go | 18 ++++----- activation/poet.go | 6 +-- activation/poet_client_test.go | 4 +- 9 files changed, 106 insertions(+), 87 deletions(-) diff --git a/activation/certifier.go b/activation/certifier.go index 36d9487364..4ac3c4aced 100644 --- a/activation/certifier.go +++ b/activation/certifier.go @@ -9,12 +9,12 @@ import ( "io" "net/http" "net/url" - "sync" "time" "github.com/hashicorp/go-retryablehttp" "github.com/spacemeshos/poet/shared" "go.uber.org/zap" + "golang.org/x/sync/singleflight" "github.com/spacemeshos/go-spacemesh/activation/wire" "github.com/spacemeshos/go-spacemesh/codec" @@ -83,8 +83,7 @@ type Certifier struct { db *localsql.Database client certifierClient - certificationsLock sync.Mutex - certifications map[string]chan struct{} + certifications singleflight.Group } func NewCertifier( @@ -93,10 +92,9 @@ func NewCertifier( client certifierClient, ) *Certifier { c := &Certifier{ - client: client, - logger: logger, - db: db, - certifications: make(map[string]chan struct{}), + client: client, + logger: logger, + db: db, } return c @@ -111,30 +109,21 @@ func (c *Certifier) Certificate( // We index certs in DB by node ID and pubkey. To avoid redundant queries, we allow only 1 // request per (nodeID, pubkey) pair to be in flight at a time. key := string(append(id.Bytes(), pubkey...)) - c.certificationsLock.Lock() - if ch, ok := c.certifications[key]; ok { - c.certificationsLock.Unlock() - <-ch - } else { - ch := make(chan struct{}) - c.certifications[key] = ch - c.certificationsLock.Unlock() - defer func() { - c.certificationsLock.Lock() - close(ch) - delete(c.certifications, key) - c.certificationsLock.Unlock() - }() - } + cert, err, _ := c.certifications.Do(key, func() (any, error) { + cert, err := certifierdb.Certificate(c.db, id, pubkey) + switch { + case err == nil: + return cert, nil + case !errors.Is(err, sql.ErrNotFound): + return nil, fmt.Errorf("getting certificate from DB for: %w", err) + } + return c.Recertify(ctx, id, certifier, pubkey) + }) - cert, err := certifierdb.Certificate(c.db, id, pubkey) - switch { - case err == nil: - return cert, nil - case !errors.Is(err, sql.ErrNotFound): - return nil, fmt.Errorf("getting certificate from DB for: %w", err) + if err != nil { + return nil, err } - return c.Recertify(ctx, id, certifier, pubkey) + return cert.(*certifierdb.PoetCert), nil } func (c *Certifier) Recertify( @@ -143,7 +132,7 @@ func (c *Certifier) Recertify( certifier *url.URL, pubkey []byte, ) (*certifierdb.PoetCert, error) { - cert, err := c.client.Certificate(ctx, id, certifier, pubkey) + cert, err := c.client.Certify(ctx, id, certifier, pubkey) if err != nil { return nil, fmt.Errorf("certifying POST at %v: %w", certifier, err) } @@ -257,7 +246,7 @@ func (c *CertifierClient) obtainPost(ctx context.Context, id types.NodeID) (*nip return nil, errors.New("PoST not found") } -func (c *CertifierClient) Certificate( +func (c *CertifierClient) Certify( ctx context.Context, id types.NodeID, url *url.URL, diff --git a/activation/certifier_test.go b/activation/certifier_test.go index 0593401446..cc329737da 100644 --- a/activation/certifier_test.go +++ b/activation/certifier_test.go @@ -28,7 +28,7 @@ func TestPersistsCerts(t *testing.T) { { c := NewCertifier(db, zaptest.NewLogger(t), client) client.EXPECT(). - Certificate(gomock.Any(), id, certifierAddress, pubkey). + Certify(gomock.Any(), id, certifierAddress, pubkey). Return(cert, nil) _, err := certdb.Certificate(db, id, pubkey) @@ -55,32 +55,58 @@ func TestPersistsCerts(t *testing.T) { } func TestAvoidsRedundantQueries(t *testing.T) { - client := NewMockcertifierClient(gomock.NewController(t)) - id := types.RandomNodeID() db := localsql.InMemory() - cert := &certdb.PoetCert{Data: []byte("cert"), Signature: []byte("sig")} + client := NewMockcertifierClient(gomock.NewController(t)) + id1 := types.RandomNodeID() + id2 := types.RandomNodeID() + cert1 := &certdb.PoetCert{Data: []byte("1"), Signature: []byte("sig")} + cert2 := &certdb.PoetCert{Data: []byte("2"), Signature: []byte("sig")} + cert3 := &certdb.PoetCert{Data: []byte("3"), Signature: []byte("sig")} certifierAddress := &url.URL{Scheme: "http", Host: "certifier.org"} pubkey := []byte("pubkey") + pubkey2 := []byte("pubkey2") c := NewCertifier(db, zaptest.NewLogger(t), client) - client.EXPECT(). - Certificate(gomock.Any(), id, certifierAddress, pubkey). - Return(cert, nil) + // The key is (id, pubkey) so we should only have one request in flight at a time + // for a given pair. + client.EXPECT().Certify(gomock.Any(), id1, certifierAddress, pubkey).Return(cert1, nil) + client.EXPECT().Certify(gomock.Any(), id2, certifierAddress, pubkey).Return(cert2, nil) + client.EXPECT().Certify(gomock.Any(), id1, certifierAddress, pubkey2).Return(cert3, nil) var eg errgroup.Group - for i := 0; i < 100; i++ { + for i := 0; i < 10; i++ { + eg.Go(func() error { + got, err := c.Certificate(context.Background(), id1, certifierAddress, pubkey) + require.NoError(t, err) + require.Equal(t, cert1, got) + return nil + }) eg.Go(func() error { - got, err := c.Certificate(context.Background(), id, certifierAddress, pubkey) + got, err := c.Certificate(context.Background(), id2, certifierAddress, pubkey) require.NoError(t, err) - require.Equal(t, cert, got) + require.Equal(t, cert2, got) + return nil + }) + eg.Go(func() error { + got, err := c.Certificate(context.Background(), id1, certifierAddress, pubkey2) + require.NoError(t, err) + require.Equal(t, cert3, got) return nil }) } eg.Wait() - got, err := certdb.Certificate(db, id, pubkey) + got, err := certdb.Certificate(db, id1, pubkey) + require.NoError(t, err) + require.Equal(t, cert1, got) + // different id - different cert + got, err = certdb.Certificate(db, id2, pubkey) + require.NoError(t, err) + require.Equal(t, cert2, got) + // different pubkey - different cert + got, err = certdb.Certificate(db, id1, pubkey2) require.NoError(t, err) - require.Equal(t, cert, got) + require.Equal(t, cert3, got) } func TestObtainingPost(t *testing.T) { diff --git a/activation/e2e/certifier_client_test.go b/activation/e2e/certifier_client_test.go index 79da084a35..26da2c7df4 100644 --- a/activation/e2e/certifier_client_test.go +++ b/activation/e2e/certifier_client_test.go @@ -91,7 +91,7 @@ func TestCertification(t *testing.T) { client := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) _, err := client. - Certificate(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.NoError(t, err) }) t.Run("certify rejects invalid cert (expired)", func(t *testing.T) { @@ -106,7 +106,7 @@ func TestCertification(t *testing.T) { client := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) cert, err := client. - Certificate(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) }) @@ -118,7 +118,7 @@ func TestCertification(t *testing.T) { client := activation.NewCertifierClient(db, localDb, zaptest.NewLogger(t)) cert, err := client. - Certificate(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) + Certify(context.Background(), sig.NodeID(), &url.URL{Scheme: "http", Host: addr.String()}, pubKey) require.Error(t, err) require.Nil(t, cert) }) diff --git a/activation/interface.go b/activation/interface.go index 33b18f37e2..eb451e1c0d 100644 --- a/activation/interface.go +++ b/activation/interface.go @@ -122,7 +122,7 @@ type SmeshingProvider interface { // PoetClient servers as an interface to communicate with a PoET server. // It is used to submit challenges and fetch proofs. type PoetClient interface { - Address() *url.URL + Address() string // Submit registers a challenge in the proving service current open round. Submit( @@ -143,9 +143,8 @@ type PoetClient interface { // The implementation can use any method to obtain the certificate, // for example, POST verification. type certifierClient interface { - // Acquire a certificate for the ID in the given certifier. - // The certificate confirms that the ID is verified and it can be later used to submit in poet. - Certificate( + // Certify obtains a certificate in a remote certifier service. + Certify( ctx context.Context, id types.NodeID, certifierAddress *url.URL, @@ -156,7 +155,14 @@ type certifierClient interface { // certifierService is used to certify nodeID for registering in the poet. // It holds the certificates and can recertify if needed. type certifierService interface { - certifierClient + // Acquire a certificate for the ID in the given certifier. + // The certificate confirms that the ID is verified and it can be later used to submit in poet. + Certificate( + ctx context.Context, + id types.NodeID, + certifierAddress *url.URL, + pubkey []byte, + ) (*certifier.PoetCert, error) Recertify( ctx context.Context, diff --git a/activation/mocks.go b/activation/mocks.go index dd8e80ef35..e74c3cc7f4 100644 --- a/activation/mocks.go +++ b/activation/mocks.go @@ -1476,10 +1476,10 @@ func (m *MockPoetClient) EXPECT() *MockPoetClientMockRecorder { } // Address mocks base method. -func (m *MockPoetClient) Address() *url.URL { +func (m *MockPoetClient) Address() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Address") - ret0, _ := ret[0].(*url.URL) + ret0, _ := ret[0].(string) return ret0 } @@ -1496,19 +1496,19 @@ type MockPoetClientAddressCall struct { } // Return rewrite *gomock.Call.Return -func (c *MockPoetClientAddressCall) Return(arg0 *url.URL) *MockPoetClientAddressCall { +func (c *MockPoetClientAddressCall) Return(arg0 string) *MockPoetClientAddressCall { c.Call = c.Call.Return(arg0) return c } // Do rewrite *gomock.Call.Do -func (c *MockPoetClientAddressCall) Do(f func() *url.URL) *MockPoetClientAddressCall { +func (c *MockPoetClientAddressCall) Do(f func() string) *MockPoetClientAddressCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockPoetClientAddressCall) DoAndReturn(f func() *url.URL) *MockPoetClientAddressCall { +func (c *MockPoetClientAddressCall) DoAndReturn(f func() string) *MockPoetClientAddressCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -1654,41 +1654,41 @@ func (m *MockcertifierClient) EXPECT() *MockcertifierClientMockRecorder { return m.recorder } -// Certificate mocks base method. -func (m *MockcertifierClient) Certificate(ctx context.Context, id types.NodeID, certifierAddress *url.URL, pubkey []byte) (*certifier.PoetCert, error) { +// Certify mocks base method. +func (m *MockcertifierClient) Certify(ctx context.Context, id types.NodeID, certifierAddress *url.URL, pubkey []byte) (*certifier.PoetCert, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Certificate", ctx, id, certifierAddress, pubkey) + ret := m.ctrl.Call(m, "Certify", ctx, id, certifierAddress, pubkey) ret0, _ := ret[0].(*certifier.PoetCert) ret1, _ := ret[1].(error) return ret0, ret1 } -// Certificate indicates an expected call of Certificate. -func (mr *MockcertifierClientMockRecorder) Certificate(ctx, id, certifierAddress, pubkey any) *MockcertifierClientCertificateCall { +// Certify indicates an expected call of Certify. +func (mr *MockcertifierClientMockRecorder) Certify(ctx, id, certifierAddress, pubkey any) *MockcertifierClientCertifyCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certificate", reflect.TypeOf((*MockcertifierClient)(nil).Certificate), ctx, id, certifierAddress, pubkey) - return &MockcertifierClientCertificateCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Certify", reflect.TypeOf((*MockcertifierClient)(nil).Certify), ctx, id, certifierAddress, pubkey) + return &MockcertifierClientCertifyCall{Call: call} } -// MockcertifierClientCertificateCall wrap *gomock.Call -type MockcertifierClientCertificateCall struct { +// MockcertifierClientCertifyCall wrap *gomock.Call +type MockcertifierClientCertifyCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockcertifierClientCertificateCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierClientCertificateCall { +func (c *MockcertifierClientCertifyCall) Return(arg0 *certifier.PoetCert, arg1 error) *MockcertifierClientCertifyCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockcertifierClientCertificateCall) Do(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertificateCall { +func (c *MockcertifierClientCertifyCall) Do(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockcertifierClientCertificateCall) DoAndReturn(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertificateCall { +func (c *MockcertifierClientCertifyCall) DoAndReturn(f func(context.Context, types.NodeID, *url.URL, []byte) (*certifier.PoetCert, error)) *MockcertifierClientCertifyCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/activation/nipost.go b/activation/nipost.go index 3eaffb2f92..7fde9dcdc7 100644 --- a/activation/nipost.go +++ b/activation/nipost.go @@ -60,7 +60,7 @@ func WithPoetClients(clients ...PoetClient) NIPostBuilderOption { return func(nb *NIPostBuilder) { nb.poetProvers = make(map[string]PoetClient, len(clients)) for _, client := range clients { - nb.poetProvers[client.Address().String()] = client + nb.poetProvers[client.Address()] = client } } } @@ -337,7 +337,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( ) error { logger := nb.log.With( log.ZContext(ctx), - zap.String("poet", client.Address().String()), + zap.String("poet", client.Address()), log.ZShortStringer("smesherID", nodeID), ) @@ -353,7 +353,7 @@ func (nb *NIPostBuilder) submitPoetChallenge( logger.Info("challenge submitted to poet proving service", zap.String("round", round.ID)) return nipost.AddPoetRegistration(nb.localDB, nodeID, nipost.PoETRegistration{ ChallengeHash: types.Hash32(challenge), - Address: client.Address().String(), + Address: client.Address(), RoundID: round.ID, RoundEnd: round.End, }) @@ -401,7 +401,7 @@ func (nb *NIPostBuilder) submitPoetChallenges( func (nb *NIPostBuilder) getPoetClient(ctx context.Context, address string) PoetClient { for _, client := range nb.poetProvers { - if address == client.Address().String() { + if address == client.Address() { return client } } diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 6485f162f3..9ad7e95874 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -32,9 +32,7 @@ func defaultPoetServiceMock(t *testing.T, ctrl *gomock.Controller, address strin Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). AnyTimes(). Return(&types.PoetRound{}, nil) - url, err := url.Parse(address) - require.NoError(t, err) - poetClient.EXPECT().Address().AnyTimes().Return(url).AnyTimes() + poetClient.EXPECT().Address().AnyTimes().Return(address).AnyTimes() return poetClient } @@ -558,7 +556,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. <-ctx.Done() return nil, ctx.Err() }) - poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") poets = append(poets, poet) } { @@ -569,7 +567,7 @@ func TestNIPostBuilder_ManyPoETs_SubmittingChallenge_DeadlineReached(t *testing. poet.EXPECT(). Proof(gomock.Any(), gomock.Any()). Return(proof, []types.Hash32{challenge}, nil) - poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9998"}) + poet.EXPECT().Address().AnyTimes().Return("http://localhost:9998") poets = append(poets, poet) } @@ -797,7 +795,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ctrl := gomock.NewController(t) mclock := NewMocklayerClock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poetProver.EXPECT().Address().Return("http://localhost:9999") mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { return genesis.Add(layerDuration * time.Duration(got)) @@ -824,7 +822,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ctrl := gomock.NewController(t) mclock := NewMocklayerClock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poetProver.EXPECT().Address().Return("http://localhost:9999") mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { return genesis.Add(layerDuration * time.Duration(got)) @@ -865,7 +863,7 @@ func TestNIPoSTBuilder_StaleChallenge(t *testing.T) { ctrl := gomock.NewController(t) mclock := NewMocklayerClock(ctrl) poetProver := NewMockPoetClient(ctrl) - poetProver.EXPECT().Address().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poetProver.EXPECT().Address().Return("http://localhost:9999") mclock.EXPECT().LayerToTime(gomock.Any()).DoAndReturn( func(got types.LayerID) time.Time { return genesis.Add(layerDuration * time.Duration(got)) @@ -1027,8 +1025,8 @@ func TestNIPostBuilder_Mainnet_Poet_Workaround(t *testing.T) { tt := []struct { name string - from *url.URL - to *url.URL + from string + to string epoch types.EpochID }{ // no mitigation needed at the moment diff --git a/activation/poet.go b/activation/poet.go index 6cc6f7800b..cd001bf172 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -358,8 +358,8 @@ func NewPoetClient( return poetClient, nil } -func (c *poetClient) Address() *url.URL { - return c.client.Address() +func (c *poetClient) Address() string { + return c.client.Address().String() } func (c *poetClient) authorize( @@ -418,7 +418,7 @@ func (c *poetClient) Submit( ) (*types.PoetRound, error) { logger := c.logger.With( log.ZContext(ctx), - zap.String("poet", c.Address().String()), + zap.String("poet", c.Address()), log.ZShortStringer("smesherID", nodeID), ) diff --git a/activation/poet_client_test.go b/activation/poet_client_test.go index 3dac5f3758..1acfbbffda 100644 --- a/activation/poet_client_test.go +++ b/activation/poet_client_test.go @@ -97,7 +97,7 @@ func Test_HTTPPoetClient_Address(t *testing.T) { }, withCustomHttpClient(ts.Client())) require.NoError(t, err) - require.Equal(t, ts.URL, client.Address().String()) + require.Equal(t, ts.URL, client.Address()) } func Test_HTTPPoetClient_Address_Mainnet(t *testing.T) { @@ -117,7 +117,7 @@ func Test_HTTPPoetClient_Address_Mainnet(t *testing.T) { CycleGap: poetCfg.CycleGap, }) require.NoError(t, err) - require.Equal(t, url, client.Address().String()) + require.Equal(t, url, client.Address()) }) } } From c9f3f96b5161ce0e5dbc252fe1f9aadc6aeb02de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 23 May 2024 13:35:58 +0200 Subject: [PATCH 54/55] Fix remaining UTs --- activation/nipost_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/activation/nipost_test.go b/activation/nipost_test.go index 9ad7e95874..f1f14116a8 100644 --- a/activation/nipost_test.go +++ b/activation/nipost_test.go @@ -3,7 +3,6 @@ package activation import ( "context" "errors" - "net/url" "testing" "time" @@ -622,7 +621,7 @@ func TestNIPostBuilder_ManyPoETs_AllFinished(t *testing.T) { poet.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). Return(&types.PoetRound{}, nil) - poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") poet.EXPECT().Proof(gomock.Any(), "").Return(proofWorse, []types.Hash32{challenge}, nil) poets = append(poets, poet) } @@ -679,7 +678,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { poetProver.EXPECT(). Submit(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), sig.NodeID()). Return(nil, errors.New("test")) - poetProver.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) nb, err := NewNIPostBuilder( @@ -714,7 +713,7 @@ func TestNIPSTBuilder_PoetUnstable(t *testing.T) { <-ctx.Done() return nil, ctx.Err() }) - poetProver.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poetProver.EXPECT().Address().AnyTimes().Return("http://localhost:9999") postService := NewMockpostService(ctrl) nb, err := NewNIPostBuilder( @@ -936,7 +935,7 @@ func TestNIPoSTBuilder_Continues_After_Interrupted(t *testing.T) { return &types.PoetRound{}, context.Canceled }) poet.EXPECT().Proof(gomock.Any(), "").Return(proof, []types.Hash32{challenge}, nil) - poet.EXPECT().Address().AnyTimes().Return(&url.URL{Scheme: "http", Host: "localhost:9999"}) + poet.EXPECT().Address().AnyTimes().Return("http://localhost:9999") poetCfg := PoetConfig{ PhaseShift: layerDuration * layersPerEpoch / 2, From 7f83cab177ea32da45496c93b559edba9c6f0db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 23 May 2024 14:16:04 +0200 Subject: [PATCH 55/55] Fix remaining UTs --- activation/poet.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activation/poet.go b/activation/poet.go index cd001bf172..ef0457fa6a 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -142,8 +142,8 @@ func NewHTTPPoetClient(server types.PoetServer, cfg PoetConfig, opts ...PoetClie return poetClient, nil } -func (c *HTTPPoetClient) Address() *url.URL { - return c.baseURL +func (c *HTTPPoetClient) Address() string { + return c.baseURL.String() } func (c *HTTPPoetClient) PowParams(ctx context.Context) (*PoetPowParams, error) { @@ -359,7 +359,7 @@ func NewPoetClient( } func (c *poetClient) Address() string { - return c.client.Address().String() + return c.client.Address() } func (c *poetClient) authorize(