diff --git a/Makefile b/Makefile index d803d3afa1..4e341a6c38 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,10 @@ license: unit-test: mocks @scripts/check_unit.sh +.PHONY: benchmark +benchmark: + @scripts/check_bench.sh + .PHONY: unit-test-wasm unit-test-wasm: export GOBIN=$(GOBIN_PATH) unit-test-wasm: depend diff --git a/go.sum b/go.sum index f4705c0d67..7f6ca89b7f 100644 --- a/go.sum +++ b/go.sum @@ -176,6 +176,7 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKe github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210224230531-58e1368e5661 h1:VmyvcZ2fMyrE7UCXPSAsMwjKOm5GlpxJSb8sWsTkeQY= github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210224230531-58e1368e5661/go.mod h1:XaPVDJcbQT8BKmThfQdWPc+hgicHFAQzSOavHw2gn/4= +github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210308190853-e0d06c12b672 h1:ANpcYtXSEMj++pomDnVhLQkQtaIXdHr0IVASVZadGB0= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210205153949-f852f978a0d6/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210210184327-0b9d0fd4c34e/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210224230531-58e1368e5661 h1:Je5vn+j5wYAC8+uuL6ZabK1VEPX6/fdgNlMA8dJjCFI= diff --git a/pkg/doc/did/doc.go b/pkg/doc/did/doc.go index b01a9b535a..59e8a47fe8 100644 --- a/pkg/doc/did/doc.go +++ b/pkg/doc/did/doc.go @@ -1322,7 +1322,7 @@ func BuildDoc(opts ...DocOption) *Doc { // CachingJSONLDLoader creates JSON-LD CachingDocumentLoader with preloaded base JSON-LD DID and security contexts. func CachingJSONLDLoader() ld.DocumentLoader { - loader := jld.NewCachingDocumentLoader() + loader := jld.NewDefaultCachingDocumentLoader() cacheContext := func(source, url string) { reader, _ := ld.DocumentFromReader(strings.NewReader(source)) //nolint:errcheck diff --git a/pkg/doc/jsonld/document_loader.go b/pkg/doc/jsonld/document_loader.go new file mode 100644 index 0000000000..108c438ddb --- /dev/null +++ b/pkg/doc/jsonld/document_loader.go @@ -0,0 +1,71 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package jsonld + +import ( + "errors" + "sync" + + "github.com/piprate/json-gold/ld" +) + +// CachingDocumentLoader is similar to json-gold's CachingDocumentLoader but uses cache as *sync.Map instead. +type CachingDocumentLoader struct { + nextLoader ld.DocumentLoader + cache map[string]interface{} + rwMutex sync.RWMutex +} + +// NewCachingDocLoader creates a new instance of CachingDocumentLoader. +func NewCachingDocLoader(nextLoader ld.DocumentLoader) *CachingDocumentLoader { + cdl := &CachingDocumentLoader{ + nextLoader: nextLoader, + cache: map[string]interface{}{}, + } + + return cdl +} + +// LoadDocument returns a RemoteDocument containing the contents of the JSON resource from the given URL (u). +func (cdl *CachingDocumentLoader) LoadDocument(u string) (*ld.RemoteDocument, error) { + cdl.rwMutex.RLock() + if doc, cached := cdl.cache[u]; cached { + defer cdl.rwMutex.RUnlock() + + if cachedDoc, ok := doc.(*ld.RemoteDocument); ok { + return cachedDoc, nil + } + + return nil, errors.New("invalid document entry") + } + + cdl.rwMutex.RUnlock() + + docFromLoader, err := cdl.nextLoader.LoadDocument(u) + if err != nil { + return nil, err + } + + cdl.rwMutex.Lock() + if _, cached := cdl.cache[u]; !cached { + cdl.cache[u] = docFromLoader + } + + cdl.rwMutex.Unlock() + + return docFromLoader, nil +} + +// AddDocument populates the cache with the given document (doc) for the provided URL (u). +func (cdl *CachingDocumentLoader) AddDocument(u string, doc interface{}) { + cdl.rwMutex.Lock() + if _, cached := cdl.cache[u]; !cached { + cdl.cache[u] = &ld.RemoteDocument{DocumentURL: u, Document: doc, ContextURL: ""} + } + + cdl.rwMutex.Unlock() +} diff --git a/pkg/doc/jsonld/document_loader_test.go b/pkg/doc/jsonld/document_loader_test.go new file mode 100644 index 0000000000..903f31c60b --- /dev/null +++ b/pkg/doc/jsonld/document_loader_test.go @@ -0,0 +1,52 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package jsonld + +import ( + "testing" + + "github.com/piprate/json-gold/ld" + "github.com/stretchr/testify/require" +) + +func TestNewCachingDocLoader(t *testing.T) { + u := "https://www.w3.org/2018/credentials/v1" + loader := NewCachingDocLoader(ld.NewRFC7324CachingDocumentLoader(httpclient())) + _, err := loader.LoadDocument(u) + require.Error(t, err, "network should be disabled") + + loader.AddDocument(u, jsonVCWithProperContexts) + + expectedDoc := &ld.RemoteDocument{ + DocumentURL: "https://www.w3.org/2018/credentials/v1", + Document: jsonVCWithProperContexts, + ContextURL: "", + } + + doc, err := loader.LoadDocument(u) + require.NoError(t, err) + require.EqualValues(t, expectedDoc, doc) +} + +// nolint +const jsonVCWithProperContexts = `{ + "@context": "https://w3id.org/security/v2", + "id": "http://www.example.org/foo/documents/a3480d17-df7f-449f-9480-e2c35e20a865", + "allowedAction": ["read", "write"], + "invocationTarget": { + "ID": "http://www.example.org/foo/documents/a3480d17-df7f-449f-9480-e2c35e20a865", + "Type": "urn:edv:document" + }, + "proof": [{ + "created": "2020-12-04T15:28:14.673975717-05:00", + "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6OfIULug35ZmoU7lysChVpD6sjYfV71UwxqIZ8u0woYSIzRtzCo3MsZJw6cGIZMEaMssnQyRqIzo8B0yHEL2Dw", + "nonce": "da7CcJahAdFG0GXN-JnS2f2mywcFNtaLyXtGVqku2DwVwUaJbGpUQjhlNi5kDbS4ZMi2cNhEN5ac6LponS-C9w", + "proofPurpose": "capabilityDelegation", + "type": "Ed25519Signature2018", + "verificationMethod": "did:key:z6MkmkFTTczYKzU94t45sG65iZi2HA21tAU9ns8bXSmBEap4#z6MkmkFTTczYKzU94t45sG65iZi2HA21tAU9ns8bXSmBEap4" + }] +}` diff --git a/pkg/doc/jsonld/jsonld.go b/pkg/doc/jsonld/jsonld.go index c491310f66..ffa0c75324 100644 --- a/pkg/doc/jsonld/jsonld.go +++ b/pkg/doc/jsonld/jsonld.go @@ -14,9 +14,9 @@ import ( "github.com/piprate/json-gold/ld" ) -// NewCachingDocumentLoader creates a Document Loader with default framework options. -func NewCachingDocumentLoader() *ld.CachingDocumentLoader { - return ld.NewCachingDocumentLoader(ld.NewRFC7324CachingDocumentLoader(httpclient())) +// NewDefaultCachingDocumentLoader creates a Document Loader with default framework options. +func NewDefaultCachingDocumentLoader() *CachingDocumentLoader { + return NewCachingDocLoader(ld.NewRFC7324CachingDocumentLoader(httpclient())) } // NewCachingDocumentLoaderWithRemote creates a Document Loader with remote enabled. diff --git a/pkg/doc/jsonld/jsonld_test.go b/pkg/doc/jsonld/jsonld_test.go index 1c6d64e696..6cdd588508 100644 --- a/pkg/doc/jsonld/jsonld_test.go +++ b/pkg/doc/jsonld/jsonld_test.go @@ -13,7 +13,10 @@ import ( ) func TestNewVerifier(t *testing.T) { - loader := NewCachingDocumentLoader() + loader := NewDefaultCachingDocumentLoader() _, err := loader.LoadDocument("https://www.w3.org/2018/credentials/v1") require.Error(t, err, "network should be disabled") + + rLoader := NewCachingDocumentLoaderWithRemote() + require.NotEmpty(t, rLoader) } diff --git a/pkg/doc/signature/jsonld/processor.go b/pkg/doc/signature/jsonld/processor.go index 241d8acbe9..1e124a4281 100644 --- a/pkg/doc/signature/jsonld/processor.go +++ b/pkg/doc/signature/jsonld/processor.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "strings" + "sync" "github.com/google/uuid" "github.com/piprate/json-gold/ld" @@ -38,7 +39,7 @@ type processorOpts struct { validateRDF bool documentLoader ld.DocumentLoader externalContexts []string - documentLoaderCache map[string]interface{} + documentLoaderCache *sync.Map } // ProcessorOpts are the options for JSON LD operations on docs (like canonicalization or compacting). @@ -68,15 +69,17 @@ func WithDocumentLoader(loader ld.DocumentLoader) ProcessorOpts { // WithDocumentLoaderCache option is for passing cached contexts to be used by JSON-LD context document loader. // Supported value types: map[string]interface{}, string, []byte, io.Reader. +// Note: Most io.Reader implementations (eg: strings.NewReader()) contain a non thread-safe buffer. Make sure to use +// one that is thread-safe. func WithDocumentLoaderCache(cache map[string]interface{}) ProcessorOpts { return func(opts *processorOpts) { if opts.documentLoaderCache == nil { - opts.documentLoaderCache = make(map[string]interface{}) + opts.documentLoaderCache = &sync.Map{} } for k, v := range cache { if cacheValue := getDocumentCacheValue(v); cacheValue != nil { - opts.documentLoaderCache[k] = cacheValue + opts.documentLoaderCache.Store(k, cacheValue) } } } @@ -506,34 +509,43 @@ func (p *Processor) normalizeFilteredDataset(view string) (string, error) { return result.(string), nil } -func useDocumentLoader(ldOptions *ld.JsonLdOptions, loader ld.DocumentLoader, cache map[string]interface{}) { - if loader == nil && len(cache) == 0 { +func useDocumentLoader(ldOptions *ld.JsonLdOptions, loader ld.DocumentLoader, cache *sync.Map) { + if loader == nil && cache == nil { return } ldOptions.DocumentLoader = getCachingDocumentLoader(loader, cache) } -func getCachingDocumentLoader(loader ld.DocumentLoader, cache map[string]interface{}) *ld.CachingDocumentLoader { +func getCachingDocumentLoader(loader ld.DocumentLoader, cache *sync.Map) *jsonld.CachingDocumentLoader { cachingLoader := createCachingDocumentLoader(loader) - for k, v := range cache { - cachingLoader.AddDocument(k, v) + if cache != nil { + cache.Range(func(key, value interface{}) bool { + k, ok := key.(string) + if !ok { + return false + } + + cachingLoader.AddDocument(k, value) + + return true + }) } return cachingLoader } -func createCachingDocumentLoader(loader ld.DocumentLoader) *ld.CachingDocumentLoader { +func createCachingDocumentLoader(loader ld.DocumentLoader) *jsonld.CachingDocumentLoader { if loader == nil { - return jsonld.NewCachingDocumentLoader() + return jsonld.NewDefaultCachingDocumentLoader() } - if cachingLoader, ok := loader.(*ld.CachingDocumentLoader); ok { + if cachingLoader, ok := loader.(*jsonld.CachingDocumentLoader); ok { return cachingLoader } - return ld.NewCachingDocumentLoader(loader) + return jsonld.NewCachingDocLoader(loader) } // prepareOpts prepare processorOpts from given CanonicalizationOpts arguments. diff --git a/pkg/doc/signature/jsonld/processor_test.go b/pkg/doc/signature/jsonld/processor_test.go index ea8e5e6ec1..5a63089a55 100644 --- a/pkg/doc/signature/jsonld/processor_test.go +++ b/pkg/doc/signature/jsonld/processor_test.go @@ -8,8 +8,10 @@ package jsonld import ( "encoding/json" + "io" "log" "strings" + "sync" "testing" "github.com/piprate/json-gold/ld" @@ -86,7 +88,7 @@ func TestGetCanonicalDocument(t *testing.T) { result: canonizedSampleVP_extraContext, opts: []ProcessorOpts{ WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), - WithDocumentLoaderCache(createContextCache("http://localhost:8652/dummy.jsonld", extraJSONLDContext)), + WithDocumentLoaderCache(createContextCache()), }, }, { @@ -95,8 +97,8 @@ func TestGetCanonicalDocument(t *testing.T) { result: canonizedSampleVP_extraContext, opts: []ProcessorOpts{ WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), - WithDocumentLoaderCache(createContextCache("http://localhost:8652/dummy.jsonld", extraJSONLDContext)), - WithDocumentLoader(jsonld.NewCachingDocumentLoader()), + WithDocumentLoaderCache(createContextCache()), + WithDocumentLoader(jsonld.NewDefaultCachingDocumentLoader()), }, }, { @@ -105,8 +107,8 @@ func TestGetCanonicalDocument(t *testing.T) { result: canonizedSampleVP_extraContext, opts: []ProcessorOpts{ WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), - WithDocumentLoaderCache(createContextCache("http://localhost:8652/dummy.jsonld", extraJSONLDContext)), - WithDocumentLoader(jsonld.NewCachingDocumentLoader()), + WithDocumentLoaderCache(createContextCache()), + WithDocumentLoader(jsonld.NewDefaultCachingDocumentLoader()), }, }, { @@ -171,11 +173,28 @@ func TestGetCanonicalDocument(t *testing.T) { result: canonizedJSONCredential, }, { - name: "canonizing sample VC document with proper proper context but remove all invalid RDF", + name: "canonizing sample VC document with proper context but remove all invalid RDF", doc: jsonVCWithProperContexts, result: canonizedJSONCredential, opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, }, + { + name: "canonizing sample VC document with proper context 2", + doc: jsonVCWithProperContexts2, + result: canonizedJSONCredential2, + }, + { + name: "canonizing sample VC document with proper context 2 but remove all invalid RDF", + doc: jsonVCWithProperContexts2, + result: canonizedJSONCredential2, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing sample VC document with proper context 2 with document loader cache", + doc: jsonVCWithProperContexts2, + result: canonizedJSONCredential2, + opts: []ProcessorOpts{WithDocumentLoaderCache(map[string]interface{}{})}, + }, { name: "canonizing sample VC document with improper context", doc: jsonVCWithIncorrectContexts, @@ -265,8 +284,8 @@ func TestCompact(t *testing.T) { }) } -func createInMemoryDocumentLoader(url, inMemoryContext string) *ld.CachingDocumentLoader { - loader := jsonld.NewCachingDocumentLoader() +func createInMemoryDocumentLoader(url, inMemoryContext string) *jsonld.CachingDocumentLoader { + loader := jsonld.NewDefaultCachingDocumentLoader() reader, err := ld.DocumentFromReader(strings.NewReader(inMemoryContext)) if err != nil { @@ -278,9 +297,9 @@ func createInMemoryDocumentLoader(url, inMemoryContext string) *ld.CachingDocume return loader } -func createContextCache(url, inMemoryContext string) map[string]interface{} { +func createContextCache() map[string]interface{} { return map[string]interface{}{ - url: inMemoryContext, + "http://localhost:8652/dummy.jsonld": extraJSONLDContext, } } @@ -293,6 +312,15 @@ func stringToMap(t *testing.T, s string) map[string]interface{} { return m } +func stringToMapBench(b *testing.B, s string) map[string]interface{} { + var m map[string]interface{} + + err := json.Unmarshal([]byte(s), &m) + require.NoError(b, err) + + return m +} + const ( jsonLdWithIncorrectRDF = `{ "@context": [ @@ -685,6 +713,25 @@ const jsonVCWithProperContexts = `{ ] }` +// nolint +const jsonVCWithProperContexts2 = `{ + "@context": "https://w3id.org/security/v2", + "id": "http://www.example.org/foo/documents/a3480d17-df7f-449f-9480-e2c35e20a865", + "allowedAction": ["read", "write"], + "invocationTarget": { + "ID": "http://www.example.org/foo/documents/a3480d17-df7f-449f-9480-e2c35e20a865", + "Type": "urn:edv:document" + }, + "proof": [{ + "created": "2020-12-04T15:28:14.673975717-05:00", + "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6OfIULug35ZmoU7lysChVpD6sjYfV71UwxqIZ8u0woYSIzRtzCo3MsZJw6cGIZMEaMssnQyRqIzo8B0yHEL2Dw", + "nonce": "da7CcJahAdFG0GXN-JnS2f2mywcFNtaLyXtGVqku2DwVwUaJbGpUQjhlNi5kDbS4ZMi2cNhEN5ac6LponS-C9w", + "proofPurpose": "capabilityDelegation", + "type": "Ed25519Signature2018", + "verificationMethod": "did:key:z6MkmkFTTczYKzU94t45sG65iZi2HA21tAU9ns8bXSmBEap4#z6MkmkFTTczYKzU94t45sG65iZi2HA21tAU9ns8bXSmBEap4" + }] +}` + // nolint const jsonVCWithIncorrectContexts = `{ "@context": [ @@ -745,6 +792,19 @@ _:c14n1 _:c14n2 . ` +// nolint +const canonizedJSONCredential2 = ` "read" . + "write" . + _:c14n2 . + _:c14n1 . +_:c14n0 "2020-12-04T15:28:14.673975717-05:00"^^ _:c14n1 . +_:c14n0 _:c14n1 . +_:c14n0 "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6OfIULug35ZmoU7lysChVpD6sjYfV71UwxqIZ8u0woYSIzRtzCo3MsZJw6cGIZMEaMssnQyRqIzo8B0yHEL2Dw" _:c14n1 . +_:c14n0 "da7CcJahAdFG0GXN-JnS2f2mywcFNtaLyXtGVqku2DwVwUaJbGpUQjhlNi5kDbS4ZMi2cNhEN5ac6LponS-C9w" _:c14n1 . +_:c14n0 _:c14n1 . +_:c14n0 _:c14n1 . +` + // nolint const canonizedJSONCredential_filtered = ` "Jayden Doe"^^ . "did:example:c276e12ec21ebfeb1f712ebc6f1" . @@ -1607,3 +1667,262 @@ func TestTransformBlankNodes(t *testing.T) { gt := TransformBlankNode(g) require.Equal(t, ge, gt) } + +func BenchmarkWithDocumentLoaderCache(b *testing.B) { + b.Run("Benchmark get canonical document", func(b *testing.B) { + tests := []struct { + name string + doc string + result string + opts []ProcessorOpts + }{ + { + name: "canonizing document with 1 incorrect RDF", + doc: jsonLdWithIncorrectRDF, + result: canonizedIncorrectRDF_Filtered, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing document with 1 incorrect RDF", + doc: jsonLdWithIncorrectRDF, + result: canonizedIncorrectRDF, + opts: []ProcessorOpts{}, + }, + { + name: "canonizing valid document 1", + doc: jsonLdSample1, + result: canonizedIncorrectRDF_Filtered, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing valid document 1", + doc: jsonLdSample1, + result: canonizedIncorrectRDF_Filtered, + opts: []ProcessorOpts{}, + }, + { + name: "canonizing sample proof document", + doc: jsonLDProofSample, + result: canonizedJsonLDProof, + }, + { + name: "canonizing sample document with multiple incorrect RDFs 1", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_filtered, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing sample document with extra context", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), + WithExternalContext("https://trustbloc.github.io/context/vc/examples-v1.jsonld"), + }, + }, + { + name: "canonizing sample document with extra dummy context and in-memory document loader", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), + WithDocumentLoader(createInMemoryDocumentLoader("http://localhost:8652/dummy.jsonld", extraJSONLDContext)), + }, + }, + { + name: "canonizing sample document with extra cached dummy context", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), + WithDocumentLoaderCache(createContextCache()), + }, + }, + { + name: "canonizing sample document with extra cached dummy context and cached document loader", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), + WithDocumentLoaderCache(createContextCache()), + WithDocumentLoader(jsonld.NewDefaultCachingDocumentLoader()), + }, + }, + { + name: "canonizing sample document with extra cached dummy context and non caching document loader", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), + WithDocumentLoaderCache(createContextCache()), + WithDocumentLoader(jsonld.NewDefaultCachingDocumentLoader()), + }, + }, + { + name: "canonizing sample document with extra byte cached dummy context", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), + WithDocumentLoaderCache(map[string]interface{}{ + "http://localhost:8652/dummy.jsonld": []byte(extraJSONLDContext), + }), + }, + }, + { + name: "canonizing sample document with extra map cached dummy context", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy.jsonld"), + WithDocumentLoaderCache(map[string]interface{}{ + "http://localhost:8652/dummy.jsonld": stringToMapBench(b, extraJSONLDContext), + }), + }, + }, + { + name: "canonizing sample document with extra io.Reader cached dummy context", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP_extraContext, + opts: []ProcessorOpts{ + WithRemoveAllInvalidRDF(), WithExternalContext("http://localhost:8652/dummy123.jsonld"), + WithDocumentLoaderCache(map[string]interface{}{ + // see Note comment of WithDocumentLoaderCache() for more information. + // SyncReader is used instead for thread-safety. + "http://localhost:8652/dummy123.jsonld": &SyncReader{ + strings.NewReader(extraJSONLDContext), + []byte(nil), *new(sync.Mutex), // nolint:gocritic // *new used here for testing only + }, + }), + }, + }, + { + name: "canonizing sample document with multiple incorrect RDFs 3", + doc: jsonLDMultipleInvalidRDFs, + result: canonizedSampleVP, + }, + { + name: "canonizing sample document with incorrect RDFs causing node label miss match issue (array type)", + doc: invalidRDFMessingUpLabelPrefixCounter, + result: canonizedSampleVP2, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing sample document with incorrect RDFs causing node label miss match issue (string type)", + doc: invalidRDFMessingUpLabelPrefixCounterStringType, + result: canonizedSampleVP2, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing document with 1 incorrect RDF11", + doc: jsonldWith2KnownInvalidRDFs, + result: canonizedIncorrectRDF_allfiltered, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing sample VC document with proper context", + doc: jsonVCWithProperContexts, + result: canonizedJSONCredential, + }, + { + name: "canonizing sample VC document with proper context but remove all invalid RDF", + doc: jsonVCWithProperContexts, + result: canonizedJSONCredential, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing sample VC document with proper context 2", + doc: jsonVCWithProperContexts2, + result: canonizedJSONCredential2, + }, + { + name: "canonizing sample VC document with proper context 2 but remove all invalid RDF", + doc: jsonVCWithProperContexts2, + result: canonizedJSONCredential2, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing sample VC document with proper context 2 with document loader cache", + doc: jsonVCWithProperContexts2, + result: canonizedJSONCredential2, + opts: []ProcessorOpts{WithDocumentLoaderCache(map[string]interface{}{})}, + }, + { + name: "canonizing sample VC document with improper context", + doc: jsonVCWithIncorrectContexts, + result: canonizedJSONCredential_notfiltered, + opts: []ProcessorOpts{}, + }, + { + name: "canonizing sample VC document with improper context but remove all invalid RDF", + doc: jsonVCWithIncorrectContexts, + result: canonizedJSONCredential_filtered, + opts: []ProcessorOpts{WithRemoveAllInvalidRDF()}, + }, + { + name: "canonizing empty document", + doc: `{}`, + result: "", + }, + } + + for _, test := range tests { + tc := test + var r string + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var jsonldDoc map[string]interface{} + err := json.Unmarshal([]byte(tc.doc), &jsonldDoc) + require.NoError(b, err) + + response, err := NewProcessor(defaultAlgorithm).GetCanonicalDocument(jsonldDoc, + append([]ProcessorOpts{jsonldCache}, tc.opts...)...) + + require.NoError(b, err) + require.EqualValues(b, tc.result, string(response)) + } + r = tc.result + }) + + result = r + } + }) +} + +// nolint:gochecknoglobals // needed to avoid Go compiler perf optimizations for benchmarks. +var result string + +// SyncReader wraps an io.Reader to be used in a tread-safe fashion. +// implementation adaptation of https://gist.github.com/jmackie/11570bdcd8a4c10d72619a5e1f21c5f8. +type SyncReader struct { + io.Reader + + s []byte // buffered io.Reader data + mux sync.Mutex // could maybe be replaced by an RWMutex +} + +// Read data in a thread-safe fashion. +func (r *SyncReader) Read(data []byte) (int, error) { + r.mux.Lock() + defer r.mux.Unlock() + + // Declare the returned error here. It is only assigned by calls to + // r.Reader.Read (`if` block below). That way callers see the io.EOF + // error only when they reach the limit of r.s + var err error + + // If the client has asked for more data than is available, we need to + // grow the buffer. + if int64(len(data)) > int64(len(r.s)) { + cp := make([]byte, len(data)) + + var n int // don't shadow err + + n, err = r.Reader.Read(cp) + r.s = append(r.s, cp[:n]...) + } + + n := copy(data, r.s) + + return n, err +} diff --git a/pkg/doc/signature/suite/bbsblssignature2020/support_test.go b/pkg/doc/signature/suite/bbsblssignature2020/support_test.go index 1e552a0c96..e65fd1cc31 100644 --- a/pkg/doc/signature/suite/bbsblssignature2020/support_test.go +++ b/pkg/doc/signature/suite/bbsblssignature2020/support_test.go @@ -18,7 +18,7 @@ import ( const jsonldContextPrefix = "testdata/context" -func addJSONLDCachedContextFromFile(loader *ld.CachingDocumentLoader, contextURL, contextFile string) { +func addJSONLDCachedContextFromFile(loader *jsonld.CachingDocumentLoader, contextURL, contextFile string) { contextContent, err := ioutil.ReadFile(filepath.Clean(filepath.Join( jsonldContextPrefix, contextFile))) if err != nil { @@ -29,7 +29,7 @@ func addJSONLDCachedContextFromFile(loader *ld.CachingDocumentLoader, contextURL } func createLDPBBS2020DocumentLoader() ld.DocumentLoader { - loader := jsonld.NewCachingDocumentLoader() + loader := jsonld.NewDefaultCachingDocumentLoader() addJSONLDCachedContextFromFile(loader, "https://www.w3.org/2018/credentials/v1", "vc.jsonld") @@ -49,7 +49,7 @@ func createLDPBBS2020DocumentLoader() ld.DocumentLoader { return loader } -func addJSONLDCachedContext(loader *ld.CachingDocumentLoader, contextURL, contextContent string) { +func addJSONLDCachedContext(loader *jsonld.CachingDocumentLoader, contextURL, contextContent string) { reader, err := ld.DocumentFromReader(strings.NewReader(contextContent)) if err != nil { panic(err) diff --git a/pkg/doc/signature/suite/bbsblssignatureproof2020/support_test.go b/pkg/doc/signature/suite/bbsblssignatureproof2020/support_test.go index 5c8090bb13..2788167cdb 100644 --- a/pkg/doc/signature/suite/bbsblssignatureproof2020/support_test.go +++ b/pkg/doc/signature/suite/bbsblssignatureproof2020/support_test.go @@ -18,7 +18,7 @@ import ( const jsonldContextPrefix = "testdata/context" -func addJSONLDCachedContextFromFile(loader *ld.CachingDocumentLoader, contextURL, contextFile string) { +func addJSONLDCachedContextFromFile(loader *jsonld.CachingDocumentLoader, contextURL, contextFile string) { contextContent, err := ioutil.ReadFile(filepath.Clean(filepath.Join( jsonldContextPrefix, contextFile))) if err != nil { @@ -29,7 +29,7 @@ func addJSONLDCachedContextFromFile(loader *ld.CachingDocumentLoader, contextURL } func createLDPBBS2020DocumentLoader() ld.DocumentLoader { - loader := jsonld.NewCachingDocumentLoader() + loader := jsonld.NewDefaultCachingDocumentLoader() addJSONLDCachedContextFromFile(loader, "https://www.w3.org/2018/credentials/v1", "vc.jsonld") @@ -55,7 +55,7 @@ func createLDPBBS2020DocumentLoader() ld.DocumentLoader { return loader } -func addJSONLDCachedContext(loader *ld.CachingDocumentLoader, contextURL, contextContent string) { +func addJSONLDCachedContext(loader *jsonld.CachingDocumentLoader, contextURL, contextContent string) { reader, err := ld.DocumentFromReader(strings.NewReader(contextContent)) if err != nil { panic(err) diff --git a/scripts/check_bench.sh b/scripts/check_bench.sh new file mode 100755 index 0000000000..a58a781921 --- /dev/null +++ b/scripts/check_bench.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Copyright SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +set -e + +echo "Running $0. This benchmark is not absolute. Add code benchmarks in the framework, then execute it with the same environment setup prior and after a change to compare perf differences." + +go test -run=^$ -bench=. ./... \ No newline at end of file diff --git a/test/bdd/pkg/verifiable/verifiable_steps.go b/test/bdd/pkg/verifiable/verifiable_steps.go index 20e0afe78b..3442950488 100644 --- a/test/bdd/pkg/verifiable/verifiable_steps.go +++ b/test/bdd/pkg/verifiable/verifiable_steps.go @@ -381,7 +381,7 @@ func mapDIDKeyType(proofType string) string { // CachingJSONLDLoader creates JSON-LD CachingDocumentLoader with preloaded VC and security JSON-LD contexts. func CachingJSONLDLoader() ld.DocumentLoader { - loader := jld.NewCachingDocumentLoader() + loader := jld.NewDefaultCachingDocumentLoader() cacheContext := func(source, url string) { reader, _ := ld.DocumentFromReader(strings.NewReader(source)) //nolint:errcheck