Skip to content

Commit db87c51

Browse files
authored
Add multithreading test for ProofBuilder (#926)
1 parent 8c2439a commit db87c51

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

client/proofbuilder_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2026 Google LLC. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package client_test is for client tests which have deps which would otherwise cause an import cycle.
16+
package client_test
17+
18+
import (
19+
"fmt"
20+
"math/rand/v2"
21+
"testing"
22+
"time"
23+
24+
"github.com/transparency-dev/tessera"
25+
"github.com/transparency-dev/tessera/client"
26+
"github.com/transparency-dev/tessera/testonly"
27+
"golang.org/x/sync/errgroup"
28+
)
29+
30+
func TestProofBuilderIsThreadsafe(t *testing.T) {
31+
ctx := t.Context()
32+
33+
// First, create a test log large enough to have a bunch of tiles/entrybundles.
34+
treeSize := uint64(2468)
35+
36+
l, done := testonly.NewTestLog(t, tessera.NewAppendOptions().WithBatching(1024, 100*time.Millisecond).WithCheckpointInterval(100*time.Millisecond))
37+
defer func() {
38+
_ = done(ctx)
39+
}()
40+
41+
var f tessera.IndexFuture
42+
t.Logf("Growing log to %d entries", treeSize)
43+
for i := range treeSize {
44+
f = l.Appender.Add(ctx, tessera.NewEntry(fmt.Appendf(nil, "Entry %d", i)))
45+
}
46+
t.Logf("Awaiting integration")
47+
if _, _, err := tessera.NewPublicationAwaiter(ctx, l.LogReader.ReadCheckpoint, 100*time.Millisecond).Await(ctx, f); err != nil {
48+
t.Fatalf("Failed while awaiting: %v", err)
49+
}
50+
51+
// Create a single ProofBuilder which we'll use below, shared between a number of concurrent goroutines.
52+
pb, err := client.NewProofBuilder(ctx, treeSize, l.LogReader.ReadTile)
53+
if err != nil {
54+
t.Fatalf("NewProofBuilder: %v", err)
55+
}
56+
57+
// Finally, spin up a bunch of goroutines asking for consistency and inclusion proofs from the same single ProofBuilder instance.
58+
t.Logf("Exercising proof builder")
59+
concurrency := 512
60+
61+
var wg errgroup.Group
62+
for range concurrency {
63+
wg.Go(func() error {
64+
leafIndex := uint64(1 + rand.IntN(int(treeSize-1)))
65+
if _, err := pb.InclusionProof(ctx, leafIndex); err != nil {
66+
return fmt.Errorf("failure calling InclusionProof(%d): %v", leafIndex, err)
67+
}
68+
69+
smaller := uint64(1 + rand.IntN(int(treeSize-1)))
70+
if _, err := pb.ConsistencyProof(ctx, smaller, treeSize); err != nil {
71+
return fmt.Errorf("failure calling ConsistencyProof(%d, %d): %v", smaller, treeSize, err)
72+
}
73+
return nil
74+
})
75+
}
76+
77+
if err := wg.Wait(); err != nil {
78+
t.Errorf("Concurrent request failed: %v", err)
79+
}
80+
}

0 commit comments

Comments
 (0)