1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
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
}
}
|