Skip to content

Commit

Permalink
fallback to PoW if cannot recertify for poet (#6197)
Browse files Browse the repository at this point in the history
## Motivation

Fallback to PoW if cannot recertify after poet registration failed with 401 (unauthorized).
  • Loading branch information
poszu committed Aug 1, 2024
1 parent 3324aef commit ae34c28
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 54 deletions.
28 changes: 13 additions & 15 deletions activation/certifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ func (c *Certifier) Certificate(
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 = c.client.Certify(ctx, id, certifier, pubkey)
if err != nil {
return nil, fmt.Errorf("certifying POST at %v: %w", certifier, err)
}

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
})

if err != nil {
Expand All @@ -126,21 +134,11 @@ func (c *Certifier) Certificate(
return cert.(*certifierdb.PoetCert), nil
}

func (c *Certifier) Recertify(
ctx context.Context,
id types.NodeID,
certifier *url.URL,
pubkey []byte,
) (*certifierdb.PoetCert, error) {
cert, err := c.client.Certify(ctx, id, certifier, pubkey)
if err != nil {
return nil, fmt.Errorf("certifying POST at %v: %w", certifier, err)
}

if err := certifierdb.AddCertificate(c.db, id, *cert, pubkey); err != nil {
c.logger.Warn("failed to persist poet cert", zap.Error(err))
func (c *Certifier) DeleteCertificate(id types.NodeID, pubkey []byte) error {
if err := certifierdb.DeleteCertificate(c.db, id, pubkey); err != nil {
return err
}
return cert, nil
return nil
}

type CertifierClient struct {
Expand Down
7 changes: 1 addition & 6 deletions activation/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,7 @@ type certifierService interface {
pubkey []byte,
) (*certifier.PoetCert, error)

Recertify(
ctx context.Context,
id types.NodeID,
certifierAddress *url.URL,
pubkey []byte,
) (*certifier.PoetCert, error)
DeleteCertificate(id types.NodeID, pubkey []byte) error
}

type poetDbAPI interface {
Expand Down
31 changes: 15 additions & 16 deletions activation/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 20 additions & 16 deletions activation/poet.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,7 @@ func (c *poetService) authorize(
}
// Fallback to PoW
// TODO: remove this fallback once we migrate to certificates fully.

logger.Debug("querying for poet pow parameters")
logger.Info("falling back to PoW authorization")
powCtx, cancel := withConditionalTimeout(ctx, c.requestTimeout)
defer cancel()
powParams, err := c.client.PowParams(powCtx)
Expand Down Expand Up @@ -460,6 +459,22 @@ func (c *poetService) authorize(
}}, nil
}

func (c *poetService) reauthorize(
ctx context.Context,
id types.NodeID,
challange []byte,
logger *zap.Logger,
) (*PoetAuth, error) {
if c.certifier != nil {
if _, pubkey, err := c.getCertifierInfo(ctx); err == nil {
if err := c.certifier.DeleteCertificate(id, pubkey); err != nil {
return nil, fmt.Errorf("deleting cert: %w", err)
}
}
}
return c.authorize(ctx, id, challange, c.logger)
}

func (c *poetService) Submit(
ctx context.Context,
deadline time.Time,
Expand Down Expand Up @@ -488,10 +503,10 @@ func (c *poetService) Submit(
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)
logger.Warn("failed to submit challenge as unathorized - authorizing again", zap.Error(err))
auth, err := c.reauthorize(ctx, nodeID, challenge, logger)
if err != nil {
return nil, fmt.Errorf("recertifying: %w", err)
return nil, fmt.Errorf("authorizing: %w", err)
}
return c.client.Submit(submitCtx, deadline, prefix, challenge, signature, nodeID, *auth)
}
Expand Down Expand Up @@ -540,17 +555,6 @@ func (c *poetService) Certify(ctx context.Context, id types.NodeID) (*certifier.
return c.certifier.Certificate(ctx, id, url, pubkey)
}

func (c *poetService) 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.getCertifierInfo(ctx)
if err != nil {
return nil, err
}
return c.certifier.Recertify(ctx, id, url, pubkey)
}

func (c *poetService) getCertifierInfo(ctx context.Context) (*url.URL, []byte, error) {
c.certifierInfoMutex.Lock()
defer c.certifierInfoMutex.Unlock()
Expand Down
79 changes: 78 additions & 1 deletion activation/poet_client_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package activation

import (
"bytes"
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -366,8 +368,9 @@ func TestPoetClient_RecertifiesOnAuthFailure(t *testing.T) {
mCertifier.EXPECT().
Certificate(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey).
Return(&certifier.PoetCert{Data: []byte("first")}, nil),
mCertifier.EXPECT().DeleteCertificate(sig.NodeID(), certifierPubKey),
mCertifier.EXPECT().
Recertify(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey).
Certificate(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey).
Return(&certifier.PoetCert{Data: []byte("second")}, nil),
)

Expand All @@ -382,6 +385,80 @@ func TestPoetClient_RecertifiesOnAuthFailure(t *testing.T) {
require.EqualValues(t, "second", <-certs)
}

func TestPoetClient_FallbacksToPowWhenCannotRecertify(t *testing.T) {
t.Parallel()

sig, err := signing.NewEdSigner()
require.NoError(t, err)

certifierAddress := &url.URL{Scheme: "http", Host: "certifier"}
certifierPubKey := []byte("certifier-pubkey")

mux := http.NewServeMux()
infoResp, err := protojson.Marshal(&rpcapi.InfoResponse{
ServicePubkey: []byte("pubkey"),
Certifier: &rpcapi.InfoResponse_Cerifier{
Url: certifierAddress.String(),
Pubkey: certifierPubKey,
},
})
require.NoError(t, err)
mux.HandleFunc("GET /v1/info", func(w http.ResponseWriter, r *http.Request) { w.Write(infoResp) })

powChallenge := []byte("challenge")
powResp, err := protojson.Marshal(&rpcapi.PowParamsResponse{PowParams: &rpcapi.PowParams{Challenge: powChallenge}})
require.NoError(t, err)
mux.HandleFunc("GET /v1/pow_params", func(w http.ResponseWriter, r *http.Request) { w.Write(powResp) })

submitResp, err := protojson.Marshal(&rpcapi.SubmitResponse{})
require.NoError(t, err)
submitCount := 0
mux.HandleFunc("POST /v1/submit", func(w http.ResponseWriter, r *http.Request) {
req := rpcapi.SubmitRequest{}
body, _ := io.ReadAll(r.Body)
protojson.Unmarshal(body, &req)

switch {
case submitCount == 0:
w.WriteHeader(http.StatusUnauthorized)
case submitCount == 1 && req.Certificate == nil && bytes.Equal(req.PowParams.Challenge, powChallenge):
w.Write(submitResp)
default:
w.WriteHeader(http.StatusUnauthorized)
}
submitCount++
})

ts := httptest.NewServer(mux)
defer ts.Close()

server := types.PoetServer{
Address: ts.URL,
Pubkey: types.NewBase64Enc([]byte("pubkey")),
}
cfg := PoetConfig{CertifierInfoCacheTTL: time.Hour}

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().DeleteCertificate(sig.NodeID(), certifierPubKey),
mCertifier.EXPECT().
Certificate(gomock.Any(), sig.NodeID(), certifierAddress, certifierPubKey).
Return(nil, errors.New("cannot recertify")),
)

client, err := NewHTTPPoetClient(server, cfg, withCustomHttpClient(ts.Client()))
require.NoError(t, err)
poet := NewPoetServiceWithClient(nil, client, cfg, zaptest.NewLogger(t), WithCertifier(mCertifier))

_, err = poet.Submit(context.Background(), time.Time{}, nil, nil, types.RandomEdSignature(), sig.NodeID())
require.NoError(t, err)
require.Equal(t, 2, submitCount)
}

func TestPoetService_CachesCertifierInfo(t *testing.T) {
t.Parallel()
type test struct {
Expand Down
13 changes: 13 additions & 0 deletions sql/localsql/certifier/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ func AddCertificate(db sql.Executor, nodeID types.NodeID, cert PoetCert, cerifie
return nil
}

func DeleteCertificate(db sql.Executor, nodeID types.NodeID, certifierID []byte) error {
enc := func(stmt *sql.Statement) {
stmt.BindBytes(1, nodeID.Bytes())
stmt.BindBytes(2, certifierID)
}
if _, err := db.Exec(`
DELETE FROM poet_certificates WHERE node_id = ?1 AND certifier_id = ?2;`, enc, nil,
); err != nil {
return fmt.Errorf("deleting poet certificate for (%s; %x): %w", nodeID.ShortString(), certifierID, err)
}
return nil
}

func Certificate(db sql.Executor, nodeID types.NodeID, certifierID []byte) (*PoetCert, error) {
enc := func(stmt *sql.Statement) {
stmt.BindBytes(1, nodeID.Bytes())
Expand Down

0 comments on commit ae34c28

Please sign in to comment.