Skip to content

Commit

Permalink
cache poet's /v1/pow_params with TTL (#6199)
Browse files Browse the repository at this point in the history
## Motivation

Similarly to /v1/info, we query /v1/pow_params very often (once per submit per node ID). As the contents returned from this endpoint change rarely (once per epoch), it makes sense to cache the result.
  • Loading branch information
poszu committed Aug 1, 2024
1 parent ae34c28 commit 761b368
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 26 deletions.
2 changes: 2 additions & 0 deletions activation/activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type PoetConfig struct {
RequestRetryDelay time.Duration `mapstructure:"retry-delay"`
PositioningATXSelectionTimeout time.Duration `mapstructure:"positioning-atx-selection-timeout"`
CertifierInfoCacheTTL time.Duration `mapstructure:"certifier-info-cache-ttl"`
PowParamsCacheTTL time.Duration `mapstructure:"pow-params-cache-ttl"`
MaxRequestRetries int `mapstructure:"retry-max"`
}

Expand All @@ -54,6 +55,7 @@ func DefaultPoetConfig() PoetConfig {
RequestRetryDelay: 400 * time.Millisecond,
MaxRequestRetries: 10,
CertifierInfoCacheTTL: 5 * time.Minute,
PowParamsCacheTTL: 5 * time.Minute,
}
}

Expand Down
75 changes: 49 additions & 26 deletions activation/poet.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,29 @@ func (c *HTTPPoetClient) req(ctx context.Context, method, path string, reqBody,
}

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

type cachedData[T any] struct {
mu sync.Mutex
data T
exp time.Time
ttl time.Duration
}

func (c *cachedData[T]) get(init func() (T, error)) (T, error) {
c.mu.Lock()
defer c.mu.Unlock()
if time.Now().Before(c.exp) {
return c.data, nil
}
d, err := init()
if err == nil {
c.data = d
c.exp = time.Now().Add(c.ttl)
}
return d, err
}

// poetService is a higher-level interface to communicate with a PoET service.
Expand All @@ -353,9 +373,8 @@ type poetService struct {

certifier certifierService

certifierInfoTTL time.Duration
certifierInfo certifierInfo
certifierInfoMutex sync.Mutex
certifierInfoCache cachedData[*certifierInfo]
powParamsCache cachedData[*PoetPowParams]
}

type PoetServiceOpt func(*poetService)
Expand Down Expand Up @@ -394,12 +413,13 @@ func NewPoetServiceWithClient(
opts ...PoetServiceOpt,
) *poetService {
poetClient := &poetService{
db: db,
logger: logger,
client: client,
requestTimeout: cfg.RequestTimeout,
certifierInfoTTL: cfg.CertifierInfoCacheTTL,
proofMembers: make(map[string][]types.Hash32, 1),
db: db,
logger: logger,
client: client,
requestTimeout: cfg.RequestTimeout,
certifierInfoCache: cachedData[*certifierInfo]{ttl: cfg.CertifierInfoCacheTTL},
powParamsCache: cachedData[*PoetPowParams]{ttl: cfg.PowParamsCacheTTL},
proofMembers: make(map[string][]types.Hash32, 1),
}

for _, opt := range opts {
Expand Down Expand Up @@ -434,7 +454,7 @@ func (c *poetService) authorize(
logger.Info("falling back to PoW authorization")
powCtx, cancel := withConditionalTimeout(ctx, c.requestTimeout)
defer cancel()
powParams, err := c.client.PowParams(powCtx)
powParams, err := c.powParams(powCtx)
if err != nil {
return nil, &PoetSvcUnstableError{msg: "failed to get PoW params", source: err}
}
Expand Down Expand Up @@ -556,19 +576,22 @@ func (c *poetService) Certify(ctx context.Context, id types.NodeID) (*certifier.
}

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)
info, err := c.certifierInfoCache.get(func() (*certifierInfo, error) {
url, pubkey, err := c.client.CertifierInfo(ctx)
if err != nil {
return nil, fmt.Errorf("getting certifier info: %w", err)
}
return &certifierInfo{url: url, pubkey: pubkey}, nil
})
if err != nil {
return nil, nil, fmt.Errorf("getting certifier info: %w", err)
}
c.certifierInfo = certifierInfo{
obtained: time.Now(),
url: url,
pubkey: pubkey,
return nil, nil, err
}
return c.certifierInfo.url, c.certifierInfo.pubkey, nil

return info.url, info.pubkey, nil
}

func (c *poetService) powParams(ctx context.Context) (*PoetPowParams, error) {
return c.powParamsCache.get(func() (*PoetPowParams, error) {
return c.client.PowParams(ctx)
})
}
34 changes: 34 additions & 0 deletions activation/poet_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,37 @@ func TestPoetService_CachesCertifierInfo(t *testing.T) {
})
}
}

func TestPoetService_CachesPowParams(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.PowParamsCacheTTL = tc.ttl
client := NewMockPoetClient(gomock.NewController(t))
poet := NewPoetServiceWithClient(nil, client, cfg, zaptest.NewLogger(t))

params := PoetPowParams{
Challenge: types.RandomBytes(10),
Difficulty: 8,
}
exp := client.EXPECT().PowParams(gomock.Any()).Return(&params, nil)
if tc.ttl == 0 {
exp.Times(5)
}
for range 5 {
got, err := poet.powParams(context.Background())
require.NoError(t, err)
require.Equal(t, params, *got)
}
})
}
}
1 change: 1 addition & 0 deletions config/mainnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ func MainnetConfig() Config {
GracePeriod: 1 * time.Hour,
PositioningATXSelectionTimeout: 50 * time.Minute,
CertifierInfoCacheTTL: 5 * time.Minute,
PowParamsCacheTTL: 5 * time.Minute,
// RequestTimeout = RequestRetryDelay * 2 * MaxRequestRetries*(MaxRequestRetries+1)/2
RequestTimeout: 1100 * time.Second,
RequestRetryDelay: 10 * time.Second,
Expand Down
1 change: 1 addition & 0 deletions config/presets/fastnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func fastnet() config.Config {
conf.POET.RequestRetryDelay = 1 * time.Second
conf.POET.MaxRequestRetries = 3
conf.POET.CertifierInfoCacheTTL = time.Minute
conf.POET.PowParamsCacheTTL = 10 * time.Second

return conf
}
1 change: 1 addition & 0 deletions config/presets/standalone.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func standalone() config.Config {
conf.POET.RequestRetryDelay = 1 * time.Second
conf.POET.MaxRequestRetries = 3
conf.POET.CertifierInfoCacheTTL = time.Minute
conf.POET.PowParamsCacheTTL = 10 * time.Second

conf.P2P.DisableNatPort = true

Expand Down
1 change: 1 addition & 0 deletions config/presets/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func testnet() config.Config {
MaxRequestRetries: 10,

CertifierInfoCacheTTL: 5 * time.Minute,
PowParamsCacheTTL: 5 * time.Minute,
},
POST: activation.PostConfig{
MinNumUnits: 2,
Expand Down

0 comments on commit 761b368

Please sign in to comment.