diff --git a/client/client.go b/client/client.go index 54b4fdcf6..cc212ff14 100644 --- a/client/client.go +++ b/client/client.go @@ -19,7 +19,6 @@ package client import ( - "bytes" "context" "errors" "fmt" @@ -181,49 +180,17 @@ func GetEntryBundle(ctx context.Context, f EntryBundleFetcherFunc, i, logSize ui // Since the tiles commit only to immutable nodes, the job of building proofs is slightly // more complex as proofs can touch "ephemeral" nodes, so these need to be synthesized. type ProofBuilder struct { - cp log.Checkpoint + treeSize uint64 nodeCache nodeCache } // NewProofBuilder creates a new ProofBuilder object for a given tree size. // The returned ProofBuilder can be re-used for proofs related to a given tree size, but // it is not thread-safe and should not be accessed concurrently. -func NewProofBuilder(ctx context.Context, cp log.Checkpoint, f TileFetcherFunc) (*ProofBuilder, error) { - ctx, span := tracer.Start(ctx, "tessera.client.NewProofBuilder") - defer span.End() - - span.SetAttributes(logSizeKey.Int64(otel.Clamp64(cp.Size))) - +func NewProofBuilder(ctx context.Context, treeSize uint64, f TileFetcherFunc) (*ProofBuilder, error) { pb := &ProofBuilder{ - cp: cp, - nodeCache: newNodeCache(f, cp.Size), - } - // Can't re-create the root of a zero size checkpoint other than by convention, - // so return early here in that case. - if cp.Size == 0 { - return pb, nil - } - - hashes, err := FetchRangeNodes(ctx, cp.Size, f) - if err != nil { - return nil, fmt.Errorf("failed to fetch range nodes: %v", err) - } - // Create a compact range which represents the state of the log. - r, err := (&compact.RangeFactory{Hash: hasher.HashChildren}).NewRange(0, cp.Size, hashes) - if err != nil { - return nil, err - } - - // Recreate the root hash so that: - // a) we validate the self-integrity of the log state, and - // b) we calculate (and cache) and ephemeral nodes present in the tree, - // this is important since they could be required by proofs. - sr, err := r.GetRootHash(pb.nodeCache.SetEphemeralNode) - if err != nil { - return nil, err - } - if !bytes.Equal(cp.Hash, sr) { - return nil, fmt.Errorf("invalid checkpoint hash %x, expected %x", cp.Hash, sr) + treeSize: treeSize, + nodeCache: newNodeCache(f, treeSize), } return pb, nil } @@ -238,22 +205,25 @@ func (pb *ProofBuilder) InclusionProof(ctx context.Context, index uint64) ([][]b span.SetAttributes(indexKey.Int64(otel.Clamp64(index))) - nodes, err := proof.Inclusion(index, pb.cp.Size) + nodes, err := proof.Inclusion(index, pb.treeSize) if err != nil { return nil, fmt.Errorf("failed to calculate inclusion proof node list: %v", err) } return pb.fetchNodes(ctx, nodes) } -// ConsistencyProof constructs a consistency proof between the two passed in tree sizes. +// ConsistencyProof constructs a consistency proof between the provided tree sizes. // This function uses the passed-in function to retrieve tiles containing any log tree // nodes necessary to build the proof. func (pb *ProofBuilder) ConsistencyProof(ctx context.Context, smaller, larger uint64) ([][]byte, error) { ctx, span := tracer.Start(ctx, "tessera.client.ConsistencyProof") defer span.End() - span.SetAttributes(smallerKey.Int64(otel.Clamp64(smaller)), largerKey.Int64(otel.Clamp64(larger))) + if m := max(smaller, larger); m > pb.treeSize { + return nil, fmt.Errorf("requested consistency proof to %d which is larger than tree size %d", m, pb.treeSize) + } + nodes, err := proof.Consistency(smaller, larger) if err != nil { return nil, fmt.Errorf("failed to calculate consistency proof node list: %v", err) @@ -318,7 +288,7 @@ func NewLogStateTracker(ctx context.Context, tF TileFetcherFunc, checkpointRaw [ return ret, err } ret.latestConsistent = *cp - ret.proofBuilder, err = NewProofBuilder(ctx, ret.latestConsistent, ret.tileFetcher) + ret.proofBuilder, err = NewProofBuilder(ctx, ret.latestConsistent.Size, ret.tileFetcher) if err != nil { return ret, fmt.Errorf("NewProofBuilder: %v", err) } @@ -343,7 +313,7 @@ func (lst *LogStateTracker) Update(ctx context.Context) ([]byte, [][]byte, []byt if err != nil { return nil, nil, nil, err } - builder, err := NewProofBuilder(ctx, *c, lst.tileFetcher) + builder, err := NewProofBuilder(ctx, c.Size, lst.tileFetcher) if err != nil { return nil, nil, nil, fmt.Errorf("failed to create proof builder: %v", err) } diff --git a/client/client_test.go b/client/client_test.go index 5ae30aaa6..50f99bf84 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -229,7 +229,7 @@ func TestHandleZeroRoot(t *testing.T) { if len(zeroCP.Hash) == 0 { t.Fatal("BadTestData: checkpoint.0 has empty root hash") } - if _, err := NewProofBuilder(context.Background(), zeroCP, testLogTileFetcher); err != nil { + if _, err := NewProofBuilder(context.Background(), zeroCP.Size, testLogTileFetcher); err != nil { t.Fatalf("NewProofBuilder: %v", err) } } diff --git a/integration/integration_test.go b/integration/integration_test.go index 66cfd10fd..64151a3bb 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -177,7 +177,7 @@ func TestLiveLogIntegration(t *testing.T) { } // Step 4.2 - Test inclusion proofs. - pb, err := client.NewProofBuilder(ctx, lst.Latest(), logReadTile) + pb, err := client.NewProofBuilder(ctx, lst.Latest().Size, logReadTile) if err != nil { t.Errorf("client.NewProofBuilder: %v", err) } diff --git a/internal/witness/witness.go b/internal/witness/witness.go index 159dfebde..a3a5ec5ce 100644 --- a/internal/witness/witness.go +++ b/internal/witness/witness.go @@ -106,7 +106,7 @@ func (wg *WitnessGateway) Witness(ctx context.Context, cp []byte) ([]byte, error Size: size, Hash: hash, } - pb, err := client.NewProofBuilder(ctx, logCP, wg.fetchTile) + pb, err := client.NewProofBuilder(ctx, logCP.Size, wg.fetchTile) if err != nil { return nil, fmt.Errorf("failed to build proof builder: %v", err) }