From 334d6cc24d14d6184c221f10bee80af07b2d10aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 1 Aug 2024 17:02:03 +0200 Subject: [PATCH] [backport] poet caching opts (#6201) * configure poet /v1/info cache ttl in presets (#6198) ## Motivation For the cache to work, it needs to have non-zero TTL set * cache poet's /v1/pow_params with TTL (#6199) ## 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. * update changelog --- CHANGELOG.md | 7 ++++ activation/activation.go | 2 + activation/poet.go | 75 ++++++++++++++++++++++------------ activation/poet_client_test.go | 34 +++++++++++++++ config/mainnet.go | 2 + config/presets/fastnet.go | 3 ++ config/presets/standalone.go | 2 + config/presets/testnet.go | 3 ++ 8 files changed, 102 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feaa82d683..08b3b0099c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ See [RELEASE](./RELEASE.md) for workflow instructions. +## Release v1.6.6 + +### Improvements + +* [#6198](https://github.com/spacemeshos/go-spacemesh/pull/6198) Configure default TTL for caching poet's /v1/info +* [#6199](https://github.com/spacemeshos/go-spacemesh/pull/6199) Cache poet's /v1/pow_params + ## Release v1.6.5 ### Improvements diff --git a/activation/activation.go b/activation/activation.go index 227f5464bf..2a31d02f98 100644 --- a/activation/activation.go +++ b/activation/activation.go @@ -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"` } @@ -54,6 +55,7 @@ func DefaultPoetConfig() PoetConfig { RequestRetryDelay: 400 * time.Millisecond, MaxRequestRetries: 10, CertifierInfoCacheTTL: 5 * time.Minute, + PowParamsCacheTTL: 5 * time.Minute, } } diff --git a/activation/poet.go b/activation/poet.go index 829c6bbfea..46b124ceb3 100644 --- a/activation/poet.go +++ b/activation/poet.go @@ -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. @@ -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) @@ -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 { @@ -435,7 +455,7 @@ func (c *poetService) authorize( logger.Debug("querying for poet pow parameters") 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} } @@ -552,19 +572,22 @@ func (c *poetService) recertify(ctx context.Context, id types.NodeID) (*certifie } 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) + }) } diff --git a/activation/poet_client_test.go b/activation/poet_client_test.go index 25a6b7603e..3c67f828db 100644 --- a/activation/poet_client_test.go +++ b/activation/poet_client_test.go @@ -421,3 +421,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(¶ms, 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) + } + }) + } +} diff --git a/config/mainnet.go b/config/mainnet.go index 8bd0effac4..79d2f08041 100644 --- a/config/mainnet.go +++ b/config/mainnet.go @@ -163,6 +163,8 @@ func MainnetConfig() Config { CycleGap: 12 * time.Hour, 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, diff --git a/config/presets/fastnet.go b/config/presets/fastnet.go index de09ac0d19..e616057ac0 100644 --- a/config/presets/fastnet.go +++ b/config/presets/fastnet.go @@ -97,5 +97,8 @@ func fastnet() config.Config { conf.POET.RequestTimeout = 12 * time.Second // RequestRetryDelay * 2 * MaxRequestRetries*(MaxRequestRetries+1)/2 conf.POET.RequestRetryDelay = 1 * time.Second conf.POET.MaxRequestRetries = 3 + conf.POET.CertifierInfoCacheTTL = time.Minute + conf.POET.PowParamsCacheTTL = 10 * time.Second + return conf } diff --git a/config/presets/standalone.go b/config/presets/standalone.go index 290b615971..899b1d48ba 100644 --- a/config/presets/standalone.go +++ b/config/presets/standalone.go @@ -83,6 +83,8 @@ func standalone() config.Config { conf.POET.RequestTimeout = 12 * time.Second // RequestRetryDelay * 2 * MaxRequestRetries*(MaxRequestRetries+1)/2 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 diff --git a/config/presets/testnet.go b/config/presets/testnet.go index 892d924aaf..bca7188fef 100644 --- a/config/presets/testnet.go +++ b/config/presets/testnet.go @@ -118,6 +118,9 @@ func testnet() config.Config { RequestTimeout: 550 * time.Second, // RequestRetryDelay * 2 * MaxRequestRetries*(MaxRequestRetries+1)/2 RequestRetryDelay: 5 * time.Second, MaxRequestRetries: 10, + + CertifierInfoCacheTTL: 5 * time.Minute, + PowParamsCacheTTL: 5 * time.Minute, }, POST: activation.PostConfig{ MinNumUnits: 2,