Skip to content

Commit

Permalink
cache poet certifier info (#6107)
Browse files Browse the repository at this point in the history
## Motivation

To avoid querying the same poet `/v1/info` endpoint many times in the 1:N setups.
  • Loading branch information
poszu authored and fasmat committed Jul 22, 2024
1 parent 69618c8 commit ee2e676
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 16 deletions.
6 changes: 4 additions & 2 deletions activation/activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ type PoetConfig struct {
RequestTimeout time.Duration `mapstructure:"poet-request-timeout"`
RequestRetryDelay time.Duration `mapstructure:"retry-delay"`
PositioningATXSelectionTimeout time.Duration `mapstructure:"positioning-atx-selection-timeout"`
CertifierInfoCacheTTL time.Duration `mapstructure:"certifier-info-cache-ttl"`
MaxRequestRetries int `mapstructure:"retry-max"`
}

func DefaultPoetConfig() PoetConfig {
return PoetConfig{
RequestRetryDelay: 400 * time.Millisecond,
MaxRequestRetries: 10,
RequestRetryDelay: 400 * time.Millisecond,
MaxRequestRetries: 10,
CertifierInfoCacheTTL: 5 * time.Minute,
}
}

Expand Down
2 changes: 1 addition & 1 deletion activation/e2e/poet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (h *HTTPPoetTestHarness) Client(
db *activation.PoetDb,
cfg activation.PoetConfig,
logger *zap.Logger,
opts ...activation.PoetClientOpt,
opts ...activation.PoetServiceOpt,
) (activation.PoetService, error) {
return activation.NewPoetService(
db,
Expand Down
57 changes: 44 additions & 13 deletions activation/poet.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"github.com/spacemeshos/go-spacemesh/sql/localsql/certifier"
)

//go:generate mockgen -typed -package=activation -destination=poet_mocks.go -source=./poet.go

var (
ErrInvalidRequest = errors.New("invalid request")
ErrUnauthorized = errors.New("unauthorized")
Expand Down Expand Up @@ -329,6 +331,12 @@ func (c *HTTPPoetClient) req(ctx context.Context, method, path string, reqBody,
return nil
}

type certifierInfo struct {
obtained time.Time
url *url.URL
pubkey []byte
}

// poetService is a higher-level interface to communicate with a PoET service.
// It wraps the HTTP client, adding additional functionality.
type poetService struct {
Expand All @@ -343,11 +351,15 @@ type poetService struct {
proofMembers map[string][]types.Hash32

certifier certifierService

certifierInfoTTL time.Duration
certifierInfo certifierInfo
certifierInfoMutex sync.Mutex
}

type PoetClientOpt func(*poetService)
type PoetServiceOpt func(*poetService)

func WithCertifier(certifier certifierService) PoetClientOpt {
func WithCertifier(certifier certifierService) PoetServiceOpt {
return func(c *poetService) {
c.certifier = certifier
}
Expand All @@ -358,7 +370,7 @@ func NewPoetService(
server types.PoetServer,
cfg PoetConfig,
logger *zap.Logger,
opts ...PoetClientOpt,
opts ...PoetServiceOpt,
) (*poetService, error) {
client, err := NewHTTPPoetClient(server, cfg, WithLogger(logger))
if err != nil {
Expand All @@ -378,14 +390,15 @@ func NewPoetServiceWithClient(
client PoetClient,
cfg PoetConfig,
logger *zap.Logger,
opts ...PoetClientOpt,
opts ...PoetServiceOpt,
) *poetService {
poetClient := &poetService{
db: db,
logger: logger,
client: client,
requestTimeout: cfg.RequestTimeout,
proofMembers: make(map[string][]types.Hash32, 1),
db: db,
logger: logger,
client: client,
requestTimeout: cfg.RequestTimeout,
certifierInfoTTL: cfg.CertifierInfoCacheTTL,
proofMembers: make(map[string][]types.Hash32, 1),
}

for _, opt := range opts {
Expand Down Expand Up @@ -519,9 +532,9 @@ func (c *poetService) Certify(ctx context.Context, id types.NodeID) (*certifier.
if c.certifier == nil {
return nil, errors.New("certifier not configured")
}
url, pubkey, err := c.client.CertifierInfo(ctx)
url, pubkey, err := c.getCertifierInfo(ctx)
if err != nil {
return nil, fmt.Errorf("getting certifier info: %w", err)
return nil, err
}
return c.certifier.Certificate(ctx, id, url, pubkey)
}
Expand All @@ -530,9 +543,27 @@ func (c *poetService) recertify(ctx context.Context, id types.NodeID) (*certifie
if c.certifier == nil {
return nil, errors.New("certifier not configured")
}
url, pubkey, err := c.client.CertifierInfo(ctx)
url, pubkey, err := c.getCertifierInfo(ctx)
if err != nil {
return nil, fmt.Errorf("getting certifier info: %w", err)
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()
if time.Since(c.certifierInfo.obtained) < c.certifierInfoTTL {
return c.certifierInfo.url, c.certifierInfo.pubkey, nil
}
url, pubkey, err := c.client.CertifierInfo(ctx)
if err != nil {
return nil, nil, fmt.Errorf("getting certifier info: %w", err)
}
c.certifierInfo = certifierInfo{
obtained: time.Now(),
url: url,
pubkey: pubkey,
}
return c.certifierInfo.url, c.certifierInfo.pubkey, nil
}
34 changes: 34 additions & 0 deletions activation/poet_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/spacemeshos/go-spacemesh/common/types"
"github.com/spacemeshos/go-spacemesh/signing"
"github.com/spacemeshos/go-spacemesh/sql"
"github.com/spacemeshos/go-spacemesh/sql/localsql/certifier"
)

Expand Down Expand Up @@ -387,3 +388,36 @@ func TestPoetClient_RecertifiesOnAuthFailure(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 2, submitCount)
}

func TestPoetService_CachesCertifierInfo(t *testing.T) {
t.Parallel()
type test struct {
name string
ttl time.Duration
}
for _, tc := range []test{
{name: "cache enabled", ttl: time.Hour},
{name: "cache disabled"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
cfg := DefaultPoetConfig()
cfg.CertifierInfoCacheTTL = tc.ttl
client := NewMockPoetClient(gomock.NewController(t))
db := NewPoetDb(sql.InMemory(), zaptest.NewLogger(t))
poet := NewPoetServiceWithClient(db, client, cfg, zaptest.NewLogger(t))
url := &url.URL{Host: "certifier.hello"}
pubkey := []byte("pubkey")
exp := client.EXPECT().CertifierInfo(gomock.Any()).Return(url, pubkey, nil)
if tc.ttl == 0 {
exp.Times(5)
}
for range 5 {
gotUrl, gotPubkey, err := poet.getCertifierInfo(context.Background())
require.NoError(t, err)
require.Equal(t, url, gotUrl)
require.Equal(t, pubkey, gotPubkey)
}
})
}
}
Loading

0 comments on commit ee2e676

Please sign in to comment.