Repository: google/certificate-transparency-go
Pull request #1454 (2024-05-01 22:30:51)

Add IssuanceChainCache LRU implementation
<!---
Describe your changes in detail here.
If this fixes an issue, please write "Fixes #123", substituting the issue number.
-->
This issuance chain cache LRU implementation contributes to https://github.com/google/certificate-transparency-go/issues/691.

### Checklist

<!---
Go over all the following points, and put an `x` in all the boxes that apply.
Feel free to not tick any boxes that don't apply to this PR (e.g. refactoring may not need a CHANGELOG update).
If you're unsure about any of these, don't hesitate to ask. We're here to help!
-->

- [ ] I have updated the [CHANGELOG](CHANGELOG.md).
  - Adjust the draft version number according to [semantic versioning](https://semver.org/) rules.
- [ ] I have updated [documentation](docs/) accordingly.



Patch:
diff --git a/go.mod b/go.mod
index 8e8439af..84ac1d45 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
 	github.com/google/go-cmp v0.6.0
 	github.com/google/trillian v1.6.0
 	github.com/gorilla/mux v1.8.1
+	github.com/hashicorp/golang-lru/v2 v2.0.7
 	github.com/kylelemons/godebug v1.1.0
 	github.com/mattn/go-sqlite3 v1.14.22
 	github.com/prometheus/client_golang v1.19.0
diff --git a/go.sum b/go.sum
index 50637d92..ae56e07c 100644
--- a/go.sum
+++ b/go.sum
@@ -140,6 +140,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
diff --git a/trillian/ctfe/cache/lru/lru.go b/trillian/ctfe/cache/lru/lru.go
new file mode 100644
index 00000000..400e8df8
--- /dev/null
+++ b/trillian/ctfe/cache/lru/lru.go
@@ -0,0 +1,51 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package lru defines the IssuanceChainCache type, which implements IssuanceChainCache interface with Get and Set operations.
+package lru
+
+import (
+	"context"
+	"time"
+
+	"github.com/hashicorp/golang-lru/v2/expirable"
+)
+
+type CacheOption struct {
+	Size int
+	TTL  time.Duration
+}
+
+type IssuanceChainCache struct {
+	opt   CacheOption
+	cache *expirable.LRU[string, []byte]
+}
+
+func NewIssuanceChainCache(opt CacheOption) *IssuanceChainCache {
+	cache := expirable.NewLRU[string, []byte](int(opt.Size), nil, opt.TTL)
+	return &IssuanceChainCache{
+		opt:   opt,
+		cache: cache,
+	}
+}
+
+func (c *IssuanceChainCache) Get(_ context.Context, key []byte) ([]byte, error) {
+	chain, _ := c.cache.Get(string(key))
+	return chain, nil
+}
+
+func (c *IssuanceChainCache) Set(_ context.Context, key []byte, chain []byte) error {
+	c.cache.Add(string(key), chain)
+	return nil
+}


Test patch:
diff --git a/trillian/ctfe/cache/lru/lru_test.go b/trillian/ctfe/cache/lru/lru_test.go
new file mode 100644
index 00000000..19e61567
--- /dev/null
+++ b/trillian/ctfe/cache/lru/lru_test.go
@@ -0,0 +1,68 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package lru
+
+import (
+	"bytes"
+	"context"
+	"crypto/sha256"
+	"os"
+	"testing"
+)
+
+func TestLRUIssuanceChainCache(t *testing.T) {
+	cacheSize := 3
+
+	tests := setupTestData(t,
+		"leaf00.chain",
+		"leaf01.chain",
+		"leaf02.chain",
+	)
+
+	cache := NewIssuanceChainCache(CacheOption{Size: cacheSize})
+
+	for key, val := range tests {
+		if err := cache.Set(context.Background(), []byte(key), val); err != nil {
+			t.Errorf("cache.Set: %v", err)
+		}
+	}
+
+	for key, want := range tests {
+		got, err := cache.Get(context.Background(), []byte(key))
+		if err != nil {
+			t.Errorf("cache.Get: %v", err)
+		}
+		if !bytes.Equal(got, want) {
+			t.Errorf("got: %v, want: %v", got, want)
+		}
+	}
+}
+
+func setupTestData(t *testing.T, filenames ...string) map[string][]byte {
+	t.Helper()
+
+	data := make(map[string][]byte, len(filenames))
+
+	for _, filename := range filenames {
+		val, err := os.ReadFile("../../../testdata/" + filename)
+		if err != nil {
+			t.Fatal(err)
+		}
+		key := sha256.Sum256(val)
+		data[string(key[:])] = val
+	}
+
+	return data
+}