Skip to content

Commit a34a039

Browse files
committed
Added ValidatorCache
Signed-off-by: Effi-S <effi.szt@gmail.com>
1 parent 5642052 commit a34a039

2 files changed

Lines changed: 122 additions & 1 deletion

File tree

token/core/zkatdlog/nogh/v1/validator/validator_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ import (
1414
"github.com/LFDT-Panurus/panurus/token/core/zkatdlog/nogh/v1/benchmark"
1515
"github.com/LFDT-Panurus/panurus/token/core/zkatdlog/nogh/v1/crypto/rp"
1616
testing2 "github.com/LFDT-Panurus/panurus/token/core/zkatdlog/nogh/v1/testutils"
17+
"github.com/LFDT-Panurus/panurus/token/core/zkatdlog/nogh/v1/validator"
1718
"github.com/LFDT-Panurus/panurus/token/driver"
19+
"github.com/LFDT-Panurus/panurus/token/driver/mock"
1820
benchmark2 "github.com/LFDT-Panurus/panurus/token/services/benchmark"
1921
"github.com/LFDT-Panurus/panurus/token/services/identity"
2022
"github.com/LFDT-Panurus/panurus/token/services/identity/idemix"
2123
"github.com/LFDT-Panurus/panurus/token/services/identity/idemixnym"
2224
"github.com/hyperledger-labs/fabric-smart-client/node/start/profile"
25+
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
2326
"github.com/stretchr/testify/require"
2427
)
2528

@@ -106,6 +109,83 @@ func TestValidator(t *testing.T) {
106109
}
107110
}
108111

