package monitor import ( "context" "time" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/client" "github.com/google/certificate-transparency-go/jsonclient" "github.com/google/trillian/client/backoff" ) // backoffClient wraps client.LogClient so that we always backoff on get-entries // 4XX and 5XX. Backoff is on by default for get-sth already, and our silentct // usage is guaranteed to not do any hammering on any of the proof endpoints. // // For reference on this issue, see: // https://github.com/google/certificate-transparency-go/issues/898 // // Update: retries was added for get-sth and proof fetching. // Only because we need 3x queries that succeed in a row, and some logs seem to // rate limit globally for all endpoints rather than per endpoint. type backoffClient struct { cli *client.LogClient } func (bc *backoffClient) BaseURI() string { return bc.cli.BaseURI() } func (bc *backoffClient) GetSTH(ctx context.Context) (*ct.SignedTreeHead, error) { backoff := 1 for { rsp, err := bc.cli.GetSTH(ctx) if err == nil { return rsp, nil } jcErr, ok := err.(jsonclient.RspError) if !ok { return rsp, err } if jcErr.StatusCode != 429 { return rsp, err } if err := sleep(ctx, time.Duration(backoff)*time.Second); err != nil { return nil, err } if backoff < 32 { backoff = 2 * backoff } } } func (bc *backoffClient) GetSTHConsistency(ctx context.Context, first, second uint64) ([][]byte, error) { backoff := 1 for { rsp, err := bc.cli.GetSTHConsistency(ctx, first, second) if err == nil { return rsp, nil } jcErr, ok := err.(jsonclient.RspError) if !ok { return rsp, err } if jcErr.StatusCode != 429 { return rsp, err } if err := sleep(ctx, time.Duration(backoff)*time.Second); err != nil { return nil, err } if backoff < 32 { backoff = 2 * backoff } } } func (bc *backoffClient) GetProofByHash(ctx context.Context, hash []byte, treeSize uint64) (*ct.GetProofByHashResponse, error) { backoff := 1 for { rsp, err := bc.cli.GetProofByHash(ctx, hash, treeSize) if err == nil { return rsp, nil } jcErr, ok := err.(jsonclient.RspError) if !ok { return rsp, err } if jcErr.StatusCode != 429 { return rsp, err } if err := sleep(ctx, time.Duration(backoff)*time.Second); err != nil { return nil, err } if backoff < 32 { backoff = 2 * backoff } } } func (bc *backoffClient) GetRawEntries(ctx context.Context, start, end int64) (*ct.GetEntriesResponse, error) { rsp, err := bc.cli.GetRawEntries(ctx, start, end) if err != nil { jcErr, ok := err.(jsonclient.RspError) if !ok { return rsp, err } if jcErr.StatusCode < 400 || jcErr.StatusCode >= 600 { return rsp, err } // This ensures we never start hammering when the status code is 4XX or // 5XX. Probably not the right thing to do in all cases, but since the // download library we're using starts hammering if the log suddenly // serves something unexpected this seems like a good safety precaution. // Users of the silentct monitor eventually notice they get no entries. return rsp, backoff.RetriableErrorf("get-entries: %v", err) } return rsp, err } func sleep(ctx context.Context, d time.Duration) error { timer := time.NewTimer(d) defer timer.Stop() select { case <-ctx.Done(): return ctx.Err() case <-timer.C: return nil } }