Skip to content

Commit 1eadc8c

Browse files
committed
Add MMD verification in CT Hammer
1 parent 15d70c7 commit 1eadc8c

File tree

7 files changed

+276
-40
lines changed

7 files changed

+276
-40
lines changed

deployment/modules/gcp/cloudbuild/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ resource "google_cloudbuild_trigger" "build_trigger" {
173173
--logtostderr \
174174
--num_writers=256 \
175175
--max_write_ops=256 \
176+
--num_mmd_verifiers=256 \
176177
--leaf_write_goal=10000
177178
EOT
178179
wait_for = ["bearer_token"]

internal/hammer/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ Example usage to test a deployment of `cmd/gcp`:
1717

1818
```shell
1919
go run ./internal/hammer \
20-
--log_public_key=test-static-ct+59739ea1+BTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGbaLj7T8pSEfEYL6nbF8U1xLjoy+dBkL5pINuSaTZ6DTW2WQ1bdZ4lO8ZuAcGLtSRESI01di5ZskWwgRwphuiY= \
20+
--log_public_key=MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZtouPtPylIR8RgvqdsXxTXEuOjL50GQvmkg25JpNnoNNbZZDVt1niU7xm4BwYu1JERIjTV2LlmyRbCBHCmG6Jg== \
2121
--log_url=https://storage.googleapis.com/transparency-dev-playground-test-static-ct-bucket \
2222
--write_log_url=http://localhost:6962/test-static-ct
2323
--max_read_ops=1024 \
2424
--num_readers_random=128 \
2525
--num_readers_full=128 \
2626
--num_writers=256 \
2727
--max_write_ops=42 \
28+
--num_mmd_verifiers=256 \
2829
--bearer_token=$(gcloud auth print-access-token)
2930
```
3031

@@ -34,12 +35,13 @@ If the timeout of 1 minute is reached first, then it exits with an exit code of
3435

3536
```shell
3637
go run ./internal/hammer \
37-
--log_public_key=test-static-ct+59739ea1+BTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGbaLj7T8pSEfEYL6nbF8U1xLjoy+dBkL5pINuSaTZ6DTW2WQ1bdZ4lO8ZuAcGLtSRESI01di5ZskWwgRwphuiY= \
38+
--log_public_key=MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZtouPtPylIR8RgvqdsXxTXEuOjL50GQvmkg25JpNnoNNbZZDVt1niU7xm4BwYu1JERIjTV2LlmyRbCBHCmG6Jg== \
3839
--log_url=https://storage.googleapis.com/transparency-dev-playground-test-static-ct-bucket \
3940
--write_log_url=http://localhost:6962/test-static-ct
4041
--max_read_ops=0 \
4142
--num_writers=512 \
4243
--max_write_ops=512 \
44+
--num_mmd_verifiers=512 \
4345
--max_runtime=1m \
4446
--leaf_write_goal=2500 \
4547
--bearer_token=$(gcloud auth print-access-token) \

internal/hammer/hammer.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var (
6565

6666
maxWriteOpsPerSecond = flag.Int("max_write_ops", 0, "The maximum number of write operations per second")
6767
numWriters = flag.Int("num_writers", 0, "The number of independent write tasks to run")
68+
numMMDVerifiers = flag.Int("num_mmd_verifiers", 0, "The number of MMD verifiers performing inclusion proof for the added leaves")
6869

6970
dupChance = flag.Float64("dup_chance", 0.1, "The probability of a generated leaf being a duplicate of a previous value")
7071

@@ -160,6 +161,7 @@ func main() {
160161
NumReadersRandom: *numReadersRandom,
161162
NumReadersFull: *numReadersFull,
162163
NumWriters: *numWriters,
164+
NumMMDVerifiers: *numMMDVerifiers,
163165
}
164166
hammer := loadtest.NewHammer(&tracker, f.ReadEntryBundle, w, gen, ha.SeqLeafChan, ha.ErrChan, opts)
165167

internal/hammer/loadtest/client.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -155,42 +155,42 @@ type httpLeafWriter struct {
155155
bearerToken string
156156
}
157157

158-
func (w httpLeafWriter) Write(ctx context.Context, newLeaf []byte) (uint64, error) {
158+
func (w httpLeafWriter) Write(ctx context.Context, newLeaf []byte) (uint64, uint64, error) {
159159
req, err := http.NewRequest(http.MethodPost, w.u.String(), bytes.NewReader(newLeaf))
160160
if err != nil {
161-
return 0, fmt.Errorf("failed to create request: %v", err)
161+
return 0, 0, fmt.Errorf("failed to create request: %v", err)
162162
}
163163
if w.bearerToken != "" {
164164
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", w.bearerToken))
165165
}
166166
resp, err := w.hc.Do(req.WithContext(ctx))
167167
if err != nil {
168-
return 0, fmt.Errorf("failed to write leaf: %v", err)
168+
return 0, 0, fmt.Errorf("failed to write leaf: %v", err)
169169
}
170170
body, err := io.ReadAll(resp.Body)
171171
_ = resp.Body.Close()
172172
if err != nil {
173-
return 0, fmt.Errorf("failed to read body: %v", err)
173+
return 0, 0, fmt.Errorf("failed to read body: %v", err)
174174
}
175175
switch resp.StatusCode {
176176
case http.StatusOK:
177177
if resp.Request.Method != http.MethodPost {
178-
return 0, fmt.Errorf("write leaf was redirected to %s", resp.Request.URL)
178+
return 0, 0, fmt.Errorf("write leaf was redirected to %s", resp.Request.URL)
179179
}
180180
// Continue below
181181
case http.StatusServiceUnavailable, http.StatusBadGateway, http.StatusGatewayTimeout:
182182
// These status codes may indicate a delay before retrying, so handle that here:
183183
time.Sleep(retryDelay(resp.Header.Get("RetryAfter"), time.Second))
184184

185-
return 0, fmt.Errorf("log not available. Status code: %d. Body: %q %w", resp.StatusCode, body, ErrRetry)
185+
return 0, 0, fmt.Errorf("log not available. Status code: %d. Body: %q %w", resp.StatusCode, body, ErrRetry)
186186
default:
187-
return 0, fmt.Errorf("write leaf was not OK. Status code: %d. Body: %q", resp.StatusCode, body)
187+
return 0, 0, fmt.Errorf("write leaf was not OK. Status code: %d. Body: %q", resp.StatusCode, body)
188188
}
189-
index, err := parseAddChainResponse(body)
189+
index, timestamp, err := parseAddChainResponse(body)
190190
if err != nil {
191-
return 0, fmt.Errorf("write leaf failed to parse response: %v", body)
191+
return 0, 0, fmt.Errorf("write leaf failed to parse response: %v", body)
192192
}
193-
return index, nil
193+
return index, timestamp, nil
194194
}
195195

196196
func retryDelay(retryAfter string, defaultDur time.Duration) time.Duration {
@@ -216,7 +216,7 @@ type roundRobinLeafWriter struct {
216216
ws []httpLeafWriter
217217
}
218218

219-
func (rr *roundRobinLeafWriter) Write(ctx context.Context, newLeaf []byte) (uint64, error) {
219+
func (rr *roundRobinLeafWriter) Write(ctx context.Context, newLeaf []byte) (uint64, uint64, error) {
220220
w := rr.next()
221221
return w(ctx, newLeaf)
222222
}
@@ -232,39 +232,39 @@ func (rr *roundRobinLeafWriter) next() LeafWriter {
232232
}
233233

234234
// parseAddChainResponse parses the add-chain response and returns the leaf
235-
// index from the extensions.
235+
// index from the extensions and timestamp from the response.
236236
// Code is inspired by https://github.com/FiloSottile/sunlight/blob/main/tile.go.
237-
func parseAddChainResponse(body []byte) (uint64, error) {
237+
func parseAddChainResponse(body []byte) (uint64, uint64, error) {
238238
var resp types.AddChainResponse
239239
if err := json.Unmarshal(body, &resp); err != nil {
240-
return 0, fmt.Errorf("can't parse add-chain response: %v", err)
240+
return 0, 0, fmt.Errorf("can't parse add-chain response: %v", err)
241241
}
242242

243243
extensionBytes, err := base64.StdEncoding.DecodeString(resp.Extensions)
244244
if err != nil {
245-
return 0, fmt.Errorf("can't decode extensions: %v", err)
245+
return 0, 0, fmt.Errorf("can't decode extensions: %v", err)
246246
}
247247
extensions := cryptobyte.String(extensionBytes)
248248
var extensionType uint8
249249
var extensionData cryptobyte.String
250250
var leafIdx int64
251251
if !extensions.ReadUint8(&extensionType) {
252-
return 0, fmt.Errorf("can't read extension type")
252+
return 0, 0, fmt.Errorf("can't read extension type")
253253
}
254254
if extensionType != 0 {
255-
return 0, fmt.Errorf("wrong extension type %d, want 0", extensionType)
255+
return 0, 0, fmt.Errorf("wrong extension type %d, want 0", extensionType)
256256
}
257257
if !extensions.ReadUint16LengthPrefixed(&extensionData) {
258-
return 0, fmt.Errorf("can't read extension data")
258+
return 0, 0, fmt.Errorf("can't read extension data")
259259
}
260260
if !readUint40(&extensionData, &leafIdx) {
261-
return 0, fmt.Errorf("can't read leaf index from extension")
261+
return 0, 0, fmt.Errorf("can't read leaf index from extension")
262262
}
263263
if !extensionData.Empty() ||
264264
!extensions.Empty() {
265-
return 0, fmt.Errorf("invalid data tile extensions: %v", resp.Extensions)
265+
return 0, 0, fmt.Errorf("invalid data tile extensions: %v", resp.Extensions)
266266
}
267-
return uint64(leafIdx), nil
267+
return uint64(leafIdx), resp.Timestamp, nil
268268
}
269269

270270
// readUint40 decodes a big-endian, 40-bit value into out and advances over it.

internal/hammer/loadtest/hammer.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,34 @@ type HammerOpts struct {
3131
NumReadersRandom int
3232
NumReadersFull int
3333
NumWriters int
34+
NumMMDVerifiers int
3435
}
3536

3637
func NewHammer(tracker *client.LogStateTracker, f client.EntryBundleFetcherFunc, w LeafWriter, gen func() []byte, seqLeafChan chan<- LeafTime, errChan chan<- error, opts HammerOpts) *Hammer {
3738
readThrottle := NewThrottle(opts.MaxReadOpsPerSecond)
3839
writeThrottle := NewThrottle(opts.MaxWriteOpsPerSecond)
3940

41+
leafMMDChan := make(chan LeafMMD, opts.NumWriters)
42+
4043
randomReaders := NewWorkerPool(func() Worker {
4144
return NewLeafReader(tracker, f, RandomNextLeaf(), readThrottle.TokenChan, errChan)
4245
})
4346
fullReaders := NewWorkerPool(func() Worker {
4447
return NewLeafReader(tracker, f, MonotonicallyIncreasingNextLeaf(), readThrottle.TokenChan, errChan)
4548
})
4649
writers := NewWorkerPool(func() Worker {
47-
return NewLogWriter(w, gen, writeThrottle.TokenChan, errChan, seqLeafChan)
50+
return NewLogWriter(w, gen, writeThrottle.TokenChan, errChan, seqLeafChan, leafMMDChan)
51+
})
52+
mmdVerifiers := NewWorkerPool(func() Worker {
53+
return NewMMDVerifier(tracker, errChan, leafMMDChan)
4854
})
4955

5056
return &Hammer{
5157
opts: opts,
5258
randomReaders: randomReaders,
5359
fullReaders: fullReaders,
5460
writers: writers,
61+
mmdVerifiers: mmdVerifiers,
5562
readThrottle: readThrottle,
5663
writeThrottle: writeThrottle,
5764
tracker: tracker,
@@ -66,6 +73,7 @@ type Hammer struct {
6673
randomReaders WorkerPool
6774
fullReaders WorkerPool
6875
writers WorkerPool
76+
mmdVerifiers WorkerPool
6977
readThrottle *Throttle
7078
writeThrottle *Throttle
7179
tracker *client.LogStateTracker
@@ -82,6 +90,9 @@ func (h *Hammer) Run(ctx context.Context) {
8290
for i := 0; i < h.opts.NumWriters; i++ {
8391
h.writers.Grow(ctx)
8492
}
93+
for i := 0; i < h.opts.NumMMDVerifiers; i++ {
94+
h.mmdVerifiers.Grow(ctx)
95+
}
8596

8697
go h.readThrottle.Run(ctx)
8798
go h.writeThrottle.Run(ctx)

internal/hammer/loadtest/tui.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,13 @@ func (c *tuiController) Run(ctx context.Context) {
9898
c.hammer.randomReaders.Grow(ctx)
9999
c.hammer.fullReaders.Grow(ctx)
100100
c.hammer.writers.Grow(ctx)
101+
c.hammer.mmdVerifiers.Grow(ctx)
101102
case 'W':
102103
klog.Info("Decreasing the number of workers")
103104
c.hammer.randomReaders.Shrink(ctx)
104105
c.hammer.fullReaders.Shrink(ctx)
105106
c.hammer.writers.Shrink(ctx)
107+
c.hammer.mmdVerifiers.Shrink(ctx)
106108
}
107109
return event
108110
})

0 commit comments

Comments
 (0)