112+
// TestVerifierCache verifies that VerifierCache deserializes each distinct owner
113+
// at most once (issue #2074): repeated lookups of the same owner reuse the
114+
// cached verifier, distinct owners are deserialized independently, and a
115+
// deserialization error is propagated without being cached so a later lookup
116+
// can retry.
117+
func TestVerifierCache(t *testing.T) {
118+
ctx := context.Background()
119+
ownerA := driver.Identity("owner-A")
120+
ownerB := driver.Identity("owner-B")
121+
122+
t.Run("same owner is deserialized only once", func(t *testing.T) {
123+
des := &mock.Deserializer{}
124+
verifier := &mock.Verifier{}
125+
des.GetOwnerVerifierReturns(verifier, nil)
126+
127+
cache := validator.NewVerifierCache(des)
128+
129+
first, err := cache.Get(ctx, ownerA)
130+
require.NoError(t, err)
131+
require.Same(t, verifier, first)
132+
133+
second, err := cache.Get(ctx, ownerA)
134+
require.NoError(t, err)
135+
require.Same(t, verifier, second)
136+
137+
require.Equal(t, 1, des.GetOwnerVerifierCallCount(), "owner should be deserialized exactly once")
138+
})
139+
140+
t.Run("distinct owners are deserialized separately", func(t *testing.T) {
141+
des := &mock.Deserializer{}
142+
verifiers := map[string]driver.Verifier{
143+
string(ownerA): &mock.Verifier{},
144+
string(ownerB): &mock.Verifier{},
145+
}
146+
des.GetOwnerVerifierStub = func(_ context.Context, id driver.Identity) (driver.Verifier, error) {
147+
return verifiers[string(id)], nil
148+
}
149+
150+
cache := validator.NewVerifierCache(des)
151+
152+
gotA, err := cache.Get(ctx, ownerA)
153+
require.NoError(t, err)
154+
require.Same(t, verifiers[string(ownerA)], gotA)
155+
156+
gotB, err := cache.Get(ctx, ownerB)
157+
require.NoError(t, err)
158+
require.Same(t, verifiers[string(ownerB)], gotB)
159+
160+
// re-fetch the first owner: it is served from the cache, not re-deserialized.
161+
gotAAgain, err := cache.Get(ctx, ownerA)
162+
require.NoError(t, err)
163+
require.Same(t, gotA, gotAAgain)
164+
165+
require.Equal(t, 2, des.GetOwnerVerifierCallCount(), "each distinct owner should be deserialized once")
166+
})
167+
168+
t.Run("deserialization error propagates and is not cached", func(t *testing.T) {
169+
des := &mock.Deserializer{}
170+
boom := errors.New("boom")
171+
verifier := &mock.Verifier{}
172+
des.GetOwnerVerifierReturnsOnCall(0, nil, boom)
173+
des.GetOwnerVerifierReturnsOnCall(1, verifier, nil)
174+
175+
cache := validator.NewVerifierCache(des)
176+
177+
_, err := cache.Get(ctx, ownerA)
178+
require.ErrorIs(t, err, boom)
179+
180+
// The failed lookup was not cached, so a subsequent call retries and succeeds.
181+
got, err := cache.Get(ctx, ownerA)
182+
require.NoError(t, err)
183+
require.Same(t, verifier, got)
184+
185+
require.Equal(t, 2, des.GetOwnerVerifierCallCount(), "a failed lookup must not be cached")
186+
})
187+
}
188+
109189
func BenchmarkValidatorTransfer(b *testing.B) {
110190
pp, err := profile.New(profile.WithAll(), profile.WithPath("./profile"))
111191
require.NoError(b, err)

token/core/zkatdlog/nogh/v1/validator/validator_transfer.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,45 @@ func TransferActionValidate(c context.Context, ctx *Context) error {
2929
return ctx.TransferAction.Validate()
3030
}
3131

32+
// VerifierCache memoizes owner signature verifiers for the lifetime of a single
33+
// transfer validation. deserializing an owner verifier is expensive so,
34+
//
35+
// # VerifierCache deserializes each distinct owner at most once, so the cost
36+
//
37+
// scales with the number of distinct owners rather than with the number of inputs.
38+
//
39+
// A VerifierCache is discarded when validation invocation ends,
40+
// so it holds no cross-transaction state
41+
type VerifierCache struct {
42+
deserializer driver.Deserializer
43+
cache map[string]driver.Verifier
44+
}
45+
46+
// NewVerifierCache returns an empty VerifierCache that deserializes owner
47+
// verifiers through the given deserializer.
48+
func NewVerifierCache(deserializer driver.Deserializer) *VerifierCache {
49+
return &VerifierCache{
50+
deserializer: deserializer,
51+
cache: make(map[string]driver.Verifier),
52+
}
53+
}
54+
55+
// Get returns the signature verifier for owner, deserializing it on first
56+
// request only.
57+
func (c *VerifierCache) Get(ctx context.Context, owner driver.Identity) (driver.Verifier, error) {
58+
key := string(owner)
59+
if verifier, ok := c.cache[key]; ok {
60+
return verifier, nil
61+
}
62+
verifier, err := c.deserializer.GetOwnerVerifier(ctx, owner)
63+
if err != nil {
64+
return nil, err
65+
}
66+
c.cache[key] = verifier
67+
68+
return verifier, nil
69+
}
70+
3271
// TransferSignatureValidate validates the signatures of the transfer action.
3372
// It assumes TransferActionValidate has been called first; however it also
3473
// performs its own nil guards so that it cannot panic even when called in
@@ -49,6 +88,8 @@ func TransferSignatureValidate(c context.Context, ctx *Context) error {
4988

5089
var isRedeem bool
5190
var inputToken []*token.Token
91+
92+
verifierCache := NewVerifierCache(ctx.Deserializer)
5293
for i, in := range ctx.TransferAction.Inputs {
5394
// Guard against a nil ActionInput or a nil Token inside it so that
5495
// this function cannot panic even if TransferActionValidate was skipped.
@@ -61,7 +102,7 @@ func TransferSignatureValidate(c context.Context, ctx *Context) error {
61102
// check sender signature
62103
uniqueID := driver.Identity(tok.Owner).UniqueID()
63104
ctx.Logger.Debugf("check sender [%d][%s]", i, uniqueID)
64-
verifier, err := ctx.Deserializer.GetOwnerVerifier(c, tok.Owner)
105+
verifier, err := verifierCache.Get(c, tok.Owner)
65106
if err != nil {
66107
return errors.Wrapf(err, "failed deserializing owner [%d][%s]", i, uniqueID)
67108
}

0 commit comments

Comments
 (0)