Repository: hyperledger/firefly
Pull request #1005 (2022-08-25 09:43:31)

Create a centralised cache manager
Create a centralised cache manager as described in #943 




Patch:
diff --git a/Makefile b/Makefile
index bcb7ba638..e1778c761 100644
--- a/Makefile
+++ b/Makefile
@@ -73,6 +73,7 @@ $(eval $(call makemock, internal/spievents,        Manager,            spievents
 $(eval $(call makemock, internal/orchestrator,     Orchestrator,       orchestratormocks))
 $(eval $(call makemock, internal/apiserver,        FFISwaggerGen,      apiservermocks))
 $(eval $(call makemock, internal/apiserver,        Server,             apiservermocks))
+$(eval $(call makemock, internal/cache,            Manager,            cachemocks))
 $(eval $(call makemock, internal/metrics,          Manager,            metricsmocks))
 $(eval $(call makemock, internal/operations,       Manager,            operationmocks))
 $(eval $(call makemock, internal/multiparty,       Manager,            multipartymocks))
diff --git a/docs/reference/config.md b/docs/reference/config.md
index f65527ddf..01f554257 100644
--- a/docs/reference/config.md
+++ b/docs/reference/config.md
@@ -38,13 +38,6 @@ nav_order: 2
 |---|-----------|----|-------------|
 |keyNormalization|Mechanism to normalize keys before using them. Valid options are `blockchain_plugin` - use blockchain plugin (default) or `none` - do not attempt normalization (deprecated - use namespaces.predefined[].asset.manager.keyNormalization)|`string`|`<nil>`
 
-## batch.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|limit|Max number of cache items for batches|`string`|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## batch.manager
 
 |Key|Description|Type|Default Value|
@@ -108,13 +101,6 @@ nav_order: 2
 |password|Password|`string`|`<nil>`
 |username|Username|`string`|`<nil>`
 
-## blockchain.ethereum.addressResolver.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|size|The size of the cache|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`1000`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`24h`
-
 ## blockchain.ethereum.addressResolver.proxy
 
 |Key|Description|Type|Default Value|
@@ -270,13 +256,6 @@ nav_order: 2
 |readBufferSize|The size in bytes of the read buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`16Kb`
 |writeBufferSize|The size in bytes of the write buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`16Kb`
 
-## blockchainevent.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|limit|Max number of cache items for blockchain events|`string`|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## broadcast.batch
 
 |Key|Description|Type|Default Value|
@@ -286,19 +265,95 @@ nav_order: 2
 |size|The maximum number of messages that can be packed into a batch|`int`|`<nil>`
 |timeout|The timeout to wait for a batch to fill, before sending|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
 
+## cache
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|enabled|Enables caching, defaults to true|`boolean`|`<nil>`
+
+## cache.addressresolver
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached items for address resolver|`int`|`<nil>`
+|ttl|Time to live of cached items for address resolver|`string`|`<nil>`
+
+## cache.batch
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached items for batches|`int`|`<nil>`
+|ttl|Time to live of cache items for batches|`string`|`<nil>`
+
 ## cache.blockchain
 
 |Key|Description|Type|Default Value|
 |---|-----------|----|-------------|
-|limit|Max number of cache items for blockchain plugin cache|`string`|`<nil>`
-|ttl|Time to live for blockchain cache items|`string`|`<nil>`
+|limit|Max number of cached items for blockchain|`int`|`<nil>`
+|ttl|Time to live of cached items for blockchain|`string`|`<nil>`
+
+## cache.blockchainevent
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached blockchain events for transactions|`int`|`<nil>`
+|ttl|Time to live of cached blockchain events for transactions|`string`|`<nil>`
+
+## cache.eventlistenertopic
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached items for blockchain listener topics|`int`|`<nil>`
+|ttl|Time to live of cached items for blockchain listener topics|`string`|`<nil>`
+
+## cache.group
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached items for groups|`int`|`<nil>`
+|ttl|Time to live of cached items for groups|`string`|`<nil>`
+
+## cache.identity
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached identities for identity manager|`int`|`<nil>`
+|ttl|Time to live of cached identities for identity manager|`string`|`<nil>`
+
+## cache.message
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|size|Max size of cached messages for data manager|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
+|ttl|Time to live of cached messages for data manager|`string`|`<nil>`
 
 ## cache.operations
 
 |Key|Description|Type|Default Value|
 |---|-----------|----|-------------|
-|limit|Max number of cache items for operations|`string`|`<nil>`
-|ttl|Time to live for operation cache items|`string`|`<nil>`
+|limit|Max number of cached items for operations|`int`|`<nil>`
+|ttl|Time to live of cached items for operations|`string`|`<nil>`
+
+## cache.signingkey
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|limit|Max number of cached signing keys for identity manager|`int`|`<nil>`
+|ttl|Time to live of cached signing keys for identity manager|`string`|`<nil>`
+
+## cache.transaction
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|size|Max size of cached transactions|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
+|ttl|Time to live of cached transactions|`string`|`<nil>`
+
+## cache.validator
+
+|Key|Description|Type|Default Value|
+|---|-----------|----|-------------|
+|size|Max size of cached validators for data manager|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
+|ttl|Time to live of cached validators for data manager|`string`|`<nil>`
 
 ## cors
 
@@ -469,13 +524,6 @@ nav_order: 2
 |initDelay|The initial retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
 |maxDelay|The maximum retry delay|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
 
-## event.listenerTopic.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|limit|Max number of cache items for blockchain listener topics|`string`|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## event.transports
 
 |Key|Description|Type|Default Value|
@@ -483,13 +531,6 @@ nav_order: 2
 |default|The default event transport for new subscriptions|`string`|`<nil>`
 |enabled|Which event interface plugins are enabled|`boolean`|`<nil>`
 
-## group.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|limit|Max number of cache items for private group addresses|`string`|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## histograms
 
 |Key|Description|Type|Default Value|
@@ -529,13 +570,6 @@ nav_order: 2
 |enabled|Enables or disables TLS on this API|`boolean`|`false`
 |keyFile|The path to the private key file for TLS on this API|`string`|`<nil>`
 
-## identity.manager.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|limit|The identity manager cache limit in count of items|`int`|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## log
 
 |Key|Description|Type|Default Value|
@@ -568,13 +602,6 @@ nav_order: 2
 |message|Configures the JSON key containing the log message|`string`|`<nil>`
 |timestamp|Configures the JSON key containing the timestamp of the log|`string`|`<nil>`
 
-## message.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|size|The size of the cache|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## message.writer
 
 |Key|Description|Type|Default Value|
@@ -764,13 +791,6 @@ nav_order: 2
 |password|Password|`string`|`<nil>`
 |username|Username|`string`|`<nil>`
 
-## plugins.blockchain[].ethereum.addressResolver.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|size|The size of the cache|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`1000`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`24h`
-
 ## plugins.blockchain[].ethereum.addressResolver.proxy
 
 |Key|Description|Type|Default Value|
@@ -1361,23 +1381,9 @@ nav_order: 2
 |readBufferSize|The size in bytes of the read buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
 |writeBufferSize|The size in bytes of the write buffer for the WebSocket connection|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
 
-## transaction.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|size|The size of the cache|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
-
 ## ui
 
 |Key|Description|Type|Default Value|
 |---|-----------|----|-------------|
 |enabled|Enables the web user interface|`boolean`|`<nil>`
-|path|The file system path which contains the static HTML, CSS, and JavaScript files for the user interface|`string`|`<nil>`
-
-## validator.cache
-
-|Key|Description|Type|Default Value|
-|---|-----------|----|-------------|
-|size|The size of the cache|[`BytesSize`](https://pkg.go.dev/github.com/docker/go-units#BytesSize)|`<nil>`
-|ttl|The time to live (TTL) for the cache|[`time.Duration`](https://pkg.go.dev/time#Duration)|`<nil>`
\ No newline at end of file
+|path|The file system path which contains the static HTML, CSS, and JavaScript files for the user interface|`string`|`<nil>`
\ No newline at end of file
diff --git a/internal/blockchain/ethereum/address_resolver.go b/internal/blockchain/ethereum/address_resolver.go
index 0494250ec..4f13743a8 100644
--- a/internal/blockchain/ethereum/address_resolver.go
+++ b/internal/blockchain/ethereum/address_resolver.go
@@ -20,15 +20,15 @@ import (
 	"context"
 	"strings"
 	"text/template"
-	"time"
 
 	"github.com/go-resty/resty/v2"
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/ffresty"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
+	"github.com/hyperledger/firefly/internal/cache"
+	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
-	"github.com/karlseguin/ccache"
 )
 
 // addressResolver is a REST-pluggable interface to allow arbitrary strings that reference
@@ -45,32 +45,40 @@ type addressResolver struct {
 	bodyTemplate   *template.Template
 	responseField  string
 	client         *resty.Client
-	cache          *ccache.Cache
-	cacheTTL       time.Duration
+	cache          cache.CInterface
 }
 
 type addressResolverInserts struct {
 	Key string
 }
 
-func newAddressResolver(ctx context.Context, config config.Section) (ar *addressResolver, err error) {
-
+func newAddressResolver(ctx context.Context, localConfig config.Section, cacheManager cache.Manager) (ar *addressResolver, err error) {
+	cache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheAddressResolverLimit,
+			coreconfig.CacheAddressResolverTTL,
+			"",
+		),
+	)
+	if err != nil {
+		return nil, err
+	}
 	ar = &addressResolver{
-		retainOriginal: config.GetBool(AddressResolverRetainOriginal),
-		method:         config.GetString(AddressResolverMethod),
-		responseField:  config.GetString(AddressResolverResponseField),
-		client:         ffresty.New(ctx, config),
-		cache:          ccache.New(ccache.Configure().MaxSize(config.GetInt64(AddressResolverCacheSize))),
-		cacheTTL:       config.GetDuration(AddressResolverCacheTTL),
+		retainOriginal: localConfig.GetBool(AddressResolverRetainOriginal),
+		method:         localConfig.GetString(AddressResolverMethod),
+		responseField:  localConfig.GetString(AddressResolverResponseField),
+		client:         ffresty.New(ctx, localConfig),
+		cache:          cache,
 	}
 
-	urlTemplateString := config.GetString(AddressResolverURLTemplate)
+	urlTemplateString := localConfig.GetString(AddressResolverURLTemplate)
 	ar.urlTemplate, err = template.New(AddressResolverURLTemplate).Option("missingkey=error").Parse(urlTemplateString)
 	if err != nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgGoTemplateCompileFailed, AddressResolverURLTemplate, err)
 	}
 
-	bodyTemplateString := config.GetString(AddressResolverBodyTemplate)
+	bodyTemplateString := localConfig.GetString(AddressResolverBodyTemplate)
 	if bodyTemplateString != "" {
 		ar.bodyTemplate, err = template.New(AddressResolverBodyTemplate).Option("missingkey=error").Parse(bodyTemplateString)
 		if err != nil {
@@ -83,9 +91,8 @@ func newAddressResolver(ctx context.Context, config config.Section) (ar *address
 
 func (ar *addressResolver) NormalizeSigningKey(ctx context.Context, keyDescriptor string) (string, error) {
 
-	if cached := ar.cache.Get(keyDescriptor); cached != nil {
-		cached.Extend(ar.cacheTTL)
-		return cached.Value().(string), nil
+	if cached := ar.cache.GetString(keyDescriptor); cached != "" {
+		return cached, nil
 	}
 
 	inserts := &addressResolverInserts{
@@ -124,6 +131,6 @@ func (ar *addressResolver) NormalizeSigningKey(ctx context.Context, keyDescripto
 		return "", i18n.NewError(ctx, coremsgs.MsgAddressResolveBadResData, keyDescriptor, jsonRes.String(), err)
 	}
 
-	ar.cache.Set(keyDescriptor, address, ar.cacheTTL)
+	ar.cache.SetString(keyDescriptor, address)
 	return address, nil
 }
diff --git a/internal/blockchain/ethereum/config.go b/internal/blockchain/ethereum/config.go
index 7e6289ca5..f250004fc 100644
--- a/internal/blockchain/ethereum/config.go
+++ b/internal/blockchain/ethereum/config.go
@@ -31,8 +31,6 @@ const (
 
 	defaultAddressResolverMethod        = "GET"
 	defaultAddressResolverResponseField = "address"
-	defaultAddressResolverCacheSize     = 1000
-	defaultAddressResolverCacheTTL      = "24h"
 )
 
 const (
@@ -66,10 +64,6 @@ const (
 	AddressResolverBodyTemplate = "bodyTemplate"
 	// AddressResolverResponseField the name of a JSON field that is provided in the response, that contains the ethereum address (default "address")
 	AddressResolverResponseField = "responseField"
-	// AddressResolverCacheSize the size of the LRU cache
-	AddressResolverCacheSize = "cache.size"
-	// AddressResolverCacheTTL the TTL on cache entries
-	AddressResolverCacheTTL = "cache.ttl"
 
 	// FFTMConfigKey is a sub-key in the config that optionally contains FireFly transaction connection information
 	FFTMConfigKey = "fftm"
@@ -96,6 +90,4 @@ func (e *Ethereum) InitConfig(config config.Section) {
 	addressResolverConf.AddKnownKey(AddressResolverURLTemplate)
 	addressResolverConf.AddKnownKey(AddressResolverBodyTemplate)
 	addressResolverConf.AddKnownKey(AddressResolverResponseField, defaultAddressResolverResponseField)
-	addressResolverConf.AddKnownKey(AddressResolverCacheSize, defaultAddressResolverCacheSize)
-	addressResolverConf.AddKnownKey(AddressResolverCacheTTL, defaultAddressResolverCacheTTL)
 }
diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go
index bc7cf73fa..ed2997ebc 100644
--- a/internal/blockchain/ethereum/ethereum.go
+++ b/internal/blockchain/ethereum/ethereum.go
@@ -24,7 +24,6 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
-	"time"
 
 	"github.com/go-resty/resty/v2"
 	"github.com/hyperledger/firefly-common/pkg/config"
@@ -36,12 +35,12 @@ import (
 	"github.com/hyperledger/firefly-signer/pkg/abi"
 	"github.com/hyperledger/firefly-signer/pkg/ffi2abi"
 	"github.com/hyperledger/firefly/internal/blockchain/common"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/metrics"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
-	"github.com/karlseguin/ccache"
 )
 
 const (
@@ -66,8 +65,7 @@ type Ethereum struct {
 	metrics         metrics.Manager
 	ethconnectConf  config.Section
 	subs            common.FireflySubscriptions
-	cache           *ccache.Cache
-	cacheTTL        time.Duration
+	cache           cache.CInterface
 }
 
 type eventStreamWebsocket struct {
@@ -129,7 +127,7 @@ func (e *Ethereum) VerifierType() core.VerifierType {
 	return core.VerifierTypeEthAddress
 }
 
-func (e *Ethereum) Init(ctx context.Context, cancelCtx context.CancelFunc, conf config.Section, metrics metrics.Manager) (err error) {
+func (e *Ethereum) Init(ctx context.Context, cancelCtx context.CancelFunc, conf config.Section, metrics metrics.Manager, cacheManager cache.Manager) (err error) {
 	e.InitConfig(conf)
 	ethconnectConf := e.ethconnectConf
 	addressResolverConf := conf.SubSection(AddressResolverConfigKey)
@@ -143,7 +141,7 @@ func (e *Ethereum) Init(ctx context.Context, cancelCtx context.CancelFunc, conf
 	e.subs = common.NewFireflySubscriptions()
 
 	if addressResolverConf.GetString(AddressResolverURLTemplate) != "" {
-		if e.addressResolver, err = newAddressResolver(ctx, addressResolverConf); err != nil {
+		if e.addressResolver, err = newAddressResolver(ctx, addressResolverConf, cacheManager); err != nil {
 			return err
 		}
 	}
@@ -173,10 +171,20 @@ func (e *Ethereum) Init(ctx context.Context, cancelCtx context.CancelFunc, conf
 		return err
 	}
 
-	e.cacheTTL = config.GetDuration(coreconfig.CacheBlockchainTTL)
-	e.cache = ccache.New(ccache.Configure().MaxSize(config.GetInt64(coreconfig.CacheBlockchainLimit)))
+	cache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheBlockchainLimit,
+			coreconfig.CacheBlockchainTTL,
+			"",
+		),
+	)
+	if err != nil {
+		return err
+	}
+	e.cache = cache
 
-	e.streams = newStreamManager(e.client, e.cache, e.cacheTTL)
+	e.streams = newStreamManager(e.client, e.cache)
 	batchSize := ethconnectConf.GetUint(EthconnectConfigBatchSize)
 	batchTimeout := uint(ethconnectConf.GetDuration(EthconnectConfigBatchTimeout).Milliseconds())
 	stream, err := e.streams.ensureEventStream(e.ctx, e.topic, batchSize, batchTimeout)
@@ -815,14 +823,13 @@ func (e *Ethereum) GetNetworkVersion(ctx context.Context, location *fftypes.JSON
 	}
 
 	cacheKey := "version:" + ethLocation.Address
-	if cached := e.cache.Get(cacheKey); cached != nil {
-		cached.Extend(e.cacheTTL)
-		return cached.Value().(int), nil
+	if cachedValue := e.cache.GetInt(cacheKey); cachedValue != 0 {
+		return cachedValue, nil
 	}
 
 	version, err = e.queryNetworkVersion(ctx, ethLocation.Address)
 	if err == nil {
-		e.cache.Set(cacheKey, version, e.cacheTTL)
+		e.cache.SetInt(cacheKey, version)
 	}
 	return version, err
 }
diff --git a/internal/blockchain/ethereum/eventstream.go b/internal/blockchain/ethereum/eventstream.go
index 7adfc70a9..0136c66d2 100644
--- a/internal/blockchain/ethereum/eventstream.go
+++ b/internal/blockchain/ethereum/eventstream.go
@@ -21,7 +21,6 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"fmt"
-	"time"
 
 	"github.com/go-resty/resty/v2"
 	"github.com/hyperledger/firefly-common/pkg/ffresty"
@@ -29,15 +28,14 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-signer/pkg/abi"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/pkg/core"
-	"github.com/karlseguin/ccache"
 )
 
 type streamManager struct {
-	client   *resty.Client
-	cache    *ccache.Cache
-	cacheTTL time.Duration
+	client *resty.Client
+	cache  cache.CInterface
 }
 
 type eventStream struct {
@@ -67,11 +65,10 @@ type subscriptionCheckpoint struct {
 	Catchup    bool               `json:"catchup,omitempty"`
 }
 
-func newStreamManager(client *resty.Client, cache *ccache.Cache, cacheTTL time.Duration) *streamManager {
+func newStreamManager(client *resty.Client, cache cache.CInterface) *streamManager {
 	return &streamManager{
-		client:   client,
-		cache:    cache,
-		cacheTTL: cacheTTL,
+		client: client,
+		cache:  cache,
 	}
 }
 
@@ -166,17 +163,15 @@ func (s *streamManager) getSubscription(ctx context.Context, subID string) (sub
 }
 
 func (s *streamManager) getSubscriptionName(ctx context.Context, subID string) (string, error) {
-	cached := s.cache.Get("sub:" + subID)
-	if cached != nil {
-		cached.Extend(s.cacheTTL)
-		return cached.Value().(string), nil
+	if cachedValue := s.cache.GetString("sub:" + subID); cachedValue != "" {
+		return cachedValue, nil
 	}
 
 	sub, err := s.getSubscription(ctx, subID)
 	if err != nil {
 		return "", err
 	}
-	s.cache.Set("sub:"+subID, sub.Name, s.cacheTTL)
+	s.cache.SetString("sub:"+subID, sub.Name)
 	return sub.Name, nil
 }
 
diff --git a/internal/blockchain/fabric/eventstream.go b/internal/blockchain/fabric/eventstream.go
index 8b883ec40..5f525485f 100644
--- a/internal/blockchain/fabric/eventstream.go
+++ b/internal/blockchain/fabric/eventstream.go
@@ -19,22 +19,20 @@ package fabric
 import (
 	"context"
 	"fmt"
-	"time"
 
 	"github.com/go-resty/resty/v2"
 	"github.com/hyperledger/firefly-common/pkg/ffresty"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/pkg/core"
-	"github.com/karlseguin/ccache"
 )
 
 type streamManager struct {
-	client   *resty.Client
-	signer   string
-	cache    *ccache.Cache
-	cacheTTL time.Duration
+	client *resty.Client
+	signer string
+	cache  cache.CInterface
 }
 
 type eventStream struct {
@@ -63,12 +61,11 @@ type eventFilter struct {
 	EventFilter string `json:"eventFilter"`
 }
 
-func newStreamManager(client *resty.Client, signer string, cache *ccache.Cache, cacheTTL time.Duration) *streamManager {
+func newStreamManager(client *resty.Client, signer string, cache cache.CInterface) *streamManager {
 	return &streamManager{
-		client:   client,
-		signer:   signer,
-		cache:    cache,
-		cacheTTL: cacheTTL,
+		client: client,
+		signer: signer,
+		cache:  cache,
 	}
 }
 
@@ -146,17 +143,14 @@ func (s *streamManager) getSubscription(ctx context.Context, subID string) (sub
 }
 
 func (s *streamManager) getSubscriptionName(ctx context.Context, subID string) (string, error) {
-	cached := s.cache.Get("sub:" + subID)
-	if cached != nil {
-		cached.Extend(s.cacheTTL)
-		return cached.Value().(string), nil
+	if cachedValue := s.cache.GetString("sub:" + subID); cachedValue != "" {
+		return cachedValue, nil
 	}
-
 	sub, err := s.getSubscription(ctx, subID)
 	if err != nil {
 		return "", err
 	}
-	s.cache.Set("sub:"+subID, sub.Name, s.cacheTTL)
+	s.cache.SetString("sub:"+subID, sub.Name)
 	return sub.Name, nil
 }
 
diff --git a/internal/blockchain/fabric/fabric.go b/internal/blockchain/fabric/fabric.go
index fba79312f..fc0dbb2ab 100644
--- a/internal/blockchain/fabric/fabric.go
+++ b/internal/blockchain/fabric/fabric.go
@@ -24,7 +24,6 @@ import (
 	"fmt"
 	"regexp"
 	"strings"
-	"time"
 
 	"github.com/go-resty/resty/v2"
 	"github.com/hyperledger/firefly-common/pkg/config"
@@ -34,12 +33,12 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/wsclient"
 	"github.com/hyperledger/firefly/internal/blockchain/common"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/metrics"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
-	"github.com/karlseguin/ccache"
 )
 
 const (
@@ -65,8 +64,7 @@ type Fabric struct {
 	metrics        metrics.Manager
 	fabconnectConf config.Section
 	subs           common.FireflySubscriptions
-	cache          *ccache.Cache
-	cacheTTL       time.Duration
+	cache          cache.CInterface
 }
 
 type eventStreamWebsocket struct {
@@ -188,7 +186,7 @@ func (f *Fabric) VerifierType() core.VerifierType {
 	return core.VerifierTypeMSPIdentity
 }
 
-func (f *Fabric) Init(ctx context.Context, cancelCtx context.CancelFunc, conf config.Section, metrics metrics.Manager) (err error) {
+func (f *Fabric) Init(ctx context.Context, cancelCtx context.CancelFunc, conf config.Section, metrics metrics.Manager, cacheManager cache.Manager) (err error) {
 	f.InitConfig(conf)
 	fabconnectConf := f.fabconnectConf
 
@@ -223,11 +221,20 @@ func (f *Fabric) Init(ctx context.Context, cancelCtx context.CancelFunc, conf co
 	if err != nil {
 		return err
 	}
+	cache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheBlockchainLimit,
+			coreconfig.CacheBlockchainTTL,
+			"",
+		),
+	)
+	if err != nil {
+		return err
+	}
+	f.cache = cache
 
-	f.cacheTTL = config.GetDuration(coreconfig.CacheBlockchainTTL)
-	f.cache = ccache.New(ccache.Configure().MaxSize(config.GetInt64(coreconfig.CacheBlockchainLimit)))
-
-	f.streams = newStreamManager(f.client, f.signer, f.cache, f.cacheTTL)
+	f.streams = newStreamManager(f.client, f.signer, f.cache)
 	batchSize := f.fabconnectConf.GetUint(FabconnectConfigBatchSize)
 	batchTimeout := uint(f.fabconnectConf.GetDuration(FabconnectConfigBatchTimeout).Milliseconds())
 	stream, err := f.streams.ensureEventStream(f.ctx, f.topic, batchSize, batchTimeout)
@@ -858,14 +865,13 @@ func (f *Fabric) GetNetworkVersion(ctx context.Context, location *fftypes.JSONAn
 	}
 
 	cacheKey := "version:" + fabricOnChainLocation.Channel + ":" + fabricOnChainLocation.Chaincode
-	if cached := f.cache.Get(cacheKey); cached != nil {
-		cached.Extend(f.cacheTTL)
-		return cached.Value().(int), nil
+	if cachedValue := f.cache.GetInt(cacheKey); cachedValue != 0 {
+		return cachedValue, nil
 	}
 
 	version, err = f.queryNetworkVersion(ctx, fabricOnChainLocation.Channel, fabricOnChainLocation.Chaincode)
 	if err == nil {
-		f.cache.Set(cacheKey, version, f.cacheTTL)
+		f.cache.SetInt(cacheKey, version)
 	}
 	return version, err
 }
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
new file mode 100644
index 000000000..c57ce4f28
--- /dev/null
+++ b/internal/cache/cache.go
@@ -0,0 +1,226 @@
+// Copyright © 2022 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 cache
+
+import (
+	"context"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/hyperledger/firefly-common/pkg/config"
+	"github.com/hyperledger/firefly-common/pkg/i18n"
+	"github.com/karlseguin/ccache"
+
+	"github.com/hyperledger/firefly/internal/coreconfig"
+	"github.com/hyperledger/firefly/internal/coremsgs"
+)
+
+type CConfig struct {
+	ctx               context.Context
+	namespace         string
+	maxLimitConfigKey config.RootKey
+	ttlConfigKey      config.RootKey
+}
+
+func NewCacheConfig(ctx context.Context, maxLimitConfigKey config.RootKey, ttlConfigKey config.RootKey, namespace string) *CConfig {
+	if namespace == "" {
+		namespace = "global"
+	}
+	cc := &CConfig{
+		ctx:               ctx,
+		namespace:         namespace,
+		maxLimitConfigKey: maxLimitConfigKey,
+		ttlConfigKey:      ttlConfigKey,
+	}
+	return cc
+}
+
+func (cc *CConfig) UniqueName() (string, error) {
+	category, err := cc.Category()
+	if err != nil {
+		return "", err
+	}
+	return cc.namespace + "::" + category, nil
+}
+
+func (cc *CConfig) Category() (string, error) {
+	if cc.maxLimitConfigKey == "" {
+		return "", i18n.NewError(cc.ctx, coremsgs.MsgCacheMissSizeLimitKeyInternal)
+	}
+	if cc.ttlConfigKey == "" {
+		return "", i18n.NewError(cc.ctx, coremsgs.MsgCacheMissTTLKeyInternal)
+	}
+
+	categoryDerivedFromMaxLimitConfigKey, _ := parseConfigKeyString(string(cc.maxLimitConfigKey))
+	categoryDerivedFromTTLConfigKey, _ := parseConfigKeyString(string(cc.ttlConfigKey))
+	if categoryDerivedFromMaxLimitConfigKey != categoryDerivedFromTTLConfigKey {
+		return "", i18n.NewError(cc.ctx, coremsgs.MsgCacheConfigKeyMismatchInternal, cc.maxLimitConfigKey, cc.ttlConfigKey, categoryDerivedFromMaxLimitConfigKey, categoryDerivedFromTTLConfigKey)
+
+	}
+	return categoryDerivedFromMaxLimitConfigKey, nil
+}
+
+func parseConfigKeyString(configKey string) (string, string) {
+	keyParts := strings.Split(configKey, ".")
+	categoryString := strings.Join(keyParts[:len(keyParts)-1], ".")
+	configName := keyParts[len(keyParts)-1]
+	return categoryString, configName
+}
+
+func (cc *CConfig) MaxSize() (int64, error) {
+	_, sizeConfigName := parseConfigKeyString(string(cc.maxLimitConfigKey))
+	switch sizeConfigName {
+	case "limit":
+		return config.GetInt64(cc.maxLimitConfigKey), nil
+	case "size":
+		return config.GetByteSize(cc.maxLimitConfigKey), nil
+	default:
+		return 0, i18n.NewError(cc.ctx, coremsgs.MsgCacheUnexpectedSizeKeyNameInternal, sizeConfigName)
+	}
+}
+
+func (cc *CConfig) TTL() time.Duration {
+	return config.GetDuration(cc.ttlConfigKey)
+}
+
+type Manager interface {
+	GetCache(cc *CConfig) (CInterface, error)
+	ListKeys() []string
+}
+
+type CInterface interface {
+	Get(key string) interface{}
+	Set(key string, val interface{})
+
+	GetString(key string) string
+	SetString(key string, val string)
+
+	GetInt(key string) int
+	SetInt(key string, val int)
+}
+
+type CCache struct {
+	enabled  bool
+	ctx      context.Context
+	name     string
+	cache    *ccache.Cache
+	cacheTTL time.Duration
+}
+
+func (c *CCache) Set(key string, val interface{}) {
+	if !c.enabled {
+		return
+	}
+	c.cache.Set(c.name+":"+key, val, c.cacheTTL)
+}
+func (c *CCache) Get(key string) interface{} {
+	if !c.enabled {
+		return nil
+	}
+	if cached := c.cache.Get(c.name + ":" + key); cached != nil {
+		cached.Extend(c.cacheTTL)
+		return cached.Value()
+	}
+	return nil
+}
+
+func (c *CCache) SetString(key string, val string) {
+	c.Set(key, val)
+}
+
+func (c *CCache) GetString(key string) string {
+	val := c.Get(key)
+	if val != nil {
+		return c.Get(key).(string)
+	}
+	return ""
+}
+
+func (c *CCache) SetInt(key string, val int) {
+	c.Set(key, val)
+}
+
+func (c *CCache) GetInt(key string) int {
+	val := c.Get(key)
+	if val != nil {
+		return c.Get(key).(int)
+	}
+	return 0
+}
+
+type cacheManager struct {
+	ctx     context.Context
+	enabled bool
+	m       sync.Mutex
+	// maintain a list of named configured CCache, the name are unique configuration category id
+	// e.g. cache.batch
+	configuredCaches map[string]CInterface
+}
+
+func (cm *cacheManager) GetCache(cc *CConfig) (CInterface, error) {
+	cacheName, err := cc.UniqueName()
+	if err != nil {
+		return nil, err
+	}
+	maxSize, err := cc.MaxSize()
+	if err != nil {
+		return nil, err
+	}
+	cm.m.Lock()
+	cache, exists := cm.configuredCaches[cacheName]
+	if !exists {
+		cache = &CCache{
+			ctx:      cc.ctx,
+			name:     cacheName,
+			cache:    ccache.New(ccache.Configure().MaxSize(maxSize)),
+			cacheTTL: cc.TTL(),
+			enabled:  cm.enabled,
+		}
+		cm.configuredCaches[cacheName] = cache
+	}
+	cm.m.Unlock()
+	return cache, nil
+}
+
+func (cm *cacheManager) ListKeys() []string {
+	keys := make([]string, 0, len(cm.configuredCaches))
+	for k := range cm.configuredCaches {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+func NewCacheManager(ctx context.Context) Manager {
+	cm := &cacheManager{
+		ctx:              ctx,
+		enabled:          config.GetBool(coreconfig.CacheEnabled),
+		configuredCaches: map[string]CInterface{},
+	}
+	return cm
+}
+
+// should only be used for testing purpose
+func NewUmanagedCache(ctx context.Context, sizeLimit int64, ttl time.Duration) CInterface {
+	return &CCache{
+		ctx:      ctx,
+		name:     "cache.unmanaged",
+		cache:    ccache.New(ccache.Configure().MaxSize(sizeLimit)),
+		cacheTTL: ttl,
+		enabled:  true,
+	}
+}
diff --git a/internal/coreconfig/coreconfig.go b/internal/coreconfig/coreconfig.go
index 19d619bb1..56a0e0ae2 100644
--- a/internal/coreconfig/coreconfig.go
+++ b/internal/coreconfig/coreconfig.go
@@ -80,10 +80,6 @@ var (
 	APIRequestMaxTimeout = ffc("api.requestMaxTimeout")
 	// APIOASPanicOnMissingDescription controls whether the OpenAPI Spec generator will strongly enforce descriptions on every field or not
 	APIOASPanicOnMissingDescription = ffc("api.oas.panicOnMissingDescription")
-	// BatchCacheLimit max number of cache items for batches
-	BatchCacheLimit = ffc("batch.cache.limit")
-	// BatchCacheTTL time to live for cache of batches
-	BatchCacheTTL = ffc("batch.cache.ttl")
 	// BatchManagerReadPageSize is the size of each page of messages read from the database into memory when assembling batches
 	BatchManagerReadPageSize = ffc("batch.manager.readPageSize")
 	// BatchManagerReadPollTimeout is how long without any notifications of new messages to wait, before doing a page query
@@ -108,10 +104,7 @@ var (
 	BlobReceiverWorkerBatchTimeout = ffc("blobreceiver.worker.batchTimeout")
 	// BlobReceiverWorkerBatchMaxInserts
 	BlobReceiverWorkerBatchMaxInserts = ffc("blobreceiver.worker.batchMaxInserts")
-	// BlockchainEventCacheLimit max number of cache items for blockchain events
-	BlockchainEventCacheLimit = ffc("blockchainevent.cache.limit")
-	// BlockchainEventCacheTTL time to live for cache of blockchain events
-	BlockchainEventCacheTTL = ffc("blockchainevent.cache.ttl")
+
 	// BroadcastBatchAgentTimeout how long to keep around a batching agent for a sending identity before disposal
 	BroadcastBatchAgentTimeout = ffc("broadcast.batch.agentTimeout")
 	// BroadcastBatchSize is the maximum number of messages that can be packed into a batch
@@ -120,14 +113,56 @@ var (
 	BroadcastBatchPayloadLimit = ffc("broadcast.batch.payloadLimit")
 	// BroadcastBatchTimeout is the timeout to wait for a batch to fill, before sending
 	BroadcastBatchTimeout = ffc("broadcast.batch.timeout")
-	// CacheBlockchainTTL time to live of blockchain plugin cache
-	CacheBlockchainTTL = ffc("cache.blockchain.ttl")
-	// CacheBlockchainLimit max number of cache items for blockchain plugin cache
+
+	// CacheEnabled determines whether cache will be enabled or not, default to true
+	CacheEnabled = ffc("cache.enabled")
+
+	// AddressResolver cache config
+	CacheAddressResolverLimit = ffc("cache.addressresolver.limit")
+	CacheAddressResolverTTL   = ffc("cache.addressresolver.ttl")
+
+	// Batch cache config
+	CacheBatchLimit = ffc("cache.batch.limit")
+	CacheBatchTTL   = ffc("cache.batch.ttl")
+
+	// Transaction - BlockchainEvent cache config
+	CacheBlockchainEventLimit = ffc("cache.blockchainevent.limit")
+	CacheBlockchainEventTTL   = ffc("cache.blockchainevent.ttl")
+	// Transaction cache config
+	CacheTransactionSize = ffc("cache.transaction.size")
+	CacheTransactionTTL  = ffc("cache.transaction.ttl")
+
+	// EventListenerTopic cache config
+	CacheEventListenerTopicLimit = ffc("cache.eventlistenertopic.limit")
+	CacheEventListenerTopicTTL   = ffc("cache.eventlistenertopic.ttl")
+
+	// Group cache config
+	CacheGroupLimit = ffc("cache.group.limit")
+	CacheGroupTTL   = ffc("cache.group.ttl")
+
+	// IdentityManager identity cache config
+	CacheIdentityLimit = ffc("cache.identity.limit")
+	CacheIdentityTTL   = ffc("cache.identity.ttl")
+
+	// IdentityManager signing key cache config
+	CacheSigningKeyLimit = ffc("cache.signingkey.limit")
+	CacheSigningKeyTTL   = ffc("cache.signingkey.ttl")
+
+	// DataManager Message cache config
+	CacheMessageSize = ffc("cache.message.size")
+	CacheMessageTTL  = ffc("cache.message.ttl")
+	// DataManager Validator cache config
+	CacheValidatorSize = ffc("cache.validator.size")
+	CacheValidatorTTL  = ffc("cache.validator.ttl")
+
+	// Blockchain cache config
+	CacheBlockchainTTL   = ffc("cache.blockchain.ttl")
 	CacheBlockchainLimit = ffc("cache.blockchain.limit")
-	// CacheOperationsTTL time to live for cache of operations
-	CacheOperationsTTL = ffc("cache.operations.ttl")
-	// CacheOperationsLimit the max number of cache items for operations
+
+	// Operations cache config
 	CacheOperationsLimit = ffc("cache.operations.limit")
+	CacheOperationsTTL   = ffc("cache.operations.ttl")
+
 	// DownloadWorkerCount is the number of download workers created to pull data from shared storage to the local DX
 	DownloadWorkerCount = ffc("download.worker.count")
 	// DownloadWorkerQueueLength is the length of the work queue in the channel to the workers - defaults to 2x the worker count
@@ -212,14 +247,6 @@ var (
 	EventDispatcherRetryMaxDelay = ffc("event.dispatcher.retry.maxDelay")
 	// EventDBEventsBufferSize the size of the buffer of change events
 	EventDBEventsBufferSize = ffc("event.dbevents.bufferSize")
-	// EventListenerTopicCacheLimit max number of cache items for blockchain listener topics
-	EventListenerTopicCacheLimit = ffc("event.listenerTopic.cache.limit")
-	// EventListenerTopicCacheTTL time-to-live for for cache of blockchain listener topics
-	EventListenerTopicCacheTTL = ffc("event.listenerTopic.cache.ttl")
-	// GroupCacheLimit cache size for private group addresses
-	GroupCacheLimit = ffc("group.cache.limit")
-	// GroupCacheTTL cache time-to-live for private group addresses
-	GroupCacheTTL = ffc("group.cache.ttl")
 	// LegacyAdminEnabled is the deprecated key that pre-dates spi.enabled
 	LegacyAdminEnabled = ffc("admin.enabled")
 	// SPIEnabled determines whether the admin interface will be enabled or not
@@ -232,14 +259,6 @@ var (
 	SPIWebSocketReadBufferSize = ffc("spi.ws.readBufferSize")
 	// SPIWebSocketWriteBufferSize is the WebSocket write buffer size for the admin change-event WebSocket
 	SPIWebSocketWriteBufferSize = ffc("spi.ws.writeBufferSize")
-	// IdentityManagerCacheTTL the identity manager cache time to live
-	IdentityManagerCacheTTL = ffc("identity.manager.cache.ttl")
-	// IdentityManagerCacheLimit the identity manager cache limit in count of items
-	IdentityManagerCacheLimit = ffc("identity.manager.cache.limit")
-	// MessageCacheSize max size for cache of messages
-	MessageCacheSize = ffc("message.cache.size")
-	// MessageCacheTTL time-to-live for cache of messages
-	MessageCacheTTL = ffc("message.cache.ttl")
 	// MessageWriterCount
 	MessageWriterCount = ffc("message.writer.count")
 	// MessageWriterBatchTimeout
@@ -290,20 +309,13 @@ var (
 	SubscriptionsRetryMaxDelay = ffc("subscription.retry.maxDelay")
 	// SubscriptionsRetryFactor the backoff factor to use for retry of database operations
 	SubscriptionsRetryFactor = ffc("subscription.retry.factor")
-	// TransactionCacheSize max size for cache of transactions
-	TransactionCacheSize = ffc("transaction.cache.size")
-	// TransactionCacheTTL time-to-live for cache of transactions
-	TransactionCacheTTL = ffc("transaction.cache.ttl")
+
 	// AssetManagerKeyNormalization mechanism to normalize keys before using them. Valid options: "blockchain_plugin" - use blockchain plugin (default), "none" - do not attempt normalization
 	AssetManagerKeyNormalization = ffc("asset.manager.keyNormalization")
 	// UIEnabled set to false to disable the UI (default is true, so UI will be enabled if ui.path is valid)
 	UIEnabled = ffc("ui.enabled")
 	// UIPath the path on which to serve the UI
 	UIPath = ffc("ui.path")
-	// ValidatorCacheSize max size for cache of validators
-	ValidatorCacheSize = ffc("validator.cache.size")
-	// ValidatorCacheTTL time-to-live for cache of validators
-	ValidatorCacheTTL = ffc("validator.cache.ttl")
 )
 
 func setDefaults() {
@@ -315,8 +327,8 @@ func setDefaults() {
 	viper.SetDefault(string(APIMaxFilterSkip), 1000) // protects database (skip+limit pagination is not for bulk operations)
 	viper.SetDefault(string(APIRequestTimeout), "120s")
 	viper.SetDefault(string(AssetManagerKeyNormalization), "blockchain_plugin")
-	viper.SetDefault(string(BatchCacheLimit), 100)
-	viper.SetDefault(string(BatchCacheTTL), "5m")
+	viper.SetDefault(string(CacheBatchLimit), 100)
+	viper.SetDefault(string(CacheBatchTTL), "5m")
 	viper.SetDefault(string(BatchManagerReadPageSize), 100)
 	viper.SetDefault(string(BatchManagerReadPollTimeout), "30s")
 	viper.SetDefault(string(BatchManagerMinimumPollDelay), "100ms")
@@ -332,14 +344,17 @@ func setDefaults() {
 	viper.SetDefault(string(BlobReceiverWorkerBatchTimeout), "50ms")
 	viper.SetDefault(string(BlobReceiverWorkerCount), 5)
 	viper.SetDefault(string(BlobReceiverWorkerBatchMaxInserts), 200)
-	viper.SetDefault(string(BlockchainEventCacheLimit), 100)
-	viper.SetDefault(string(BlockchainEventCacheTTL), "5m")
+	viper.SetDefault(string(CacheBlockchainEventLimit), 100)
+	viper.SetDefault(string(CacheBlockchainEventTTL), "5m")
 	viper.SetDefault(string(BroadcastBatchAgentTimeout), "2m")
 	viper.SetDefault(string(BroadcastBatchSize), 200)
 	viper.SetDefault(string(BroadcastBatchPayloadLimit), "800Kb")
 	viper.SetDefault(string(BroadcastBatchTimeout), "1s")
 	viper.SetDefault(string(CacheBlockchainLimit), 100)
 	viper.SetDefault(string(CacheBlockchainTTL), "5m")
+	viper.SetDefault(string(CacheAddressResolverLimit), 1000)
+	viper.SetDefault(string(CacheAddressResolverTTL), "24h")
+	viper.SetDefault(string(CacheEnabled), true)
 	viper.SetDefault(string(CacheOperationsLimit), 200)
 	viper.SetDefault(string(CacheOperationsTTL), "5m")
 	viper.SetDefault(string(HistogramsMaxChartRows), 100)
@@ -365,17 +380,17 @@ func setDefaults() {
 	viper.SetDefault(string(EventDispatcherPollTimeout), "30s")
 	viper.SetDefault(string(EventTransportsEnabled), []string{"websockets", "webhooks"})
 	viper.SetDefault(string(EventTransportsDefault), "websockets")
-	viper.SetDefault(string(EventListenerTopicCacheLimit), 100)
-	viper.SetDefault(string(EventListenerTopicCacheTTL), "5m")
-	viper.SetDefault(string(GroupCacheLimit), 50)
-	viper.SetDefault(string(GroupCacheTTL), "1h")
+	viper.SetDefault(string(CacheEventListenerTopicLimit), 100)
+	viper.SetDefault(string(CacheEventListenerTopicTTL), "5m")
+	viper.SetDefault(string(CacheGroupLimit), 50)
+	viper.SetDefault(string(CacheGroupTTL), "1h")
 	viper.SetDefault(string(SPIEnabled), false)
 	viper.SetDefault(string(SPIWebSocketReadBufferSize), "16Kb")
 	viper.SetDefault(string(SPIWebSocketWriteBufferSize), "16Kb")
 	viper.SetDefault(string(SPIWebSocketBlockedWarnInterval), "1m")
 	viper.SetDefault(string(SPIWebSocketEventQueueLength), 250)
-	viper.SetDefault(string(MessageCacheSize), "50Mb")
-	viper.SetDefault(string(MessageCacheTTL), "5m")
+	viper.SetDefault(string(CacheMessageSize), "50Mb")
+	viper.SetDefault(string(CacheMessageTTL), "5m")
 	viper.SetDefault(string(MessageWriterBatchMaxInserts), 200)
 	viper.SetDefault(string(MessageWriterBatchTimeout), "10ms")
 	viper.SetDefault(string(MessageWriterCount), 5)
@@ -400,13 +415,15 @@ func setDefaults() {
 	viper.SetDefault(string(SubscriptionsRetryInitialDelay), "250ms")
 	viper.SetDefault(string(SubscriptionsRetryMaxDelay), "30s")
 	viper.SetDefault(string(SubscriptionsRetryFactor), 2.0)
-	viper.SetDefault(string(TransactionCacheSize), "1Mb")
-	viper.SetDefault(string(TransactionCacheTTL), "5m")
+	viper.SetDefault(string(CacheTransactionSize), "1Mb")
+	viper.SetDefault(string(CacheTransactionTTL), "5m")
 	viper.SetDefault(string(UIEnabled), true)
-	viper.SetDefault(string(ValidatorCacheSize), "1Mb")
-	viper.SetDefault(string(ValidatorCacheTTL), "1h")
-	viper.SetDefault(string(IdentityManagerCacheLimit), 100 /* items */)
-	viper.SetDefault(string(IdentityManagerCacheTTL), "1h")
+	viper.SetDefault(string(CacheValidatorSize), "1Mb")
+	viper.SetDefault(string(CacheValidatorTTL), "1h")
+	viper.SetDefault(string(CacheIdentityLimit), 100)
+	viper.SetDefault(string(CacheIdentityTTL), "1h")
+	viper.SetDefault(string(CacheSigningKeyLimit), 100)
+	viper.SetDefault(string(CacheSigningKeyTTL), "1h")
 }
 
 func Reset() {
diff --git a/internal/coremsgs/en_config_descriptions.go b/internal/coremsgs/en_config_descriptions.go
index b5495523f..58b3fb69f 100644
--- a/internal/coremsgs/en_config_descriptions.go
+++ b/internal/coremsgs/en_config_descriptions.go
@@ -93,16 +93,32 @@ var (
 	ConfigBlockchainFabricFabconnectURL          = ffc("config.blockchain.fabric.fabconnect.url", "The URL of the Fabconnect instance", "URL "+i18n.StringType)
 	ConfigBlockchainFabricFabconnectProxyURL     = ffc("config.blockchain.fabric.fabconnect.proxy.url", "Optional HTTP proxy server to use when connecting to Fabconnect", "URL "+i18n.StringType)
 
-	ConfigCacheBlockchainTTL  = ffc("config.cache.blockchain.ttl", "Time to live for blockchain cache items", i18n.StringType)
-	ConfigCacheBlockchainSize = ffc("config.cache.blockchain.limit", "Max number of cache items for blockchain plugin cache", i18n.StringType)
-
-	ConfigCacheOperationsTTL  = ffc("config.cache.operations.ttl", "Time to live for operation cache items", i18n.StringType)
-	ConfigCacheOperationsSize = ffc("config.cache.operations.limit", "Max number of cache items for operations", i18n.StringType)
-
-	ConfigCacheBatchLimit           = ffc("config.batch.cache.limit", "Max number of cache items for batches", i18n.StringType)
-	ConfigCacheBlockchainEventLimit = ffc("config.blockchainevent.cache.limit", "Max number of cache items for blockchain events", i18n.StringType)
-	ConfigCacheListenerTopicLimit   = ffc("config.event.listenerTopic.cache.limit", "Max number of cache items for blockchain listener topics", i18n.StringType)
-	ConfigCacheGroupLimit           = ffc("config.group.cache.limit", "Max number of cache items for private group addresses", i18n.StringType)
+	ConfigCacheEnabled = ffc("config.cache.enabled", "Enables caching, defaults to true", i18n.BooleanType)
+
+	ConfigCacheAddressResolverLimit    = ffc("config.cache.addressresolver.limit", "Max number of cached items for address resolver", i18n.IntType)
+	ConfigCacheAddressResolverTTL      = ffc("config.cache.addressresolver.ttl", "Time to live of cached items for address resolver", i18n.StringType)
+	ConfigCacheBatchLimit              = ffc("config.cache.batch.limit", "Max number of cached items for batches", i18n.IntType)
+	ConfigCacheBatchTTL                = ffc("config.cache.batch.ttl", "Time to live of cache items for batches", i18n.StringType)
+	ConfigCacheBlockchainEventLimit    = ffc("config.cache.blockchainevent.limit", "Max number of cached blockchain events for transactions", i18n.IntType)
+	ConfigCacheBlockchainEventTTL      = ffc("config.cache.blockchainevent.ttl", "Time to live of cached blockchain events for transactions", i18n.StringType)
+	ConfigCacheTransactionSize         = ffc("config.cache.transaction.size", "Max size of cached transactions", i18n.ByteSizeType)
+	ConfigCacheTransactionTTL          = ffc("config.cache.transaction.ttl", "Time to live of cached transactions", i18n.StringType)
+	ConfigCacheEventListenerTopicLimit = ffc("config.cache.eventlistenertopic.limit", "Max number of cached items for blockchain listener topics", i18n.IntType)
+	ConfigCacheEventListenerTopicTTL   = ffc("config.cache.eventlistenertopic.ttl", "Time to live of cached items for blockchain listener topics", i18n.StringType)
+	ConfigCacheGroupLimit              = ffc("config.cache.group.limit", "Max number of cached items for groups", i18n.IntType)
+	ConfigCacheGroupTTL                = ffc("config.cache.group.ttl", "Time to live of cached items for groups", i18n.StringType)
+	ConfigCacheIdentityLimit           = ffc("config.cache.identity.limit", "Max number of cached identities for identity manager", i18n.IntType)
+	ConfigCacheIdentityTTL             = ffc("config.cache.identity.ttl", "Time to live of cached identities for identity manager", i18n.StringType)
+	ConfigCacheSigningKeyLimit         = ffc("config.cache.signingkey.limit", "Max number of cached signing keys for identity manager", i18n.IntType)
+	ConfigCacheSigningKeyTTL           = ffc("config.cache.signingkey.ttl", "Time to live of cached signing keys for identity manager", i18n.StringType)
+	ConfigCacheMessageSize             = ffc("config.cache.message.size", "Max size of cached messages for data manager", i18n.ByteSizeType)
+	ConfigCacheMessageTTL              = ffc("config.cache.message.ttl", "Time to live of cached messages for data manager", i18n.StringType)
+	ConfigCacheValidatorSize           = ffc("config.cache.validator.size", "Max size of cached validators for data manager", i18n.ByteSizeType)
+	ConfigCacheValidatorTTL            = ffc("config.cache.validator.ttl", "Time to live of cached validators for data manager", i18n.StringType)
+	ConfigCacheBlockchainLimit         = ffc("config.cache.blockchain.limit", "Max number of cached items for blockchain", i18n.IntType)
+	ConfigCacheBlockchainTTL           = ffc("config.cache.blockchain.ttl", "Time to live of cached items for blockchain", i18n.StringType)
+	ConfigCacheOperationsLimit         = ffc("config.cache.operations.limit", "Max number of cached items for operations", i18n.IntType)
+	ConfigCacheOperationsTTL           = ffc("config.cache.operations.ttl", "Time to live of cached items for operations", i18n.StringType)
 
 	ConfigPluginDatabase     = ffc("config.plugins.database", "The list of configured Database plugins", i18n.StringType)
 	ConfigPluginDatabaseName = ffc("config.plugins.database[].name", "The name of the Database plugin", i18n.StringType)
@@ -233,7 +249,6 @@ var (
 	ConfigPluginIdentityType = ffc("config.plugins.identity[].type", "The type of a configured Identity plugin", i18n.StringType)
 	ConfigPluginIdentityName = ffc("config.plugins.identity[].name", "The name of a configured Identity plugin", i18n.StringType)
 
-	ConfigIdentityManagerCacheLimit              = ffc("config.identity.manager.cache.limit", "The identity manager cache limit in count of items", i18n.IntType)
 	ConfigIdentityManagerLegacySystemIdentitites = ffc("config.identity.manager.legacySystemIdentities", "Whether the identity manager should resolve legacy identities registered on the ff_system namespace", i18n.BooleanType)
 
 	ConfigLogCompress   = ffc("config.log.compress", "Determines if the rotated log files should be compressed using gzip", i18n.BooleanType)
diff --git a/internal/coremsgs/en_error_messages.go b/internal/coremsgs/en_error_messages.go
index d11dfb571..43acd4ee5 100644
--- a/internal/coremsgs/en_error_messages.go
+++ b/internal/coremsgs/en_error_messages.go
@@ -27,239 +27,243 @@ var ffe = func(key, translation string, statusHint ...int) i18n.ErrorMessageKey
 
 //revive:disable
 var (
-	MsgConfigFailed                      = ffe("FF10101", "Failed to read config")
-	MsgJSONDecodeFailed                  = ffe("FF10103", "Failed to decode input JSON")
-	MsgTLSConfigFailed                   = ffe("FF10105", "Failed to initialize TLS configuration")
-	MsgWebsocketClientError              = ffe("FF10108", "Error received from WebSocket client: %s")
-	Msg404NotFound                       = ffe("FF10109", "Not found", 404)
-	MsgUnknownBlockchainPlugin           = ffe("FF10110", "Unknown blockchain plugin: %s")
-	MsgEthconnectRESTErr                 = ffe("FF10111", "Error from ethconnect: %s")
-	MsgDBInitFailed                      = ffe("FF10112", "Database initialization failed")
-	MsgDBQueryBuildFailed                = ffe("FF10113", "Database query builder failed")
-	MsgDBBeginFailed                     = ffe("FF10114", "Database begin transaction failed")
-	MsgDBQueryFailed                     = ffe("FF10115", "Database query failed")
-	MsgDBInsertFailed                    = ffe("FF10116", "Database insert failed")
-	MsgDBUpdateFailed                    = ffe("FF10117", "Database update failed")
-	MsgDBDeleteFailed                    = ffe("FF10118", "Database delete failed")
-	MsgDBCommitFailed                    = ffe("FF10119", "Database commit failed")
-	MsgDBMissingJoin                     = ffe("FF10120", "Database missing expected join entry in table '%s' for id '%s'")
-	MsgDBReadErr                         = ffe("FF10121", "Database resultset read error from table '%s'")
-	MsgUnknownDatabasePlugin             = ffe("FF10122", "Unknown database plugin '%s'")
-	MsgNullDataReferenceID               = ffe("FF10123", "Data id is null in message data reference %d")
-	MsgDupDataReferenceID                = ffe("FF10124", "Duplicate data ID in message '%s'")
-	MsgScanFailed                        = ffe("FF10125", "Failed to restore type '%T' into '%T'")
-	MsgUnregisteredBatchType             = ffe("FF10126", "Unregistered batch type '%s'")
-	MsgBatchDispatchTimeout              = ffe("FF10127", "Timed out dispatching work to batch")
-	MsgInitializationNilDepError         = ffe("FF10128", "Initialization failed in %s due to unmet dependency")
-	MsgNilResponseNon204                 = ffe("FF10129", "No output from API call")
-	MsgDataNotFound                      = ffe("FF10133", "Data not found for message %s", 400)
-	MsgUnknownSharedStoragePlugin        = ffe("FF10134", "Unknown Shared Storage plugin '%s'")
-	MsgIPFSHashDecodeFailed              = ffe("FF10135", "Failed to decode IPFS hash into 32byte value '%s'")
-	MsgIPFSRESTErr                       = ffe("FF10136", "Error from IPFS: %s")
-	MsgSerializationFailed               = ffe("FF10137", "Serialization failed")
-	MsgMissingPluginConfig               = ffe("FF10138", "Missing configuration '%s' for %s")
-	MsgMissingDataHashIndex              = ffe("FF10139", "Missing data hash for index '%d' in message", 400)
-	MsgInvalidEthAddress                 = ffe("FF10141", "Supplied ethereum address is invalid", 400)
-	Msg404NoResult                       = ffe("FF10143", "No result found", 404)
-	MsgUnsupportedSQLOpInFilter          = ffe("FF10150", "No SQL mapping implemented for filter operator '%s'", 400)
-	MsgFilterSortDesc                    = ffe("FF10154", "Sort field. For multi-field sort use comma separated values (or multiple query values) with '-' prefix for descending")
-	MsgContextCanceled                   = ffe("FF00154", "Context cancelled")
-	MsgDBMigrationFailed                 = ffe("FF10163", "Database migration failed")
-	MsgHashMismatch                      = ffe("FF10164", "Hash mismatch")
-	MsgDefaultNamespaceNotFound          = ffe("FF10166", "namespaces.default '%s' must be included in the namespaces.predefined configuration")
-	MsgEventTypesParseFail               = ffe("FF10168", "Unable to parse list of event types", 400)
-	MsgUnknownEventType                  = ffe("FF10169", "Unknown event type '%s'", 400)
-	MsgIDMismatch                        = ffe("FF10170", "ID mismatch")
-	MsgRegexpCompileFailed               = ffe("FF10171", "Unable to compile '%s' regexp '%s'")
-	MsgUnknownEventTransportPlugin       = ffe("FF10172", "Unknown event transport plugin: %s")
-	MsgWSConnectionNotActive             = ffe("FF10173", "Websocket connection '%s' no longer active")
-	MsgWSSubAlreadyInFlight              = ffe("FF10174", "Websocket subscription '%s' already has a message in flight")
-	MsgWSMsgSubNotMatched                = ffe("FF10175", "Acknowledgment does not match an inflight event + subscription")
-	MsgWSClientSentInvalidData           = ffe("FF10176", "Invalid data")
-	MsgWSClientUnknownAction             = ffe("FF10177", "Unknown action '%s'")
-	MsgWSInvalidStartAction              = ffe("FF10178", "A start action must set namespace and either a name or ephemeral=true")
-	MsgWSAutoAckChanged                  = ffe("FF10179", "The autoack option must be set consistently on all start requests")
-	MsgWSAutoAckEnabled                  = ffe("FF10180", "The autoack option is enabled on this connection")
-	MsgConnSubscriptionNotStarted        = ffe("FF10181", "Subscription %v is not started on connection")
-	MsgDispatcherClosing                 = ffe("FF10182", "Event dispatcher closing")
-	MsgMaxFilterSkip                     = ffe("FF10183", "You have reached the maximum pagination limit for this query (%d)", 400)
-	MsgMaxFilterLimit                    = ffe("FF10184", "Your query exceeds the maximum filter limit (%d)", 400)
-	MsgAPIServerStaticFail               = ffe("FF10185", "An error occurred loading static content", 500)
-	MsgEventListenerClosing              = ffe("FF10186", "Event listener closing")
-	MsgNamespaceDoesNotExist             = ffe("FF10187", "Namespace does not exist", 404)
-	MsgInvalidSubscription               = ffe("FF10189", "Invalid subscription", 400)
-	MsgMismatchedTransport               = ffe("FF10190", "Connection ID '%s' appears not to be unique between transport '%s' and '%s'", 400)
-	MsgInvalidFirstEvent                 = ffe("FF10191", "Invalid firstEvent definition - must be 'newest','oldest' or a sequence number", 400)
-	MsgNumberMustBeGreaterEqual          = ffe("FF10192", "Number must be greater than or equal to %d", 400)
-	MsgAlreadyExists                     = ffe("FF10193", "A %s with name '%s:%s' already exists", 409)
-	MsgJSONValidatorBadRef               = ffe("FF10194", "Cannot use JSON validator for data with type '%s' and validator reference '%v'", 400)
-	MsgDatatypeNotFound                  = ffe("FF10195", "Datatype '%v' not found", 400)
-	MsgSchemaLoadFailed                  = ffe("FF10196", "Datatype '%s' schema invalid", 400)
-	MsgDataCannotBeValidated             = ffe("FF10197", "Data cannot be validated", 400)
-	MsgJSONDataInvalidPerSchema          = ffe("FF10198", "Data does not conform to the JSON schema of datatype '%s': %s", 400)
-	MsgDataValueIsNull                   = ffe("FF10199", "Data value is null", 400)
-	MsgDataInvalidHash                   = ffe("FF10201", "Invalid data: hashes do not match Hash=%s Expected=%s", 400)
-	MsgDataReferenceUnresolvable         = ffe("FF10204", "Data reference %d cannot be resolved", 400)
-	MsgDataMissing                       = ffe("FF10205", "Data entry %d has neither 'id' to refer to existing data, or 'value' to include in-line JSON data", 400)
-	MsgAuthorInvalid                     = ffe("FF10206", "Invalid author specified", 400)
-	MsgNoTransaction                     = ffe("FF10207", "Message does not have a transaction", 404)
-	MsgBatchNotSet                       = ffe("FF10208", "Message does not have an assigned batch", 404)
-	MsgBatchNotFound                     = ffe("FF10209", "Batch '%s' not found for message", 500)
-	MsgBatchTXNotSet                     = ffe("FF10210", "Batch '%s' does not have an assigned transaction", 404)
-	MsgOwnerMissing                      = ffe("FF10211", "Owner missing", 400)
-	MsgUnknownIdentityPlugin             = ffe("FF10212", "Unknown Identity plugin '%s'")
-	MsgUnknownDataExchangePlugin         = ffe("FF10213", "Unknown Data Exchange plugin '%s'")
-	MsgParentIdentityNotFound            = ffe("FF10214", "Identity '%s' not found in identity chain for %s '%s'")
-	MsgInvalidSigningIdentity            = ffe("FF10215", "Invalid signing identity")
-	MsgNodeAndOrgIDMustBeSet             = ffe("FF10216", "node.name, org.name and org.key must be configured first", 409)
-	MsgBlobStreamingFailed               = ffe("FF10217", "Blob streaming terminated with error", 500)
-	MsgNodeNotFound                      = ffe("FF10224", "Node with name or identity '%s' not found", 400)
-	MsgLocalNodeResolveFailed            = ffe("FF10225", "Unable to find local node to add to group. Check the status API to confirm the node is registered", 500)
-	MsgGroupNotFound                     = ffe("FF10226", "Group '%s' not found", 404)
-	MsgDXRESTErr                         = ffe("FF10229", "Error from data exchange: %s")
-	MsgInvalidHex                        = ffe("FF10231", "Invalid hex supplied", 400)
-	MsgInvalidWrongLenB32                = ffe("FF00107", "Byte length must be 32 (64 hex characters)", 400)
-	MsgNodeNotFoundInOrg                 = ffe("FF10233", "Unable to find any nodes owned by org '%s', or parent orgs", 400)
-	MsgDXBadResponse                     = ffe("FF10237", "Unexpected '%s' in data exchange response: %s")
-	MsgDXBadHash                         = ffe("FF10238", "Unexpected hash returned from data exchange upload. Hash=%s Expected=%s")
-	MsgBlobNotFound                      = ffe("FF10239", "No blob has been uploaded or confirmed received, with hash=%s", 404)
-	MsgDownloadBlobFailed                = ffe("FF10240", "Error download blob with reference '%s' from local data exchange")
-	MsgDataDoesNotHaveBlob               = ffe("FF10241", "Data does not have a blob attachment", 404)
-	MsgWebhookURLEmpty                   = ffe("FF10242", "Webhook subscription option 'url' cannot be empty", 400)
-	MsgWebhookInvalidStringMap           = ffe("FF10243", "Webhook subscription option '%s' must be map of string values. %s=%T", 400)
-	MsgWebsocketsNoData                  = ffe("FF10244", "Websockets subscriptions do not support streaming the full data payload, just the references (withData must be false)", 400)
-	MsgWebhooksWithData                  = ffe("FF10245", "Webhook subscriptions require the full data payload (withData must be true)", 400)
-	MsgWebhooksReplyBadJSON              = ffe("FF10257", "Failed to process reply from webhook as JSON")
-	MsgRequestTimeout                    = ffe("FF10260", "The request with id '%s' timed out after %.2fms", 408)
-	MsgRequestReplyTagRequired           = ffe("FF10261", "For request messages 'header.tag' must be set on the request message to route it to a suitable responder", 400)
-	MsgRequestCannotHaveCID              = ffe("FF10262", "For request messages 'header.cid' must be unset", 400)
-	MsgSystemTransportInternal           = ffe("FF10266", "You cannot create subscriptions on the system events transport")
-	MsgFilterCountNotSupported           = ffe("FF10267", "This query does not support generating a count of all results")
-	MsgRejected                          = ffe("FF10269", "Message with ID '%s' was rejected. Please check the FireFly logs for more information")
-	MsgRequestMustBePrivate              = ffe("FF10271", "For request messages you must specify a group of private recipients", 400)
-	MsgUnknownTokensPlugin               = ffe("FF10272", "Unknown tokens plugin '%s'", 400)
-	MsgMissingTokensPluginConfig         = ffe("FF10273", "Invalid tokens configuration - name and plugin are required", 400)
-	MsgTokensRESTErr                     = ffe("FF10274", "Error from tokens service: %s")
-	MsgTokenPoolDuplicate                = ffe("FF10275", "Duplicate token pool: %s")
-	MsgTokenPoolRejected                 = ffe("FF10276", "Token pool with ID '%s' was rejected. Please check the FireFly logs for more information")
-	MsgIdentityNotFoundByString          = ffe("FF10277", "Identity could not be resolved via lookup string '%s'")
-	MsgAuthorOrgSigningKeyMismatch       = ffe("FF10279", "Author organization '%s' is not associated with signing key '%s'")
-	MsgCannotTransferToSelf              = ffe("FF10280", "From and to addresses must be different", 400)
-	MsgLocalOrgLookupFailed              = ffe("FF10281", "Unable to resolve the local org '%s' by the configured signing key on the node. Please confirm the org is registered with key '%s'", 500)
-	MsgFabconnectRESTErr                 = ffe("FF10284", "Error from fabconnect: %s")
-	MsgInvalidIdentity                   = ffe("FF10285", "Supplied Fabric signer identity is invalid", 400)
-	MsgFailedToDecodeCertificate         = ffe("FF10286", "Failed to decode certificate: %s", 500)
-	MsgInvalidMessageType                = ffe("FF10287", "Invalid message type - allowed types are %s", 400)
-	MsgWSClosed                          = ffe("FF10290", "Websocket closed")
-	MsgFieldNotSpecified                 = ffe("FF10292", "Field '%s' must be specified", 400)
-	MsgTokenPoolNotConfirmed             = ffe("FF10293", "Token pool is not yet confirmed")
-	MsgHistogramCollectionParam          = ffe("FF10297", "Collection to fetch")
-	MsgInvalidNumberOfIntervals          = ffe("FF10298", "Number of time intervals must be between %d and %d", 400)
-	MsgInvalidChartNumberParam           = ffe("FF10299", "Invalid %s. Must be a number.", 400)
-	MsgHistogramInvalidTimes             = ffe("FF10300", "Start time must be before end time", 400)
-	MsgUnsupportedCollection             = ffe("FF10301", "%s collection is not supported", 400)
-	MsgContractInterfaceExists           = ffe("FF10302", "A contract interface already exists in the namespace: '%s' with name: '%s' and version: '%s'", 409)
-	MsgContractInterfaceNotFound         = ffe("FF10303", "Contract interface %s not found", 404)
-	MsgContractMissingInputArgument      = ffe("FF10304", "Missing required input argument '%s'", 400)
-	MsgContractWrongInputType            = ffe("FF10305", "Input '%v' is of type '%v' not expected type of '%v'", 400)
-	MsgContractMissingInputField         = ffe("FF10306", "Expected object of type '%v' to contain field named '%v' but it was missing", 400)
-	MsgContractMapInputType              = ffe("FF10307", "Unable to map input type '%v' to known FireFly type - was expecting '%v'", 400)
-	MsgContractByteDecode                = ffe("FF10308", "Unable to decode field '%v' as bytes", 400)
-	MsgContractInternalType              = ffe("FF10309", "Input '%v' of type '%v' is not compatible blockchain internalType of '%v'", 400)
-	MsgContractLocationInvalid           = ffe("FF10310", "Failed to validate contract location: %v", 400)
-	MsgContractParamInvalid              = ffe("FF10311", "Failed to validate contract param: %v", 400)
-	MsgContractListenerNameExists        = ffe("FF10312", "A contract listener already exists in the namespace: '%s' with name: '%s'", 409)
-	MsgContractMethodNotSet              = ffe("FF10313", "Either an interface reference and method path, or in-line method definition, must be supplied on invoke contract request", 400)
-	MsgContractMethodResolveError        = ffe("FF10315", "Unable to resolve contract method: %s", 400)
-	MsgContractLocationExists            = ffe("FF10316", "The contract location cannot be changed after it is created", 400)
-	MsgListenerNoEvent                   = ffe("FF10317", "Either an interface reference and event path, or in-line event definition must be supplied when creating a contract listener", 400)
-	MsgListenerEventNotFound             = ffe("FF10318", "No event was found in namespace '%s' with id '%s'", 400)
-	MsgEventNameMustBeSet                = ffe("FF10319", "Event name must be set", 400)
-	MsgMethodNameMustBeSet               = ffe("FF10320", "Method name must be set", 400)
-	MsgContractEventResolveError         = ffe("FF10321", "Unable to resolve contract event", 400)
-	MsgQueryOpUnsupportedMod             = ffe("FF10322", "Operation '%s' on '%s' does not support modifiers", 400)
-	MsgDXBadSize                         = ffe("FF10323", "Unexpected size returned from data exchange upload. Size=%d Expected=%d")
-	MsgTooLargeBroadcast                 = ffe("FF10327", "Message size %.2fkb is too large for the max broadcast batch size of %.2fkb", 400)
-	MsgTooLargePrivate                   = ffe("FF10328", "Message size %.2fkb is too large for the max private message size of %.2fkb", 400)
-	MsgManifestMismatch                  = ffe("FF10329", "Manifest mismatch overriding '%s' status as failure: '%s'", 400)
-	MsgFFIValidationFail                 = ffe("FF10331", "Field '%s' does not validate against the provided schema", 400)
-	MsgFFISchemaParseFail                = ffe("FF10332", "Failed to parse schema for param '%s'", 400)
-	MsgFFISchemaCompileFail              = ffe("FF10333", "Failed compile schema for param '%s'", 400)
-	MsgPluginInitializationFailed        = ffe("FF10334", "Plugin initialization error", 500)
-	MsgUnknownTransactionType            = ffe("FF10336", "Unknown transaction type '%s'", 400)
-	MsgGoTemplateCompileFailed           = ffe("FF10337", "Go template compilation for '%s' failed: %s", 500)
-	MsgGoTemplateExecuteFailed           = ffe("FF10338", "Go template execution for '%s' failed: %s", 500)
-	MsgAddressResolveFailed              = ffe("FF10339", "Failed to resolve signing key string '%s': %s", 500)
-	MsgAddressResolveBadStatus           = ffe("FF10340", "Failed to resolve signing key string '%s' [%d]: %s", 500)
-	MsgAddressResolveBadResData          = ffe("FF10341", "Failed to resolve signing key string '%s' - invalid address returned '%s': %s", 500)
-	MsgDXNotInitialized                  = ffe("FF10342", "Data exchange is initializing")
-	MsgGroupRequired                     = ffe("FF10344", "Group must be set", 400)
-	MsgDBLockFailed                      = ffe("FF10345", "Database lock failed")
-	MsgFFIGenerationFailed               = ffe("FF10346", "Error generating smart contract interface: %s", 400)
-	MsgFFIGenerationUnsupported          = ffe("FF10347", "Smart contract interface generation is not supported by this blockchain plugin", 400)
-	MsgBlobHashMismatch                  = ffe("FF10348", "Blob hash mismatch sent=%s received=%s", 400)
-	MsgDIDResolverUnknown                = ffe("FF10349", "DID resolver unknown for DID: %s", 400)
-	MsgIdentityNotOrg                    = ffe("FF10350", "Identity '%s' with DID '%s' is not an organization", 400)
-	MsgIdentityNotNode                   = ffe("FF10351", "Identity '%s' with DID '%s' is not a node", 400)
-	MsgBlockchainKeyNotSet               = ffe("FF10352", "No blockchain key specified", 400)
-	MsgNoVerifierForIdentity             = ffe("FF10353", "No %s verifier registered for identity %s", 400)
-	MsgNodeMissingBlockchainKey          = ffe("FF10354", "No default signing key or organization signing key configured for this namespace", 400)
-	MsgAuthorRegistrationMismatch        = ffe("FF10355", "Verifier '%s' cannot be used for signing with author '%s'. Verifier registered to '%s'", 400)
-	MsgAuthorMissingForKey               = ffe("FF10356", "Key '%s' has not been registered by any identity, and a separate 'author' was not supplied", 404)
-	MsgAuthorIncorrectForRootReg         = ffe("FF10357", "Author namespace '%s' and DID '%s' combination invalid for root organization registration", 400)
-	MsgKeyIdentityMissing                = ffe("FF10358", "Identity owner of key '%s' not found", 500)
-	MsgIdentityChainLoop                 = ffe("FF10364", "Loop detected on identity %s in chain for %s (%s)", 400)
-	MsgInvalidIdentityParentType         = ffe("FF10365", "Parent %s (%s) of type %s is invalid for child %s (%s) of type", 400)
-	MsgParentIdentityMissingClaim        = ffe("FF10366", "Parent %s (%s) is invalid (missing claim)", 400)
-	MsgDXInfoMissingID                   = ffe("FF10367", "Data exchange endpoint info missing 'id' field", 500)
-	MsgEventNotFound                     = ffe("FF10370", "Event with name '%s' not found", 400)
-	MsgOperationNotSupported             = ffe("FF10371", "Operation not supported: %s", 400)
-	MsgFailedToRetrieve                  = ffe("FF10372", "Failed to retrieve %s %s", 500)
-	MsgBlobMissingPublic                 = ffe("FF10373", "Blob for data %s missing public payload reference while flushing batch", 500)
-	MsgDBMultiRowConfigError             = ffe("FF10374", "Database invalid configuration - using multi-row insert on DB plugin that does not support query syntax for input")
-	MsgDBNoSequence                      = ffe("FF10375", "Failed to retrieve sequence for insert row %d (could mean duplicate insert)", 500)
-	MsgDownloadSharedFailed              = ffe("FF10376", "Error downloading data with reference '%s' from shared storage")
-	MsgDownloadBatchMaxBytes             = ffe("FF10377", "Error downloading batch with reference '%s' from shared storage - maximum size limit reached")
-	MsgOperationDataIncorrect            = ffe("FF10378", "Operation data type incorrect: %T", 400)
-	MsgDataMissingBlobHash               = ffe("FF10379", "Blob for data %s cannot be transferred as it is missing a hash", 500)
-	MsgUnexpectedDXMessageType           = ffe("FF10380", "Unexpected websocket event type from DX plugin: %s", 500)
-	MsgContractListenerExists            = ffe("FF10383", "A contract listener already exists for this combination of topic + location + event", 409)
-	MsgInvalidOutputOption               = ffe("FF10385", "invalid output option '%s'")
-	MsgInvalidPluginConfiguration        = ffe("FF10386", "Invalid %s plugin configuration - name and type are required")
-	MsgReferenceMarkdownMissing          = ffe("FF10387", "Reference markdown file missing: '%s'")
-	MsgFFSystemReservedName              = ffe("FF10388", "Invalid namespace configuration - %s is a reserved name")
-	MsgInvalidNamespaceMode              = ffe("FF10389", "Invalid %s namespace configuration - unknown mode")
-	MsgNamespaceUnknownPlugin            = ffe("FF10390", "Invalid %s namespace configuration - unknown plugin %s")
-	MsgNamespaceWrongPluginsMultiparty   = ffe("FF10391", "Invalid %s namespace configuration - multiparty mode requires database, blockchain, shared storage, and data exchange plugins")
-	MsgNamespaceNoDatabase               = ffe("FF10392", "Invalid %s namespace configuration - a database plugin is required")
-	MsgNamespaceMultiplePluginType       = ffe("FF10394", "Invalid %s namespace configuration - multiple %s plugins provided")
-	MsgDuplicatePluginName               = ffe("FF10395", "Invalid plugin configuration - plugin with name %s already exists")
-	MsgInvalidFireFlyContractIndex       = ffe("FF10396", "No configuration found for FireFly contract at %s")
-	MsgUnrecognizedNetworkAction         = ffe("FF10397", "Unrecognized network action: %s", 400)
-	MsgOverrideExistingFieldCustomOption = ffe("FF10398", "Cannot override existing field with custom option named '%s'", 400)
-	MsgTerminateNotSupported             = ffe("FF10399", "The 'terminate' operation to mark a switchover of smart contracts is not supported on namespace %s", 400)
-	MsgDefRejectedBadPayload             = ffe("FF10400", "Rejected %s message '%s' - invalid payload")
-	MsgDefRejectedAuthorBlank            = ffe("FF10401", "Rejected %s message '%s' - author is blank")
-	MsgDefRejectedSignatureMismatch      = ffe("FF10402", "Rejected %s message '%s' - signature mismatch")
-	MsgDefRejectedValidateFail           = ffe("FF10403", "Rejected %s '%s' - validate failed: %s")
-	MsgDefRejectedIDMismatch             = ffe("FF10404", "Rejected %s '%s' - ID mismatch with existing record")
-	MsgDefRejectedLocationMismatch       = ffe("FF10405", "Rejected %s '%s' - location mismatch with existing record")
-	MsgDefRejectedSchemaFail             = ffe("FF10406", "Rejected %s '%s' - schema check: %s")
-	MsgDefRejectedConflict               = ffe("FF10407", "Rejected %s '%s' - conflicts with existing: %s")
-	MsgDefRejectedIdentityNotFound       = ffe("FF10408", "Rejected %s '%s' - identity not found: %s")
-	MsgDefRejectedWrongAuthor            = ffe("FF10409", "Rejected %s '%s' - wrong author: %s")
-	MsgDefRejectedHashMismatch           = ffe("FF10410", "Rejected %s '%s' - hash mismatch: %s != %s")
-	MsgInvalidNamespaceUUID              = ffe("FF10411", "Expected 'namespace:' prefix on ID '%s'", 400)
-	MsgBadNetworkVersion                 = ffe("FF10412", "Bad network version: %s")
-	MsgDefinitionRejected                = ffe("FF10413", "Definition rejected")
-	MsgActionNotSupported                = ffe("FF10414", "This action is not supported in this namespace", 400)
-	MsgMessagesNotSupported              = ffe("FF10415", "Messages are not supported in this namespace", 400)
-	MsgInvalidSubscriptionForNetwork     = ffe("FF10416", "Subscription name '%s' is invalid according to multiparty network rules in effect (network version=%d)")
-	MsgBlockchainNotConfigured           = ffe("FF10417", "No blockchain plugin configured")
-	MsgInvalidBatchPinEvent              = ffe("FF10418", "BatchPin event is not valid - %s (%s): %s")
-	MsgDuplicatePluginBroadcastName      = ffe("FF10419", "Invalid %s plugin broadcast name: %s - broadcast names must be unique")
-	MsgInvalidConnectorName              = ffe("FF10420", "Could not find name %s for %s connector")
-	MsgCannotInitLegacyNS                = ffe("FF10421", "could not initialize legacy '%s' namespace - found conflicting V1 multi-party config in %s and %s")
-	MsgInvalidGroupMember                = ffe("FF10422", "invalid group member - node '%s' is not owned by '%s' or any of its ancestors")
-	MsgContractListenerStatusInvalid     = ffe("FF10423", "Failed to validate contract listener status: %v", 400)
+	MsgConfigFailed                       = ffe("FF10101", "Failed to read config")
+	MsgJSONDecodeFailed                   = ffe("FF10103", "Failed to decode input JSON")
+	MsgTLSConfigFailed                    = ffe("FF10105", "Failed to initialize TLS configuration")
+	MsgWebsocketClientError               = ffe("FF10108", "Error received from WebSocket client: %s")
+	Msg404NotFound                        = ffe("FF10109", "Not found", 404)
+	MsgUnknownBlockchainPlugin            = ffe("FF10110", "Unknown blockchain plugin: %s")
+	MsgEthconnectRESTErr                  = ffe("FF10111", "Error from ethconnect: %s")
+	MsgDBInitFailed                       = ffe("FF10112", "Database initialization failed")
+	MsgDBQueryBuildFailed                 = ffe("FF10113", "Database query builder failed")
+	MsgDBBeginFailed                      = ffe("FF10114", "Database begin transaction failed")
+	MsgDBQueryFailed                      = ffe("FF10115", "Database query failed")
+	MsgDBInsertFailed                     = ffe("FF10116", "Database insert failed")
+	MsgDBUpdateFailed                     = ffe("FF10117", "Database update failed")
+	MsgDBDeleteFailed                     = ffe("FF10118", "Database delete failed")
+	MsgDBCommitFailed                     = ffe("FF10119", "Database commit failed")
+	MsgDBMissingJoin                      = ffe("FF10120", "Database missing expected join entry in table '%s' for id '%s'")
+	MsgDBReadErr                          = ffe("FF10121", "Database resultset read error from table '%s'")
+	MsgUnknownDatabasePlugin              = ffe("FF10122", "Unknown database plugin '%s'")
+	MsgNullDataReferenceID                = ffe("FF10123", "Data id is null in message data reference %d")
+	MsgDupDataReferenceID                 = ffe("FF10124", "Duplicate data ID in message '%s'")
+	MsgScanFailed                         = ffe("FF10125", "Failed to restore type '%T' into '%T'")
+	MsgUnregisteredBatchType              = ffe("FF10126", "Unregistered batch type '%s'")
+	MsgBatchDispatchTimeout               = ffe("FF10127", "Timed out dispatching work to batch")
+	MsgInitializationNilDepError          = ffe("FF10128", "Initialization failed in %s due to unmet dependency")
+	MsgNilResponseNon204                  = ffe("FF10129", "No output from API call")
+	MsgDataNotFound                       = ffe("FF10133", "Data not found for message %s", 400)
+	MsgUnknownSharedStoragePlugin         = ffe("FF10134", "Unknown Shared Storage plugin '%s'")
+	MsgIPFSHashDecodeFailed               = ffe("FF10135", "Failed to decode IPFS hash into 32byte value '%s'")
+	MsgIPFSRESTErr                        = ffe("FF10136", "Error from IPFS: %s")
+	MsgSerializationFailed                = ffe("FF10137", "Serialization failed")
+	MsgMissingPluginConfig                = ffe("FF10138", "Missing configuration '%s' for %s")
+	MsgMissingDataHashIndex               = ffe("FF10139", "Missing data hash for index '%d' in message", 400)
+	MsgInvalidEthAddress                  = ffe("FF10141", "Supplied ethereum address is invalid", 400)
+	Msg404NoResult                        = ffe("FF10143", "No result found", 404)
+	MsgUnsupportedSQLOpInFilter           = ffe("FF10150", "No SQL mapping implemented for filter operator '%s'", 400)
+	MsgFilterSortDesc                     = ffe("FF10154", "Sort field. For multi-field sort use comma separated values (or multiple query values) with '-' prefix for descending")
+	MsgContextCanceled                    = ffe("FF00154", "Context cancelled")
+	MsgDBMigrationFailed                  = ffe("FF10163", "Database migration failed")
+	MsgHashMismatch                       = ffe("FF10164", "Hash mismatch")
+	MsgDefaultNamespaceNotFound           = ffe("FF10166", "namespaces.default '%s' must be included in the namespaces.predefined configuration")
+	MsgEventTypesParseFail                = ffe("FF10168", "Unable to parse list of event types", 400)
+	MsgUnknownEventType                   = ffe("FF10169", "Unknown event type '%s'", 400)
+	MsgIDMismatch                         = ffe("FF10170", "ID mismatch")
+	MsgRegexpCompileFailed                = ffe("FF10171", "Unable to compile '%s' regexp '%s'")
+	MsgUnknownEventTransportPlugin        = ffe("FF10172", "Unknown event transport plugin: %s")
+	MsgWSConnectionNotActive              = ffe("FF10173", "Websocket connection '%s' no longer active")
+	MsgWSSubAlreadyInFlight               = ffe("FF10174", "Websocket subscription '%s' already has a message in flight")
+	MsgWSMsgSubNotMatched                 = ffe("FF10175", "Acknowledgment does not match an inflight event + subscription")
+	MsgWSClientSentInvalidData            = ffe("FF10176", "Invalid data")
+	MsgWSClientUnknownAction              = ffe("FF10177", "Unknown action '%s'")
+	MsgWSInvalidStartAction               = ffe("FF10178", "A start action must set namespace and either a name or ephemeral=true")
+	MsgWSAutoAckChanged                   = ffe("FF10179", "The autoack option must be set consistently on all start requests")
+	MsgWSAutoAckEnabled                   = ffe("FF10180", "The autoack option is enabled on this connection")
+	MsgConnSubscriptionNotStarted         = ffe("FF10181", "Subscription %v is not started on connection")
+	MsgDispatcherClosing                  = ffe("FF10182", "Event dispatcher closing")
+	MsgMaxFilterSkip                      = ffe("FF10183", "You have reached the maximum pagination limit for this query (%d)", 400)
+	MsgMaxFilterLimit                     = ffe("FF10184", "Your query exceeds the maximum filter limit (%d)", 400)
+	MsgAPIServerStaticFail                = ffe("FF10185", "An error occurred loading static content", 500)
+	MsgEventListenerClosing               = ffe("FF10186", "Event listener closing")
+	MsgNamespaceDoesNotExist              = ffe("FF10187", "Namespace does not exist", 404)
+	MsgInvalidSubscription                = ffe("FF10189", "Invalid subscription", 400)
+	MsgMismatchedTransport                = ffe("FF10190", "Connection ID '%s' appears not to be unique between transport '%s' and '%s'", 400)
+	MsgInvalidFirstEvent                  = ffe("FF10191", "Invalid firstEvent definition - must be 'newest','oldest' or a sequence number", 400)
+	MsgNumberMustBeGreaterEqual           = ffe("FF10192", "Number must be greater than or equal to %d", 400)
+	MsgAlreadyExists                      = ffe("FF10193", "A %s with name '%s:%s' already exists", 409)
+	MsgJSONValidatorBadRef                = ffe("FF10194", "Cannot use JSON validator for data with type '%s' and validator reference '%v'", 400)
+	MsgDatatypeNotFound                   = ffe("FF10195", "Datatype '%v' not found", 400)
+	MsgSchemaLoadFailed                   = ffe("FF10196", "Datatype '%s' schema invalid", 400)
+	MsgDataCannotBeValidated              = ffe("FF10197", "Data cannot be validated", 400)
+	MsgJSONDataInvalidPerSchema           = ffe("FF10198", "Data does not conform to the JSON schema of datatype '%s': %s", 400)
+	MsgDataValueIsNull                    = ffe("FF10199", "Data value is null", 400)
+	MsgDataInvalidHash                    = ffe("FF10201", "Invalid data: hashes do not match Hash=%s Expected=%s", 400)
+	MsgDataReferenceUnresolvable          = ffe("FF10204", "Data reference %d cannot be resolved", 400)
+	MsgDataMissing                        = ffe("FF10205", "Data entry %d has neither 'id' to refer to existing data, or 'value' to include in-line JSON data", 400)
+	MsgAuthorInvalid                      = ffe("FF10206", "Invalid author specified", 400)
+	MsgNoTransaction                      = ffe("FF10207", "Message does not have a transaction", 404)
+	MsgBatchNotSet                        = ffe("FF10208", "Message does not have an assigned batch", 404)
+	MsgBatchNotFound                      = ffe("FF10209", "Batch '%s' not found for message", 500)
+	MsgBatchTXNotSet                      = ffe("FF10210", "Batch '%s' does not have an assigned transaction", 404)
+	MsgOwnerMissing                       = ffe("FF10211", "Owner missing", 400)
+	MsgUnknownIdentityPlugin              = ffe("FF10212", "Unknown Identity plugin '%s'")
+	MsgUnknownDataExchangePlugin          = ffe("FF10213", "Unknown Data Exchange plugin '%s'")
+	MsgParentIdentityNotFound             = ffe("FF10214", "Identity '%s' not found in identity chain for %s '%s'")
+	MsgInvalidSigningIdentity             = ffe("FF10215", "Invalid signing identity")
+	MsgNodeAndOrgIDMustBeSet              = ffe("FF10216", "node.name, org.name and org.key must be configured first", 409)
+	MsgBlobStreamingFailed                = ffe("FF10217", "Blob streaming terminated with error", 500)
+	MsgNodeNotFound                       = ffe("FF10224", "Node with name or identity '%s' not found", 400)
+	MsgLocalNodeResolveFailed             = ffe("FF10225", "Unable to find local node to add to group. Check the status API to confirm the node is registered", 500)
+	MsgGroupNotFound                      = ffe("FF10226", "Group '%s' not found", 404)
+	MsgDXRESTErr                          = ffe("FF10229", "Error from data exchange: %s")
+	MsgInvalidHex                         = ffe("FF10231", "Invalid hex supplied", 400)
+	MsgInvalidWrongLenB32                 = ffe("FF00107", "Byte length must be 32 (64 hex characters)", 400)
+	MsgNodeNotFoundInOrg                  = ffe("FF10233", "Unable to find any nodes owned by org '%s', or parent orgs", 400)
+	MsgDXBadResponse                      = ffe("FF10237", "Unexpected '%s' in data exchange response: %s")
+	MsgDXBadHash                          = ffe("FF10238", "Unexpected hash returned from data exchange upload. Hash=%s Expected=%s")
+	MsgBlobNotFound                       = ffe("FF10239", "No blob has been uploaded or confirmed received, with hash=%s", 404)
+	MsgDownloadBlobFailed                 = ffe("FF10240", "Error download blob with reference '%s' from local data exchange")
+	MsgDataDoesNotHaveBlob                = ffe("FF10241", "Data does not have a blob attachment", 404)
+	MsgWebhookURLEmpty                    = ffe("FF10242", "Webhook subscription option 'url' cannot be empty", 400)
+	MsgWebhookInvalidStringMap            = ffe("FF10243", "Webhook subscription option '%s' must be map of string values. %s=%T", 400)
+	MsgWebsocketsNoData                   = ffe("FF10244", "Websockets subscriptions do not support streaming the full data payload, just the references (withData must be false)", 400)
+	MsgWebhooksWithData                   = ffe("FF10245", "Webhook subscriptions require the full data payload (withData must be true)", 400)
+	MsgWebhooksReplyBadJSON               = ffe("FF10257", "Failed to process reply from webhook as JSON")
+	MsgRequestTimeout                     = ffe("FF10260", "The request with id '%s' timed out after %.2fms", 408)
+	MsgRequestReplyTagRequired            = ffe("FF10261", "For request messages 'header.tag' must be set on the request message to route it to a suitable responder", 400)
+	MsgRequestCannotHaveCID               = ffe("FF10262", "For request messages 'header.cid' must be unset", 400)
+	MsgSystemTransportInternal            = ffe("FF10266", "You cannot create subscriptions on the system events transport")
+	MsgFilterCountNotSupported            = ffe("FF10267", "This query does not support generating a count of all results")
+	MsgRejected                           = ffe("FF10269", "Message with ID '%s' was rejected. Please check the FireFly logs for more information")
+	MsgRequestMustBePrivate               = ffe("FF10271", "For request messages you must specify a group of private recipients", 400)
+	MsgUnknownTokensPlugin                = ffe("FF10272", "Unknown tokens plugin '%s'", 400)
+	MsgMissingTokensPluginConfig          = ffe("FF10273", "Invalid tokens configuration - name and plugin are required", 400)
+	MsgTokensRESTErr                      = ffe("FF10274", "Error from tokens service: %s")
+	MsgTokenPoolDuplicate                 = ffe("FF10275", "Duplicate token pool: %s")
+	MsgTokenPoolRejected                  = ffe("FF10276", "Token pool with ID '%s' was rejected. Please check the FireFly logs for more information")
+	MsgIdentityNotFoundByString           = ffe("FF10277", "Identity could not be resolved via lookup string '%s'")
+	MsgAuthorOrgSigningKeyMismatch        = ffe("FF10279", "Author organization '%s' is not associated with signing key '%s'")
+	MsgCannotTransferToSelf               = ffe("FF10280", "From and to addresses must be different", 400)
+	MsgLocalOrgLookupFailed               = ffe("FF10281", "Unable to resolve the local org '%s' by the configured signing key on the node. Please confirm the org is registered with key '%s'", 500)
+	MsgFabconnectRESTErr                  = ffe("FF10284", "Error from fabconnect: %s")
+	MsgInvalidIdentity                    = ffe("FF10285", "Supplied Fabric signer identity is invalid", 400)
+	MsgFailedToDecodeCertificate          = ffe("FF10286", "Failed to decode certificate: %s", 500)
+	MsgInvalidMessageType                 = ffe("FF10287", "Invalid message type - allowed types are %s", 400)
+	MsgWSClosed                           = ffe("FF10290", "Websocket closed")
+	MsgFieldNotSpecified                  = ffe("FF10292", "Field '%s' must be specified", 400)
+	MsgTokenPoolNotConfirmed              = ffe("FF10293", "Token pool is not yet confirmed")
+	MsgHistogramCollectionParam           = ffe("FF10297", "Collection to fetch")
+	MsgInvalidNumberOfIntervals           = ffe("FF10298", "Number of time intervals must be between %d and %d", 400)
+	MsgInvalidChartNumberParam            = ffe("FF10299", "Invalid %s. Must be a number.", 400)
+	MsgHistogramInvalidTimes              = ffe("FF10300", "Start time must be before end time", 400)
+	MsgUnsupportedCollection              = ffe("FF10301", "%s collection is not supported", 400)
+	MsgContractInterfaceExists            = ffe("FF10302", "A contract interface already exists in the namespace: '%s' with name: '%s' and version: '%s'", 409)
+	MsgContractInterfaceNotFound          = ffe("FF10303", "Contract interface %s not found", 404)
+	MsgContractMissingInputArgument       = ffe("FF10304", "Missing required input argument '%s'", 400)
+	MsgContractWrongInputType             = ffe("FF10305", "Input '%v' is of type '%v' not expected type of '%v'", 400)
+	MsgContractMissingInputField          = ffe("FF10306", "Expected object of type '%v' to contain field named '%v' but it was missing", 400)
+	MsgContractMapInputType               = ffe("FF10307", "Unable to map input type '%v' to known FireFly type - was expecting '%v'", 400)
+	MsgContractByteDecode                 = ffe("FF10308", "Unable to decode field '%v' as bytes", 400)
+	MsgContractInternalType               = ffe("FF10309", "Input '%v' of type '%v' is not compatible blockchain internalType of '%v'", 400)
+	MsgContractLocationInvalid            = ffe("FF10310", "Failed to validate contract location: %v", 400)
+	MsgContractParamInvalid               = ffe("FF10311", "Failed to validate contract param: %v", 400)
+	MsgContractListenerNameExists         = ffe("FF10312", "A contract listener already exists in the namespace: '%s' with name: '%s'", 409)
+	MsgContractMethodNotSet               = ffe("FF10313", "Either an interface reference and method path, or in-line method definition, must be supplied on invoke contract request", 400)
+	MsgContractMethodResolveError         = ffe("FF10315", "Unable to resolve contract method: %s", 400)
+	MsgContractLocationExists             = ffe("FF10316", "The contract location cannot be changed after it is created", 400)
+	MsgListenerNoEvent                    = ffe("FF10317", "Either an interface reference and event path, or in-line event definition must be supplied when creating a contract listener", 400)
+	MsgListenerEventNotFound              = ffe("FF10318", "No event was found in namespace '%s' with id '%s'", 400)
+	MsgEventNameMustBeSet                 = ffe("FF10319", "Event name must be set", 400)
+	MsgMethodNameMustBeSet                = ffe("FF10320", "Method name must be set", 400)
+	MsgContractEventResolveError          = ffe("FF10321", "Unable to resolve contract event", 400)
+	MsgQueryOpUnsupportedMod              = ffe("FF10322", "Operation '%s' on '%s' does not support modifiers", 400)
+	MsgDXBadSize                          = ffe("FF10323", "Unexpected size returned from data exchange upload. Size=%d Expected=%d")
+	MsgTooLargeBroadcast                  = ffe("FF10327", "Message size %.2fkb is too large for the max broadcast batch size of %.2fkb", 400)
+	MsgTooLargePrivate                    = ffe("FF10328", "Message size %.2fkb is too large for the max private message size of %.2fkb", 400)
+	MsgManifestMismatch                   = ffe("FF10329", "Manifest mismatch overriding '%s' status as failure: '%s'", 400)
+	MsgFFIValidationFail                  = ffe("FF10331", "Field '%s' does not validate against the provided schema", 400)
+	MsgFFISchemaParseFail                 = ffe("FF10332", "Failed to parse schema for param '%s'", 400)
+	MsgFFISchemaCompileFail               = ffe("FF10333", "Failed compile schema for param '%s'", 400)
+	MsgPluginInitializationFailed         = ffe("FF10334", "Plugin initialization error", 500)
+	MsgUnknownTransactionType             = ffe("FF10336", "Unknown transaction type '%s'", 400)
+	MsgGoTemplateCompileFailed            = ffe("FF10337", "Go template compilation for '%s' failed: %s", 500)
+	MsgGoTemplateExecuteFailed            = ffe("FF10338", "Go template execution for '%s' failed: %s", 500)
+	MsgAddressResolveFailed               = ffe("FF10339", "Failed to resolve signing key string '%s': %s", 500)
+	MsgAddressResolveBadStatus            = ffe("FF10340", "Failed to resolve signing key string '%s' [%d]: %s", 500)
+	MsgAddressResolveBadResData           = ffe("FF10341", "Failed to resolve signing key string '%s' - invalid address returned '%s': %s", 500)
+	MsgDXNotInitialized                   = ffe("FF10342", "Data exchange is initializing")
+	MsgGroupRequired                      = ffe("FF10344", "Group must be set", 400)
+	MsgDBLockFailed                       = ffe("FF10345", "Database lock failed")
+	MsgFFIGenerationFailed                = ffe("FF10346", "Error generating smart contract interface: %s", 400)
+	MsgFFIGenerationUnsupported           = ffe("FF10347", "Smart contract interface generation is not supported by this blockchain plugin", 400)
+	MsgBlobHashMismatch                   = ffe("FF10348", "Blob hash mismatch sent=%s received=%s", 400)
+	MsgDIDResolverUnknown                 = ffe("FF10349", "DID resolver unknown for DID: %s", 400)
+	MsgIdentityNotOrg                     = ffe("FF10350", "Identity '%s' with DID '%s' is not an organization", 400)
+	MsgIdentityNotNode                    = ffe("FF10351", "Identity '%s' with DID '%s' is not a node", 400)
+	MsgBlockchainKeyNotSet                = ffe("FF10352", "No blockchain key specified", 400)
+	MsgNoVerifierForIdentity              = ffe("FF10353", "No %s verifier registered for identity %s", 400)
+	MsgNodeMissingBlockchainKey           = ffe("FF10354", "No default signing key or organization signing key configured for this namespace", 400)
+	MsgAuthorRegistrationMismatch         = ffe("FF10355", "Verifier '%s' cannot be used for signing with author '%s'. Verifier registered to '%s'", 400)
+	MsgAuthorMissingForKey                = ffe("FF10356", "Key '%s' has not been registered by any identity, and a separate 'author' was not supplied", 404)
+	MsgAuthorIncorrectForRootReg          = ffe("FF10357", "Author namespace '%s' and DID '%s' combination invalid for root organization registration", 400)
+	MsgKeyIdentityMissing                 = ffe("FF10358", "Identity owner of key '%s' not found", 500)
+	MsgIdentityChainLoop                  = ffe("FF10364", "Loop detected on identity %s in chain for %s (%s)", 400)
+	MsgInvalidIdentityParentType          = ffe("FF10365", "Parent %s (%s) of type %s is invalid for child %s (%s) of type", 400)
+	MsgParentIdentityMissingClaim         = ffe("FF10366", "Parent %s (%s) is invalid (missing claim)", 400)
+	MsgDXInfoMissingID                    = ffe("FF10367", "Data exchange endpoint info missing 'id' field", 500)
+	MsgEventNotFound                      = ffe("FF10370", "Event with name '%s' not found", 400)
+	MsgOperationNotSupported              = ffe("FF10371", "Operation not supported: %s", 400)
+	MsgFailedToRetrieve                   = ffe("FF10372", "Failed to retrieve %s %s", 500)
+	MsgBlobMissingPublic                  = ffe("FF10373", "Blob for data %s missing public payload reference while flushing batch", 500)
+	MsgDBMultiRowConfigError              = ffe("FF10374", "Database invalid configuration - using multi-row insert on DB plugin that does not support query syntax for input")
+	MsgDBNoSequence                       = ffe("FF10375", "Failed to retrieve sequence for insert row %d (could mean duplicate insert)", 500)
+	MsgDownloadSharedFailed               = ffe("FF10376", "Error downloading data with reference '%s' from shared storage")
+	MsgDownloadBatchMaxBytes              = ffe("FF10377", "Error downloading batch with reference '%s' from shared storage - maximum size limit reached")
+	MsgOperationDataIncorrect             = ffe("FF10378", "Operation data type incorrect: %T", 400)
+	MsgDataMissingBlobHash                = ffe("FF10379", "Blob for data %s cannot be transferred as it is missing a hash", 500)
+	MsgUnexpectedDXMessageType            = ffe("FF10380", "Unexpected websocket event type from DX plugin: %s", 500)
+	MsgContractListenerExists             = ffe("FF10383", "A contract listener already exists for this combination of topic + location + event", 409)
+	MsgInvalidOutputOption                = ffe("FF10385", "invalid output option '%s'")
+	MsgInvalidPluginConfiguration         = ffe("FF10386", "Invalid %s plugin configuration - name and type are required")
+	MsgReferenceMarkdownMissing           = ffe("FF10387", "Reference markdown file missing: '%s'")
+	MsgFFSystemReservedName               = ffe("FF10388", "Invalid namespace configuration - %s is a reserved name")
+	MsgInvalidNamespaceMode               = ffe("FF10389", "Invalid %s namespace configuration - unknown mode")
+	MsgNamespaceUnknownPlugin             = ffe("FF10390", "Invalid %s namespace configuration - unknown plugin %s")
+	MsgNamespaceWrongPluginsMultiparty    = ffe("FF10391", "Invalid %s namespace configuration - multiparty mode requires database, blockchain, shared storage, and data exchange plugins")
+	MsgNamespaceNoDatabase                = ffe("FF10392", "Invalid %s namespace configuration - a database plugin is required")
+	MsgNamespaceMultiplePluginType        = ffe("FF10394", "Invalid %s namespace configuration - multiple %s plugins provided")
+	MsgDuplicatePluginName                = ffe("FF10395", "Invalid plugin configuration - plugin with name %s already exists")
+	MsgInvalidFireFlyContractIndex        = ffe("FF10396", "No configuration found for FireFly contract at %s")
+	MsgUnrecognizedNetworkAction          = ffe("FF10397", "Unrecognized network action: %s", 400)
+	MsgOverrideExistingFieldCustomOption  = ffe("FF10398", "Cannot override existing field with custom option named '%s'", 400)
+	MsgTerminateNotSupported              = ffe("FF10399", "The 'terminate' operation to mark a switchover of smart contracts is not supported on namespace %s", 400)
+	MsgDefRejectedBadPayload              = ffe("FF10400", "Rejected %s message '%s' - invalid payload")
+	MsgDefRejectedAuthorBlank             = ffe("FF10401", "Rejected %s message '%s' - author is blank")
+	MsgDefRejectedSignatureMismatch       = ffe("FF10402", "Rejected %s message '%s' - signature mismatch")
+	MsgDefRejectedValidateFail            = ffe("FF10403", "Rejected %s '%s' - validate failed: %s")
+	MsgDefRejectedIDMismatch              = ffe("FF10404", "Rejected %s '%s' - ID mismatch with existing record")
+	MsgDefRejectedLocationMismatch        = ffe("FF10405", "Rejected %s '%s' - location mismatch with existing record")
+	MsgDefRejectedSchemaFail              = ffe("FF10406", "Rejected %s '%s' - schema check: %s")
+	MsgDefRejectedConflict                = ffe("FF10407", "Rejected %s '%s' - conflicts with existing: %s")
+	MsgDefRejectedIdentityNotFound        = ffe("FF10408", "Rejected %s '%s' - identity not found: %s")
+	MsgDefRejectedWrongAuthor             = ffe("FF10409", "Rejected %s '%s' - wrong author: %s")
+	MsgDefRejectedHashMismatch            = ffe("FF10410", "Rejected %s '%s' - hash mismatch: %s != %s")
+	MsgInvalidNamespaceUUID               = ffe("FF10411", "Expected 'namespace:' prefix on ID '%s'", 400)
+	MsgBadNetworkVersion                  = ffe("FF10412", "Bad network version: %s")
+	MsgDefinitionRejected                 = ffe("FF10413", "Definition rejected")
+	MsgActionNotSupported                 = ffe("FF10414", "This action is not supported in this namespace", 400)
+	MsgMessagesNotSupported               = ffe("FF10415", "Messages are not supported in this namespace", 400)
+	MsgInvalidSubscriptionForNetwork      = ffe("FF10416", "Subscription name '%s' is invalid according to multiparty network rules in effect (network version=%d)")
+	MsgBlockchainNotConfigured            = ffe("FF10417", "No blockchain plugin configured")
+	MsgInvalidBatchPinEvent               = ffe("FF10418", "BatchPin event is not valid - %s (%s): %s")
+	MsgDuplicatePluginBroadcastName       = ffe("FF10419", "Invalid %s plugin broadcast name: %s - broadcast names must be unique")
+	MsgInvalidConnectorName               = ffe("FF10420", "Could not find name %s for %s connector")
+	MsgCannotInitLegacyNS                 = ffe("FF10421", "could not initialize legacy '%s' namespace - found conflicting V1 multi-party config in %s and %s")
+	MsgInvalidGroupMember                 = ffe("FF10422", "invalid group member - node '%s' is not owned by '%s' or any of its ancestors")
+	MsgContractListenerStatusInvalid      = ffe("FF10423", "Failed to validate contract listener status: %v", 400)
+	MsgCacheMissSizeLimitKeyInternal      = ffe("FF10424", "could not initialize cache - size limit config key is not provided")
+	MsgCacheMissTTLKeyInternal            = ffe("FF10425", "could not initialize cache - ttl config key is not provided")
+	MsgCacheConfigKeyMismatchInternal     = ffe("FF10426", "could not initialize cache - '%s' and '%s' do not have identical prefix, mismatching prefixes are: '%s','%s'")
+	MsgCacheUnexpectedSizeKeyNameInternal = ffe("FF10427", "could not initialize cache - '%s' is not an expected size configuration key suffix. Expected values are: 'size', 'limit'")
 )
diff --git a/internal/data/data_manager.go b/internal/data/data_manager.go
index 2101da62b..95dd36218 100644
--- a/internal/data/data_manager.go
+++ b/internal/data/data_manager.go
@@ -20,19 +20,18 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/ffapi"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
 	"github.com/hyperledger/firefly/pkg/dataexchange"
-	"github.com/karlseguin/ccache"
 )
 
 type Manager interface {
@@ -58,13 +57,11 @@ type Manager interface {
 
 type dataManager struct {
 	blobStore
-	namespace         *core.Namespace
-	database          database.Plugin
-	validatorCache    *ccache.Cache
-	validatorCacheTTL time.Duration
-	messageCache      *ccache.Cache
-	messageCacheTTL   time.Duration
-	messageWriter     *messageWriter
+	namespace      *core.Namespace
+	database       database.Plugin
+	validatorCache cache.CInterface
+	messageCache   cache.CInterface
+	messageWriter  *messageWriter
 }
 
 type messageCacheEntry struct {
@@ -95,31 +92,45 @@ const (
 	CRORequireBatchID
 )
 
-func NewDataManager(ctx context.Context, ns *core.Namespace, di database.Plugin, dx dataexchange.Plugin) (Manager, error) {
+func NewDataManager(ctx context.Context, ns *core.Namespace, di database.Plugin, dx dataexchange.Plugin, cacheManager cache.Manager) (Manager, error) {
 	if di == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "DataManager")
 	}
 	dm := &dataManager{
-		namespace:         ns,
-		database:          di,
-		validatorCacheTTL: config.GetDuration(coreconfig.ValidatorCacheTTL),
-		messageCacheTTL:   config.GetDuration(coreconfig.MessageCacheTTL),
+		namespace: ns,
+		database:  di,
 	}
 	dm.blobStore = blobStore{
 		dm:       dm,
 		database: di,
 		exchange: dx,
 	}
-	dm.validatorCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetByteSize(coreconfig.ValidatorCacheSize)),
+
+	validatorCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheValidatorSize,
+			coreconfig.CacheValidatorTTL,
+			ns.Name,
+		),
 	)
-	dm.messageCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetByteSize(coreconfig.MessageCacheSize)),
+	if err != nil {
+		return nil, err
+	}
+	dm.validatorCache = validatorCache
+
+	messageCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheMessageSize,
+			coreconfig.CacheMessageTTL,
+			ns.Name,
+		),
 	)
+	if err != nil {
+		return nil, err
+	}
+	dm.messageCache = messageCache
 	dm.messageWriter = newMessageWriter(ctx, di, &messageWriterConf{
 		workerCount:  config.GetInt(coreconfig.MessageWriterCount),
 		batchTimeout: config.GetDuration(coreconfig.MessageWriterBatchTimeout),
@@ -153,9 +164,8 @@ func (dm *dataManager) getValidatorForDatatype(ctx context.Context, validator co
 	}
 
 	key := fmt.Sprintf("%s:%s:%s", validator, dm.namespace.Name, datatypeRef)
-	if cached := dm.validatorCache.Get(key); cached != nil {
-		cached.Extend(dm.validatorCacheTTL)
-		return cached.Value().(Validator), nil
+	if cachedValue := dm.validatorCache.Get(key); cachedValue != nil {
+		return cachedValue.(Validator), nil
 	}
 
 	datatype, err := dm.database.GetDatatypeByName(ctx, dm.namespace.Name, datatypeRef.Name, datatypeRef.Version)
@@ -171,7 +181,7 @@ func (dm *dataManager) getValidatorForDatatype(ctx context.Context, validator co
 		return nil, nil
 	}
 
-	dm.validatorCache.Set(key, v, dm.validatorCacheTTL)
+	dm.validatorCache.Set(key, v)
 	return v, err
 }
 
@@ -222,12 +232,12 @@ func (dm *dataManager) PeekMessageCache(ctx context.Context, id *fftypes.UUID, o
 }
 
 func (dm *dataManager) queryMessageCache(ctx context.Context, id *fftypes.UUID, options ...CacheReadOption) *messageCacheEntry {
-	cached := dm.messageCache.Get(id.String())
-	if cached == nil {
+	cachedValue := dm.messageCache.Get(id.String())
+	if cachedValue == nil {
 		log.L(context.Background()).Debugf("Cache miss for message %s", id)
 		return nil
 	}
-	mce := cached.Value().(*messageCacheEntry)
+	mce := cachedValue.(*messageCacheEntry)
 	for _, opt := range options {
 		switch opt {
 		case CRORequirePublicBlobRefs:
@@ -250,7 +260,6 @@ func (dm *dataManager) queryMessageCache(ctx context.Context, id *fftypes.UUID,
 		}
 	}
 	log.L(ctx).Debugf("Cache hit for message %s", id)
-	cached.Extend(dm.messageCacheTTL)
 	mce.msg.LocalNamespace = dm.namespace.Name // always populate LocalNamespace on the way out of the cache
 	return mce
 }
@@ -263,7 +272,7 @@ func (dm *dataManager) UpdateMessageCache(msg *core.Message, data core.DataArray
 		data: data,
 		size: msg.EstimateSize(true),
 	}
-	dm.messageCache.Set(msg.Header.ID.String(), cacheEntry, dm.messageCacheTTL)
+	dm.messageCache.Set(msg.Header.ID.String(), cacheEntry)
 	log.L(context.Background()).Debugf("Added to message cache: %s (topics=%d,pins=%d)", msg.Header.ID.String(), len(msg.Header.Topics), len(msg.Pins))
 }
 
diff --git a/internal/events/aggregator.go b/internal/events/aggregator.go
index dd7826f0a..58ecd3ae9 100644
--- a/internal/events/aggregator.go
+++ b/internal/events/aggregator.go
@@ -22,12 +22,12 @@ import (
 	"encoding/binary"
 	"fmt"
 	"strings"
-	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/retry"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/data"
 	"github.com/hyperledger/firefly/internal/definitions"
@@ -37,7 +37,6 @@ import (
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 )
 
 const (
@@ -45,20 +44,19 @@ const (
 )
 
 type aggregator struct {
-	ctx           context.Context
-	namespace     string
-	database      database.Plugin
-	messaging     privatemessaging.Manager
-	definitions   definitions.Handler
-	identity      identity.Manager
-	data          data.Manager
-	eventPoller   *eventPoller
-	verifierType  core.VerifierType
-	retry         *retry.Retry
-	metrics       metrics.Manager
-	batchCache    *ccache.Cache
-	batchCacheTTL time.Duration
-	rewinder      *rewinder
+	ctx          context.Context
+	namespace    string
+	database     database.Plugin
+	messaging    privatemessaging.Manager
+	definitions  definitions.Handler
+	identity     identity.Manager
+	data         data.Manager
+	eventPoller  *eventPoller
+	verifierType core.VerifierType
+	retry        *retry.Retry
+	metrics      metrics.Manager
+	batchCache   cache.CInterface
+	rewinder     *rewinder
 }
 
 type batchCacheEntry struct {
@@ -90,25 +88,32 @@ func privatePinHash(topic string, group *fftypes.Bytes32, identity string, nonce
 	return fftypes.HashResult(h)
 }
 
-func newAggregator(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, pm privatemessaging.Manager, sh definitions.Handler, im identity.Manager, dm data.Manager, en *eventNotifier, mm metrics.Manager) *aggregator {
+func newAggregator(ctx context.Context, ns string, di database.Plugin, bi blockchain.Plugin, pm privatemessaging.Manager, sh definitions.Handler, im identity.Manager, dm data.Manager, en *eventNotifier, mm metrics.Manager, cacheManager cache.Manager) (*aggregator, error) {
 	batchSize := config.GetInt(coreconfig.EventAggregatorBatchSize)
 	ag := &aggregator{
-		ctx:           log.WithLogField(ctx, "role", "aggregator"),
-		namespace:     ns,
-		database:      di,
-		messaging:     pm,
-		definitions:   sh,
-		identity:      im,
-		data:          dm,
-		verifierType:  bi.VerifierType(),
-		metrics:       mm,
-		batchCacheTTL: config.GetDuration(coreconfig.BatchCacheTTL),
-	}
-	ag.batchCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetInt64(coreconfig.BatchCacheLimit)),
+		ctx:          log.WithLogField(ctx, "role", "aggregator"),
+		namespace:    ns,
+		database:     di,
+		messaging:    pm,
+		definitions:  sh,
+		identity:     im,
+		data:         dm,
+		verifierType: bi.VerifierType(),
+		metrics:      mm,
+	}
+
+	batchCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheBatchLimit,
+			coreconfig.CacheBatchTTL,
+			ns,
+		),
 	)
+	if err != nil {
+		return nil, err
+	}
+	ag.batchCache = batchCache
 	firstEvent := core.SubOptsFirstEvent(config.GetString(coreconfig.EventAggregatorFirstEvent))
 	ag.eventPoller = newEventPoller(ctx, di, en, &eventPollerConf{
 		eventBatchSize:             batchSize,
@@ -135,7 +140,7 @@ func newAggregator(ctx context.Context, ns string, di database.Plugin, bi blockc
 	})
 	ag.retry = &ag.eventPoller.conf.retry
 	ag.rewinder = newRewinder(ag)
-	return ag
+	return ag, nil
 }
 
 func (ag *aggregator) start() {
@@ -314,10 +319,9 @@ func (ag *aggregator) getBatchCacheKey(id *fftypes.UUID, hash *fftypes.Bytes32)
 
 func (ag *aggregator) GetBatchForPin(ctx context.Context, pin *core.Pin) (*core.BatchPersisted, *core.BatchManifest, error) {
 	cacheKey := ag.getBatchCacheKey(pin.Batch, pin.BatchHash)
-	cached := ag.batchCache.Get(cacheKey)
-	if cached != nil {
-		cached.Extend(ag.batchCacheTTL)
-		bce := cached.Value().(*batchCacheEntry)
+	cachedValue := ag.batchCache.Get(cacheKey)
+	if cachedValue != nil {
+		bce := cachedValue.(*batchCacheEntry)
 		log.L(ag.ctx).Debugf("Batch cache hit %s", cacheKey)
 		return bce.batch, bce.manifest, nil
 	}
@@ -346,7 +350,7 @@ func (ag *aggregator) cacheBatch(cacheKey string, batch *core.BatchPersisted, ma
 		batch:    batch,
 		manifest: manifest,
 	}
-	ag.batchCache.Set(cacheKey, bce, ag.batchCacheTTL)
+	ag.batchCache.Set(cacheKey, bce)
 	log.L(ag.ctx).Debugf("Cached batch %s", cacheKey)
 }
 
diff --git a/internal/events/blockchain_event.go b/internal/events/blockchain_event.go
index 193e021d3..989bcb49b 100644
--- a/internal/events/blockchain_event.go
+++ b/internal/events/blockchain_event.go
@@ -51,16 +51,15 @@ func (em *eventManager) getChainListenerByProtocolIDCached(ctx context.Context,
 }
 
 func (em *eventManager) getChainListenerCached(cacheKey string, getter func() (*core.ContractListener, error)) (*core.ContractListener, error) {
-	cached := em.chainListenerCache.Get(cacheKey)
-	if cached != nil {
-		cached.Extend(em.chainListenerCacheTTL)
-		return cached.Value().(*core.ContractListener), nil
+
+	if cachedValue := em.chainListenerCache.Get(cacheKey); cachedValue != nil {
+		return cachedValue.(*core.ContractListener), nil
 	}
 	listener, err := getter()
 	if listener == nil || err != nil {
 		return nil, err
 	}
-	em.chainListenerCache.Set(cacheKey, listener, em.chainListenerCacheTTL)
+	em.chainListenerCache.Set(cacheKey, listener)
 	return listener, err
 }
 
diff --git a/internal/events/event_manager.go b/internal/events/event_manager.go
index 0aa43e3a5..7f511ba60 100644
--- a/internal/events/event_manager.go
+++ b/internal/events/event_manager.go
@@ -21,7 +21,6 @@ import (
 	"context"
 	"encoding/json"
 	"strconv"
-	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
@@ -30,6 +29,7 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/retry"
 	"github.com/hyperledger/firefly/internal/assets"
 	"github.com/hyperledger/firefly/internal/broadcast"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/data"
@@ -50,7 +50,6 @@ import (
 	"github.com/hyperledger/firefly/pkg/events"
 	"github.com/hyperledger/firefly/pkg/sharedstorage"
 	"github.com/hyperledger/firefly/pkg/tokens"
-	"github.com/karlseguin/ccache"
 )
 
 type EventManager interface {
@@ -90,39 +89,51 @@ type EventManager interface {
 }
 
 type eventManager struct {
-	ctx                   context.Context
-	namespace             *core.Namespace
-	enricher              *eventEnricher
-	database              database.Plugin
-	txHelper              txcommon.Helper
-	identity              identity.Manager
-	defsender             definitions.Sender
-	defhandler            definitions.Handler
-	data                  data.Manager
-	subManager            *subscriptionManager
-	retry                 retry.Retry
-	aggregator            *aggregator              // optional
-	broadcast             broadcast.Manager        // optional
-	messaging             privatemessaging.Manager // optional
-	assets                assets.Manager
-	sharedDownload        shareddownload.Manager // optional
-	blobReceiver          *blobReceiver          // optional
-	newEventNotifier      *eventNotifier
-	newPinNotifier        *eventNotifier
-	defaultTransport      string
-	internalEvents        *system.Events
-	metrics               metrics.Manager
-	chainListenerCache    *ccache.Cache
-	chainListenerCacheTTL time.Duration
-	multiparty            multiparty.Manager // optional
+	ctx                context.Context
+	namespace          *core.Namespace
+	enricher           *eventEnricher
+	database           database.Plugin
+	txHelper           txcommon.Helper
+	identity           identity.Manager
+	defsender          definitions.Sender
+	defhandler         definitions.Handler
+	data               data.Manager
+	subManager         *subscriptionManager
+	retry              retry.Retry
+	aggregator         *aggregator              // optional
+	broadcast          broadcast.Manager        // optional
+	messaging          privatemessaging.Manager // optional
+	assets             assets.Manager
+	sharedDownload     shareddownload.Manager // optional
+	blobReceiver       *blobReceiver          // optional
+	newEventNotifier   *eventNotifier
+	newPinNotifier     *eventNotifier
+	defaultTransport   string
+	internalEvents     *system.Events
+	metrics            metrics.Manager
+	chainListenerCache cache.CInterface
+	multiparty         multiparty.Manager // optional
 }
 
-func NewEventManager(ctx context.Context, ns *core.Namespace, di database.Plugin, bi blockchain.Plugin, im identity.Manager, dh definitions.Handler, dm data.Manager, ds definitions.Sender, bm broadcast.Manager, pm privatemessaging.Manager, am assets.Manager, sd shareddownload.Manager, mm metrics.Manager, om operations.Manager, txHelper txcommon.Helper, transports map[string]events.Plugin, mp multiparty.Manager) (EventManager, error) {
+func NewEventManager(ctx context.Context, ns *core.Namespace, di database.Plugin, bi blockchain.Plugin, im identity.Manager, dh definitions.Handler, dm data.Manager, ds definitions.Sender, bm broadcast.Manager, pm privatemessaging.Manager, am assets.Manager, sd shareddownload.Manager, mm metrics.Manager, om operations.Manager, txHelper txcommon.Helper, transports map[string]events.Plugin, mp multiparty.Manager, cacheManager cache.Manager) (EventManager, error) {
 	if di == nil || im == nil || dh == nil || dm == nil || om == nil || ds == nil || am == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "EventManager")
 	}
 	newPinNotifier := newEventNotifier(ctx, "pins")
 	newEventNotifier := newEventNotifier(ctx, "events")
+
+	eventListenerCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheEventListenerTopicLimit,
+			coreconfig.CacheEventListenerTopicTTL,
+			ns.Name,
+		),
+	)
+	if err != nil {
+		return nil, err
+	}
+
 	em := &eventManager{
 		ctx:            log.WithLogField(ctx, "role", "event-manager"),
 		namespace:      ns,
@@ -142,23 +153,25 @@ func NewEventManager(ctx context.Context, ns *core.Namespace, di database.Plugin
 			MaximumDelay: config.GetDuration(coreconfig.EventAggregatorRetryMaxDelay),
 			Factor:       config.GetFloat64(coreconfig.EventAggregatorRetryFactor),
 		},
-		defaultTransport:      config.GetString(coreconfig.EventTransportsDefault),
-		newEventNotifier:      newEventNotifier,
-		newPinNotifier:        newPinNotifier,
-		metrics:               mm,
-		chainListenerCache:    ccache.New(ccache.Configure().MaxSize(config.GetInt64(coreconfig.EventListenerTopicCacheLimit))),
-		chainListenerCacheTTL: config.GetDuration(coreconfig.EventListenerTopicCacheTTL),
+		defaultTransport:   config.GetString(coreconfig.EventTransportsDefault),
+		newEventNotifier:   newEventNotifier,
+		newPinNotifier:     newPinNotifier,
+		metrics:            mm,
+		chainListenerCache: eventListenerCache,
 	}
 	ie, _ := eifactory.GetPlugin(ctx, system.SystemEventsTransport)
 	em.internalEvents = ie.(*system.Events)
 	if bi != nil {
-		em.aggregator = newAggregator(ctx, ns.Name, di, bi, pm, dh, im, dm, newPinNotifier, mm)
+		aggregator, err := newAggregator(ctx, ns.Name, di, bi, pm, dh, im, dm, newPinNotifier, mm, cacheManager)
+		if err != nil {
+			return nil, err
+		}
+		em.aggregator = aggregator
 		em.blobReceiver = newBlobReceiver(ctx, em.aggregator)
 	}
 
 	em.enricher = newEventEnricher(ns.Name, di, dm, om, txHelper)
 
-	var err error
 	if em.subManager, err = newSubscriptionManager(ctx, ns.Name, em.enricher, di, dm, newEventNotifier, bm, pm, txHelper, transports); err != nil {
 		return nil, err
 	}
diff --git a/internal/identity/identitymanager.go b/internal/identity/identitymanager.go
index dfed896e1..42bb170d8 100644
--- a/internal/identity/identitymanager.go
+++ b/internal/identity/identitymanager.go
@@ -20,19 +20,17 @@ import (
 	"context"
 	"fmt"
 	"strings"
-	"time"
 
-	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/multiparty"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 )
 
 const (
@@ -63,32 +61,49 @@ type identityManager struct {
 	defaultKey             string
 	multipartyRootVerifier *core.VerifierRef
 	localNode              *core.Identity
-	identityCacheTTL       time.Duration
-	identityCache          *ccache.Cache
-	signingKeyCacheTTL     time.Duration
-	signingKeyCache        *ccache.Cache
+	identityCache          cache.CInterface
+	signingKeyCache        cache.CInterface
 }
 
-func NewIdentityManager(ctx context.Context, ns, defaultKey string, di database.Plugin, bi blockchain.Plugin, mp multiparty.Manager) (Manager, error) {
+func NewIdentityManager(ctx context.Context, ns, defaultKey string, di database.Plugin, bi blockchain.Plugin, mp multiparty.Manager, cacheManager cache.Manager) (Manager, error) {
 	if di == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "IdentityManager")
 	}
 	im := &identityManager{
-		database:           di,
-		blockchain:         bi,
-		namespace:          ns,
-		multiparty:         mp,
-		defaultKey:         defaultKey,
-		identityCacheTTL:   config.GetDuration(coreconfig.IdentityManagerCacheTTL),
-		signingKeyCacheTTL: config.GetDuration(coreconfig.IdentityManagerCacheTTL),
-	}
-	// For the identity and signingkey caches, we just treat them all equally sized and the max items
-	im.identityCache = ccache.New(
-		ccache.Configure().MaxSize(config.GetInt64(coreconfig.IdentityManagerCacheLimit)),
+		database:   di,
+		blockchain: bi,
+		namespace:  ns,
+		multiparty: mp,
+		defaultKey: defaultKey,
+	}
+
+	identityCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheIdentityLimit,
+			coreconfig.CacheIdentityTTL,
+			ns,
+		),
 	)
-	im.signingKeyCache = ccache.New(
-		ccache.Configure().MaxSize(config.GetInt64(coreconfig.IdentityManagerCacheLimit)),
+	if err != nil {
+		return nil, err
+	}
+	im.identityCache = identityCache
+
+	signingKeyCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheSigningKeyLimit,
+			coreconfig.CacheSigningKeyTTL,
+			ns,
+		),
 	)
+	if err != nil {
+		return nil, err
+	}
+
+	// For the identity and signingkey caches, we just treat them all equally sized and the max items
+	im.signingKeyCache = signingKeyCache
 
 	return im, nil
 }
@@ -289,9 +304,8 @@ func (im *identityManager) normalizeKeyViaBlockchainPlugin(ctx context.Context,
 		return nil, i18n.NewError(ctx, coremsgs.MsgBlockchainNotConfigured)
 	}
 
-	if cached := im.signingKeyCache.Get(inputKey); cached != nil {
-		cached.Extend(im.identityCacheTTL)
-		return cached.Value().(*core.VerifierRef), nil
+	if cachedValue := im.signingKeyCache.Get(inputKey); cachedValue != nil {
+		return cachedValue.(*core.VerifierRef), nil
 	}
 	keyString, err := im.blockchain.NormalizeSigningKey(ctx, inputKey)
 	if err != nil {
@@ -301,7 +315,7 @@ func (im *identityManager) normalizeKeyViaBlockchainPlugin(ctx context.Context,
 		Type:  im.blockchain.VerifierType(),
 		Value: keyString,
 	}
-	im.signingKeyCache.Set(inputKey, verifier, im.identityCacheTTL)
+	im.signingKeyCache.Set(inputKey, verifier)
 	return verifier, nil
 }
 
@@ -407,9 +421,8 @@ func (im *identityManager) validateParentType(ctx context.Context, child *core.I
 
 func (im *identityManager) cachedIdentityLookupByVerifierRef(ctx context.Context, namespace string, verifierRef *core.VerifierRef) (*core.Identity, error) {
 	cacheKey := fmt.Sprintf("key=%s|%s|%s", namespace, verifierRef.Type, verifierRef.Value)
-	if cached := im.identityCache.Get(cacheKey); cached != nil {
-		cached.Extend(im.identityCacheTTL)
-		return cached.Value().(*core.Identity), nil
+	if cachedValue := im.identityCache.Get(cacheKey); cachedValue != nil {
+		return cachedValue.(*core.Identity), nil
 	}
 	verifier, err := im.database.GetVerifierByValue(ctx, verifierRef.Type, namespace, verifierRef.Value)
 	if err != nil {
@@ -425,7 +438,7 @@ func (im *identityManager) cachedIdentityLookupByVerifierRef(ctx context.Context
 		return nil, i18n.NewError(ctx, i18n.MsgEmptyMemberIdentity, verifier.Identity)
 	}
 	// Cache the result
-	im.identityCache.Set(cacheKey, identity, im.identityCacheTTL)
+	im.identityCache.Set(cacheKey, identity)
 	return identity, nil
 }
 
@@ -441,9 +454,8 @@ func (im *identityManager) CachedIdentityLookupNilOK(ctx context.Context, didLoo
 		}
 		log.L(ctx).Debugf("Resolved DID '%s' to identity: %s / %s (err=%v)", didLookupStr, uuidResolved, didResolved, err)
 	}()
-	if cached := im.identityCache.Get(cacheKey); cached != nil {
-		cached.Extend(im.identityCacheTTL)
-		identity = cached.Value().(*core.Identity)
+	if cachedValue := im.identityCache.Get(cacheKey); cachedValue != nil {
+		identity = cachedValue.(*core.Identity)
 	} else {
 		if strings.HasPrefix(didLookupStr, core.DIDPrefix) {
 			if !strings.HasPrefix(didLookupStr, core.FireFlyDIDPrefix) {
@@ -471,7 +483,7 @@ func (im *identityManager) CachedIdentityLookupNilOK(ctx context.Context, didLoo
 
 		if identity != nil {
 			// Cache the result
-			im.identityCache.Set(cacheKey, identity, im.identityCacheTTL)
+			im.identityCache.Set(cacheKey, identity)
 		}
 	}
 	return identity, false, nil
@@ -491,16 +503,15 @@ func (im *identityManager) CachedIdentityLookupMustExist(ctx context.Context, di
 func (im *identityManager) CachedIdentityLookupByID(ctx context.Context, id *fftypes.UUID) (identity *core.Identity, err error) {
 	// Use an LRU cache for the author identity, as it's likely for the same identity to be re-used over and over
 	cacheKey := fmt.Sprintf("id=%s", id)
-	if cached := im.identityCache.Get(cacheKey); cached != nil {
-		cached.Extend(im.identityCacheTTL)
-		identity = cached.Value().(*core.Identity)
+	if cachedValue := im.identityCache.Get(cacheKey); cachedValue != nil {
+		identity = cachedValue.(*core.Identity)
 	} else {
 		identity, err = im.database.GetIdentityByID(ctx, im.namespace, id)
 		if err != nil || identity == nil {
 			return identity, err
 		}
 		// Cache the result
-		im.identityCache.Set(cacheKey, identity, im.identityCacheTTL)
+		im.identityCache.Set(cacheKey, identity)
 	}
 	return identity, nil
 }
diff --git a/internal/namespace/manager.go b/internal/namespace/manager.go
index e3ed0ef27..a2fb61c73 100644
--- a/internal/namespace/manager.go
+++ b/internal/namespace/manager.go
@@ -30,6 +30,7 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly/internal/blockchain/bifactory"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/database/difactory"
@@ -107,6 +108,7 @@ type namespaceManager struct {
 		auth          map[string]authPlugin
 	}
 	metricsEnabled      bool
+	cacheManager        cache.Manager
 	metrics             metrics.Manager
 	adminEvents         spievents.Manager
 	utOrchestrator      orchestrator.Orchestrator
@@ -303,7 +305,7 @@ func (nm *namespaceManager) initNamespace(ctx context.Context, ns *namespace) (e
 
 	or := nm.utOrchestrator
 	if or == nil {
-		or = orchestrator.NewOrchestrator(&ns.Namespace, ns.config, plugins, nm.metrics)
+		or = orchestrator.NewOrchestrator(&ns.Namespace, ns.config, plugins, nm.metrics, nm.cacheManager)
 	}
 	ns.orchestrator = or
 	orCtx, orCancel := context.WithCancel(ctx)
@@ -375,6 +377,10 @@ func (nm *namespaceManager) loadPlugins(ctx context.Context) (err error) {
 		nm.metrics = metrics.NewMetricsManager(ctx)
 	}
 
+	if nm.cacheManager == nil {
+		nm.cacheManager = cache.NewCacheManager(ctx)
+	}
+
 	if nm.plugins.database == nil {
 		nm.plugins.database, err = nm.getDatabasePlugins(ctx)
 		if err != nil {
@@ -722,7 +728,7 @@ func (nm *namespaceManager) initPlugins(ctx context.Context, cancelCtx context.C
 		entry.plugin.SetHandler(database.GlobalHandler, nm)
 	}
 	for _, entry := range nm.plugins.blockchain {
-		if err = entry.plugin.Init(ctx, cancelCtx, entry.config, nm.metrics); err != nil {
+		if err = entry.plugin.Init(ctx, cancelCtx, entry.config, nm.metrics, nm.cacheManager); err != nil {
 			return err
 		}
 	}
diff --git a/internal/operations/manager.go b/internal/operations/manager.go
index 7bbe7d301..700f204c2 100644
--- a/internal/operations/manager.go
+++ b/internal/operations/manager.go
@@ -20,18 +20,16 @@ import (
 	"context"
 	"database/sql/driver"
 	"fmt"
-	"time"
 
-	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 )
 
 type OperationHandler interface {
@@ -66,25 +64,35 @@ type operationsManager struct {
 	database  database.Plugin
 	handlers  map[core.OpType]OperationHandler
 	updater   *operationUpdater
-	cacheTTL  time.Duration
-	cache     *ccache.Cache
+	cache     cache.CInterface
 }
 
-func NewOperationsManager(ctx context.Context, ns string, di database.Plugin, txHelper txcommon.Helper) (Manager, error) {
+func NewOperationsManager(ctx context.Context, ns string, di database.Plugin, txHelper txcommon.Helper, cacheManager cache.Manager) (Manager, error) {
 	if di == nil || txHelper == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "OperationsManager")
 	}
+
+	cache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheOperationsLimit,
+			coreconfig.CacheOperationsTTL,
+			ns,
+		),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
 	om := &operationsManager{
 		ctx:       ctx,
 		namespace: ns,
 		database:  di,
 		handlers:  make(map[core.OpType]OperationHandler),
-		cacheTTL:  config.GetDuration(coreconfig.CacheOperationsTTL),
 	}
 	om.updater = newOperationUpdater(ctx, om, di, txHelper)
-	om.cache = ccache.New(
-		ccache.Configure().MaxSize(config.GetInt64(coreconfig.CacheOperationsLimit)),
-	)
+	om.cache = cache
 	return om, nil
 }
 
@@ -245,20 +253,19 @@ func (om *operationsManager) getOperationsCached(ctx context.Context, opIDs []*f
 }
 
 func (om *operationsManager) getCachedOperation(id *fftypes.UUID) *core.Operation {
-	if op := om.cache.Get(id.String()); op != nil {
-		op.Extend(om.cacheTTL)
-		return op.Value().(*core.Operation)
+	if cachedValue := om.cache.Get(id.String()); cachedValue != nil {
+		return cachedValue.(*core.Operation)
 	}
 	return nil
 }
 
 func (om *operationsManager) cacheOperation(op *core.Operation) {
-	om.cache.Set(op.ID.String(), op, om.cacheTTL)
+	om.cache.Set(op.ID.String(), op)
 }
 
 func (om *operationsManager) updateCachedOperation(id *fftypes.UUID, status core.OpStatus, errorMsg *string, output fftypes.JSONObject, retry *fftypes.UUID) {
-	if op := om.cache.Get(id.String()); op != nil {
-		val := op.Value().(*core.Operation)
+	if cachedValue := om.cache.Get(id.String()); cachedValue != nil {
+		val := cachedValue.(*core.Operation)
 		if status != "" {
 			val.Status = status
 		}
diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go
index 87ff3451e..5497024c5 100644
--- a/internal/orchestrator/orchestrator.go
+++ b/internal/orchestrator/orchestrator.go
@@ -26,6 +26,7 @@ import (
 	"github.com/hyperledger/firefly/internal/assets"
 	"github.com/hyperledger/firefly/internal/batch"
 	"github.com/hyperledger/firefly/internal/broadcast"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/contracts"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/data"
@@ -201,16 +202,18 @@ type orchestrator struct {
 	bc             boundCallbacks
 	contracts      contracts.Manager
 	metrics        metrics.Manager
+	cacheManager   cache.Manager
 	operations     operations.Manager
 	txHelper       txcommon.Helper
 }
 
-func NewOrchestrator(ns *core.Namespace, config Config, plugins *Plugins, metrics metrics.Manager) Orchestrator {
+func NewOrchestrator(ns *core.Namespace, config Config, plugins *Plugins, metrics metrics.Manager, cacheManager cache.Manager) Orchestrator {
 	or := &orchestrator{
-		namespace: ns,
-		config:    config,
-		plugins:   plugins,
-		metrics:   metrics,
+		namespace:    ns,
+		config:       config,
+		plugins:      plugins,
+		metrics:      metrics,
+		cacheManager: cacheManager,
 	}
 	return or
 }
@@ -391,14 +394,32 @@ func (or *orchestrator) initHandlers(ctx context.Context) (err error) {
 	return nil
 }
 
+func (or *orchestrator) initMultiPartyComponents(ctx context.Context) (err error) {
+	if or.batch == nil {
+		or.batch, err = batch.NewBatchManager(ctx, or.namespace.Name, or.database(), or.data, or.identity, or.txHelper)
+		if err != nil {
+			return err
+		}
+	}
+
+	if or.messaging == nil {
+		if or.messaging, err = privatemessaging.NewPrivateMessaging(ctx, or.namespace, or.database(), or.dataexchange(), or.blockchain(), or.identity, or.batch, or.data, or.syncasync, or.multiparty, or.metrics, or.operations, or.cacheManager); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 
 	if or.txHelper == nil {
-		or.txHelper = txcommon.NewTransactionHelper(or.namespace.Name, or.database(), or.data)
+		if or.txHelper, err = txcommon.NewTransactionHelper(ctx, or.namespace.Name, or.database(), or.data, or.cacheManager); err != nil {
+			return err
+		}
 	}
 
 	if or.operations == nil {
-		if or.operations, err = operations.NewOperationsManager(ctx, or.namespace.Name, or.database(), or.txHelper); err != nil {
+		if or.operations, err = operations.NewOperationsManager(ctx, or.namespace.Name, or.database(), or.txHelper, or.cacheManager); err != nil {
 			return err
 		}
 	}
@@ -416,7 +437,7 @@ func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 	}
 
 	if or.identity == nil {
-		or.identity, err = identity.NewIdentityManager(ctx, or.namespace.Name, or.config.DefaultKey, or.database(), or.blockchain(), or.multiparty)
+		or.identity, err = identity.NewIdentityManager(ctx, or.namespace.Name, or.config.DefaultKey, or.database(), or.blockchain(), or.multiparty, or.cacheManager)
 		if err != nil {
 			return err
 		}
@@ -425,17 +446,8 @@ func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 	or.syncasync = syncasync.NewSyncAsyncBridge(ctx, or.namespace.Name, or.database(), or.data, or.operations)
 
 	if or.config.Multiparty.Enabled {
-		if or.batch == nil {
-			or.batch, err = batch.NewBatchManager(ctx, or.namespace.Name, or.database(), or.data, or.identity, or.txHelper)
-			if err != nil {
-				return err
-			}
-		}
-
-		if or.messaging == nil {
-			if or.messaging, err = privatemessaging.NewPrivateMessaging(ctx, or.namespace, or.database(), or.dataexchange(), or.blockchain(), or.identity, or.batch, or.data, or.syncasync, or.multiparty, or.metrics, or.operations); err != nil {
-				return err
-			}
+		if err = or.initMultiPartyComponents(ctx); err != nil {
+			return err
 		}
 	}
 
@@ -489,7 +501,7 @@ func (or *orchestrator) initManagers(ctx context.Context) (err error) {
 
 func (or *orchestrator) initComponents(ctx context.Context) (err error) {
 	if or.data == nil {
-		or.data, err = data.NewDataManager(ctx, or.namespace, or.database(), or.dataexchange())
+		or.data, err = data.NewDataManager(ctx, or.namespace, or.database(), or.dataexchange(), or.cacheManager)
 		if err != nil {
 			return err
 		}
@@ -500,7 +512,7 @@ func (or *orchestrator) initComponents(ctx context.Context) (err error) {
 	}
 
 	if or.events == nil {
-		or.events, err = events.NewEventManager(ctx, or.namespace, or.database(), or.blockchain(), or.identity, or.defhandler, or.data, or.defsender, or.broadcast, or.messaging, or.assets, or.sharedDownload, or.metrics, or.operations, or.txHelper, or.plugins.Events, or.multiparty)
+		or.events, err = events.NewEventManager(ctx, or.namespace, or.database(), or.blockchain(), or.identity, or.defhandler, or.data, or.defsender, or.broadcast, or.messaging, or.assets, or.sharedDownload, or.metrics, or.operations, or.txHelper, or.plugins.Events, or.multiparty, or.cacheManager)
 		if err != nil {
 			return err
 		}
diff --git a/internal/privatemessaging/groupmanager.go b/internal/privatemessaging/groupmanager.go
index 12bae8159..26244edff 100644
--- a/internal/privatemessaging/groupmanager.go
+++ b/internal/privatemessaging/groupmanager.go
@@ -19,17 +19,16 @@ package privatemessaging
 import (
 	"context"
 	"encoding/json"
-	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/i18n"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/data"
 	"github.com/hyperledger/firefly/internal/identity"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 )
 
 type GroupManager interface {
@@ -40,12 +39,11 @@ type GroupManager interface {
 }
 
 type groupManager struct {
-	namespace     *core.Namespace
-	database      database.Plugin
-	identity      identity.Manager
-	data          data.Manager
-	groupCacheTTL time.Duration
-	groupCache    *ccache.Cache
+	namespace  *core.Namespace
+	database   database.Plugin
+	identity   identity.Manager
+	data       data.Manager
+	groupCache cache.CInterface
 }
 
 type groupHashEntry struct {
@@ -181,9 +179,8 @@ func (gm *groupManager) GetGroups(ctx context.Context, filter database.AndFilter
 
 func (gm *groupManager) getGroupNodes(ctx context.Context, groupHash *fftypes.Bytes32, allowNil bool) (*core.Group, []*core.Identity, error) {
 
-	if cached := gm.groupCache.Get(groupHash.String()); cached != nil {
-		cached.Extend(gm.groupCacheTTL)
-		ghe := cached.Value().(*groupHashEntry)
+	if cachedValue := gm.groupCache.Get(groupHash.String()); cachedValue != nil {
+		ghe := cachedValue.(*groupHashEntry)
 		return ghe.group, ghe.nodes, nil
 	}
 
@@ -216,7 +213,7 @@ func (gm *groupManager) getGroupNodes(ctx context.Context, groupHash *fftypes.By
 	gm.groupCache.Set(group.Hash.String(), &groupHashEntry{
 		group: group,
 		nodes: nodes,
-	}, gm.groupCacheTTL)
+	})
 	return group, nodes, nil
 }
 
diff --git a/internal/privatemessaging/privatemessaging.go b/internal/privatemessaging/privatemessaging.go
index 678330063..e1c5436f5 100644
--- a/internal/privatemessaging/privatemessaging.go
+++ b/internal/privatemessaging/privatemessaging.go
@@ -26,6 +26,7 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/retry"
 	"github.com/hyperledger/firefly/internal/batch"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/coremsgs"
 	"github.com/hyperledger/firefly/internal/data"
@@ -38,7 +39,6 @@ import (
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
 	"github.com/hyperledger/firefly/pkg/dataexchange"
-	"github.com/karlseguin/ccache"
 )
 
 const pinnedPrivateDispatcherName = "pinned_private"
@@ -82,7 +82,7 @@ type blobTransferTracker struct {
 	op       *core.PreparedOperation
 }
 
-func NewPrivateMessaging(ctx context.Context, ns *core.Namespace, di database.Plugin, dx dataexchange.Plugin, bi blockchain.Plugin, im identity.Manager, ba batch.Manager, dm data.Manager, sa syncasync.Bridge, mult multiparty.Manager, mm metrics.Manager, om operations.Manager) (Manager, error) {
+func NewPrivateMessaging(ctx context.Context, ns *core.Namespace, di database.Plugin, dx dataexchange.Plugin, bi blockchain.Plugin, im identity.Manager, ba batch.Manager, dm data.Manager, sa syncasync.Bridge, mult multiparty.Manager, mm metrics.Manager, om operations.Manager, cacheManager cache.Manager) (Manager, error) {
 	if di == nil || im == nil || dx == nil || bi == nil || ba == nil || dm == nil || mm == nil || om == nil || mult == nil {
 		return nil, i18n.NewError(ctx, coremsgs.MsgInitializationNilDepError, "PrivateMessaging")
 	}
@@ -98,11 +98,10 @@ func NewPrivateMessaging(ctx context.Context, ns *core.Namespace, di database.Pl
 		syncasync:  sa,
 		multiparty: mult,
 		groupManager: groupManager{
-			namespace:     ns,
-			database:      di,
-			identity:      im,
-			data:          dm,
-			groupCacheTTL: config.GetDuration(coreconfig.GroupCacheTTL),
+			namespace: ns,
+			database:  di,
+			identity:  im,
+			data:      dm,
 		},
 		retry: retry.Retry{
 			InitialDelay: config.GetDuration(coreconfig.PrivateMessagingRetryInitDelay),
@@ -114,11 +113,20 @@ func NewPrivateMessaging(ctx context.Context, ns *core.Namespace, di database.Pl
 		operations:            om,
 		orgFirstNodes:         make(map[fftypes.UUID]*core.Identity),
 	}
-	pm.groupManager.groupCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetInt64(coreconfig.GroupCacheLimit)),
+
+	groupCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheGroupLimit,
+			coreconfig.CacheGroupTTL,
+			ns.Name,
+		),
 	)
+	if err != nil {
+		return nil, err
+	}
+
+	pm.groupManager.groupCache = groupCache
 
 	bo := batch.DispatcherOptions{
 		BatchType:      core.BatchTypePrivate,
diff --git a/internal/txcommon/txcommon.go b/internal/txcommon/txcommon.go
index 1f6ca4adc..b2e512c80 100644
--- a/internal/txcommon/txcommon.go
+++ b/internal/txcommon/txcommon.go
@@ -19,16 +19,14 @@ package txcommon
 import (
 	"context"
 	"strings"
-	"time"
 
-	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/data"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 )
 
 type Helper interface {
@@ -44,40 +42,56 @@ type transactionHelper struct {
 	namespace            string
 	database             database.Plugin
 	data                 data.Manager
-	transactionCache     *ccache.Cache
-	transactionCacheTTL  time.Duration
-	blockchainEventCache *ccache.Cache
-	blockchainEventTTL   time.Duration
+	transactionCache     cache.CInterface
+	blockchainEventCache cache.CInterface
 }
 
-func NewTransactionHelper(ns string, di database.Plugin, dm data.Manager) Helper {
+func NewTransactionHelper(ctx context.Context, ns string, di database.Plugin, dm data.Manager, cacheManager cache.Manager) (Helper, error) {
 	t := &transactionHelper{
 		namespace: ns,
 		database:  di,
 		data:      dm,
 	}
-	t.transactionCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetByteSize(coreconfig.TransactionCacheSize)),
+
+	transactionCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheTransactionSize,
+			coreconfig.CacheTransactionTTL,
+			ns,
+		),
 	)
-	t.blockchainEventCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetInt64(coreconfig.BlockchainEventCacheLimit)),
+
+	if err != nil {
+		return nil, err
+	}
+
+	t.transactionCache = transactionCache
+
+	blockchainEventCache, err := cacheManager.GetCache(
+		cache.NewCacheConfig(
+			ctx,
+			coreconfig.CacheBlockchainEventLimit,
+			coreconfig.CacheBlockchainEventTTL,
+			ns,
+		),
 	)
-	return t
+
+	if err != nil {
+		return nil, err
+	}
+	t.blockchainEventCache = blockchainEventCache
+	return t, nil
 }
 
 func (t *transactionHelper) updateTransactionsCache(tx *core.Transaction) {
-	t.transactionCache.Set(tx.ID.String(), tx, t.transactionCacheTTL)
+	t.transactionCache.Set(tx.ID.String(), tx)
 }
 
 func (t *transactionHelper) GetTransactionByIDCached(ctx context.Context, id *fftypes.UUID) (*core.Transaction, error) {
-	cached := t.transactionCache.Get(id.String())
-	if cached != nil {
-		cached.Extend(t.transactionCacheTTL)
-		return cached.Value().(*core.Transaction), nil
+
+	if cachedValue := t.transactionCache.Get(id.String()); cachedValue != nil {
+		return cachedValue.(*core.Transaction), nil
 	}
 	tx, err := t.database.GetTransactionByID(ctx, t.namespace, id)
 	if err != nil || tx == nil {
@@ -166,14 +180,13 @@ func (t *transactionHelper) AddBlockchainTX(ctx context.Context, tx *core.Transa
 }
 
 func (t *transactionHelper) addBlockchainEventToCache(chainEvent *core.BlockchainEvent) {
-	t.blockchainEventCache.Set(chainEvent.ID.String(), chainEvent, t.blockchainEventTTL)
+	t.blockchainEventCache.Set(chainEvent.ID.String(), chainEvent)
 }
 
 func (t *transactionHelper) GetBlockchainEventByIDCached(ctx context.Context, id *fftypes.UUID) (*core.BlockchainEvent, error) {
-	cached := t.blockchainEventCache.Get(id.String())
-	if cached != nil {
-		cached.Extend(t.blockchainEventTTL)
-		return cached.Value().(*core.BlockchainEvent), nil
+
+	if cachedValue := t.blockchainEventCache.Get(id.String()); cachedValue != nil {
+		return cachedValue.(*core.BlockchainEvent), nil
 	}
 	chainEvent, err := t.database.GetBlockchainEventByID(ctx, t.namespace, id)
 	if err != nil || chainEvent == nil {
diff --git a/mocks/blockchainmocks/plugin.go b/mocks/blockchainmocks/plugin.go
index 2564792a6..a1c5a5e90 100644
--- a/mocks/blockchainmocks/plugin.go
+++ b/mocks/blockchainmocks/plugin.go
@@ -3,9 +3,11 @@
 package blockchainmocks
 
 import (
-	config "github.com/hyperledger/firefly-common/pkg/config"
+	cache "github.com/hyperledger/firefly/internal/cache"
 	blockchain "github.com/hyperledger/firefly/pkg/blockchain"
 
+	config "github.com/hyperledger/firefly-common/pkg/config"
+
 	context "context"
 
 	core "github.com/hyperledger/firefly/pkg/core"
@@ -221,13 +223,13 @@ func (_m *Plugin) GetNetworkVersion(ctx context.Context, location *fftypes.JSONA
 	return r0, r1
 }
 
-// Init provides a mock function with given fields: ctx, cancelCtx, _a2, _a3
-func (_m *Plugin) Init(ctx context.Context, cancelCtx context.CancelFunc, _a2 config.Section, _a3 metrics.Manager) error {
-	ret := _m.Called(ctx, cancelCtx, _a2, _a3)
+// Init provides a mock function with given fields: ctx, cancelCtx, _a2, _a3, cacheManager
+func (_m *Plugin) Init(ctx context.Context, cancelCtx context.CancelFunc, _a2 config.Section, _a3 metrics.Manager, cacheManager cache.Manager) error {
+	ret := _m.Called(ctx, cancelCtx, _a2, _a3, cacheManager)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, context.CancelFunc, config.Section, metrics.Manager) error); ok {
-		r0 = rf(ctx, cancelCtx, _a2, _a3)
+	if rf, ok := ret.Get(0).(func(context.Context, context.CancelFunc, config.Section, metrics.Manager, cache.Manager) error); ok {
+		r0 = rf(ctx, cancelCtx, _a2, _a3, cacheManager)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -364,13 +366,13 @@ func (_m *Plugin) Start() error {
 	return r0
 }
 
-// SubmitBatchPin provides a mock function with given fields: ctx, nsOpID, networkNamespace, signingKey, batch, location
-func (_m *Plugin) SubmitBatchPin(ctx context.Context, nsOpID string, networkNamespace string, signingKey string, batch *blockchain.BatchPin, location *fftypes.JSONAny) error {
-	ret := _m.Called(ctx, nsOpID, networkNamespace, signingKey, batch, location)
+// SubmitBatchPin provides a mock function with given fields: ctx, nsOpID, remtoeNamespace, signingKey, batch, location
+func (_m *Plugin) SubmitBatchPin(ctx context.Context, nsOpID string, remtoeNamespace string, signingKey string, batch *blockchain.BatchPin, location *fftypes.JSONAny) error {
+	ret := _m.Called(ctx, nsOpID, remtoeNamespace, signingKey, batch, location)
 
 	var r0 error
 	if rf, ok := ret.Get(0).(func(context.Context, string, string, string, *blockchain.BatchPin, *fftypes.JSONAny) error); ok {
-		r0 = rf(ctx, nsOpID, networkNamespace, signingKey, batch, location)
+		r0 = rf(ctx, nsOpID, remtoeNamespace, signingKey, batch, location)
 	} else {
 		r0 = ret.Error(0)
 	}
diff --git a/mocks/cachemocks/manager.go b/mocks/cachemocks/manager.go
new file mode 100644
index 000000000..b8b0cd5c9
--- /dev/null
+++ b/mocks/cachemocks/manager.go
@@ -0,0 +1,52 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package cachemocks
+
+import (
+	cache "github.com/hyperledger/firefly/internal/cache"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Manager is an autogenerated mock type for the Manager type
+type Manager struct {
+	mock.Mock
+}
+
+// GetCache provides a mock function with given fields: cc
+func (_m *Manager) GetCache(cc *cache.CConfig) (cache.CInterface, error) {
+	ret := _m.Called(cc)
+
+	var r0 cache.CInterface
+	if rf, ok := ret.Get(0).(func(*cache.CConfig) cache.CInterface); ok {
+		r0 = rf(cc)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(cache.CInterface)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*cache.CConfig) error); ok {
+		r1 = rf(cc)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// ListKeys provides a mock function with given fields:
+func (_m *Manager) ListKeys() []string {
+	ret := _m.Called()
+
+	var r0 []string
+	if rf, ok := ret.Get(0).(func() []string); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]string)
+		}
+	}
+
+	return r0
+}
diff --git a/pkg/blockchain/plugin.go b/pkg/blockchain/plugin.go
index 8af1c5547..26c44cce4 100644
--- a/pkg/blockchain/plugin.go
+++ b/pkg/blockchain/plugin.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/metrics"
 	"github.com/hyperledger/firefly/pkg/core"
 )
@@ -33,7 +34,7 @@ type Plugin interface {
 	InitConfig(config config.Section)
 
 	// Init initializes the plugin, with configuration
-	Init(ctx context.Context, cancelCtx context.CancelFunc, config config.Section, metrics metrics.Manager) error
+	Init(ctx context.Context, cancelCtx context.CancelFunc, config config.Section, metrics metrics.Manager, cacheManager cache.Manager) error
 
 	// SetHandler registers a handler to receive callbacks
 	// Plugin will attempt (but is not guaranteed) to deliver events only for the given namespace


Test patch:
diff --git a/internal/assets/manager_test.go b/internal/assets/manager_test.go
index 851cf54ff..36ffef150 100644
--- a/internal/assets/manager_test.go
+++ b/internal/assets/manager_test.go
@@ -18,10 +18,13 @@ package assets
 import (
 	"context"
 	"testing"
+	"time"
 
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
@@ -56,13 +59,16 @@ func newTestAssetsCommon(t *testing.T, metrics bool) (*assetManager, func()) {
 	mpm := &privatemessagingmocks.Manager{}
 	mti := &tokenmocks.Plugin{}
 	mm := &metricsmocks.Manager{}
+	cmi := &cachemocks.Manager{}
+	ctx := context.Background()
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mm.On("IsMetricsEnabled").Return(metrics)
 	mm.On("TransferSubmitted", mock.Anything)
 	mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything)
 	mti.On("Name").Return("ut").Maybe()
-	ctx, cancel := context.WithCancel(context.Background())
+	ctx, cancel := context.WithCancel(ctx)
 	a, err := NewAssetManager(ctx, "ns1", "blockchain_plugin", mdi, map[string]tokens.Plugin{"magic-tokens": mti}, mim, msa, mbm, mpm, mm, mom, txHelper)
 	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything).Maybe()
 	rag.RunFn = func(a mock.Arguments) {
diff --git a/internal/batch/batch_manager_test.go b/internal/batch/batch_manager_test.go
index 867e9b7bb..48ad65c53 100644
--- a/internal/batch/batch_manager_test.go
+++ b/internal/batch/batch_manager_test.go
@@ -26,8 +26,10 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
@@ -47,7 +49,10 @@ func newTestBatchManager(t *testing.T) (*batchManager, func()) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bm, err := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	assert.NoError(t, err)
 	return bm.(*batchManager), bm.(*batchManager).cancelCtx
@@ -59,7 +64,10 @@ func TestE2EDispatchBroadcast(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mim.On("GetLocalNode", mock.Anything).Return(&core.Identity{}, nil)
 
 	readyForDispatch := make(chan bool)
@@ -176,7 +184,10 @@ func TestE2EDispatchPrivateUnpinned(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mim.On("GetLocalNode", mock.Anything).Return(&core.Identity{}, nil)
 
 	readyForDispatch := make(chan bool)
@@ -288,7 +299,10 @@ func TestDispatchUnknownType(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	ctx, cancel := context.WithCancel(context.Background())
 	bmi, _ := NewBatchManager(ctx, "ns1", mdi, mdm, mim, txHelper)
 	bm := bmi.(*batchManager)
@@ -319,7 +333,10 @@ func TestGetInvalidBatchTypeMsg(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bm, _ := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	defer bm.Close()
 	_, err := bm.(*batchManager).getProcessor(core.BatchTypeBroadcast, "wrong", nil, &core.SignerRef{})
@@ -330,7 +347,10 @@ func TestMessageSequencerCancelledContext(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mdi.On("GetMessageIDs", mock.Anything, "ns1", mock.Anything).Return(nil, fmt.Errorf("pop")).Once()
 	bm, _ := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	defer bm.Close()
@@ -345,7 +365,10 @@ func TestMessageSequencerMissingMessageData(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bm, _ := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	bm.RegisterDispatcher("utdispatcher", core.TransactionTypeNone, []core.MessageType{core.MessageTypeBroadcast},
 		func(c context.Context, state *DispatchState) error {
@@ -387,7 +410,10 @@ func TestMessageSequencerUpdateMessagesFail(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mim.On("GetLocalNode", mock.Anything).Return(&core.Identity{}, nil)
 	ctx, cancelCtx := context.WithCancel(context.Background())
 	bm, _ := NewBatchManager(ctx, "ns1", mdi, mdm, mim, txHelper)
@@ -442,7 +468,10 @@ func TestMessageSequencerDispatchFail(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mim.On("GetLocalNode", mock.Anything).Return(&core.Identity{}, nil)
 	ctx, cancelCtx := context.WithCancel(context.Background())
 	bm, _ := NewBatchManager(ctx, "ns1", mdi, mdm, mim, txHelper)
@@ -483,7 +512,9 @@ func TestMessageSequencerUpdateBatchFail(t *testing.T) {
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
 	ctx, cancelCtx := context.WithCancel(context.Background())
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mim.On("GetLocalNode", mock.Anything).Return(&core.Identity{}, nil)
 	bm, _ := NewBatchManager(ctx, "ns1", mdi, mdm, mim, txHelper)
 	bm.RegisterDispatcher("utdispatcher", core.TransactionTypeBatchPin, []core.MessageType{core.MessageTypeBroadcast},
@@ -567,7 +598,10 @@ func TestAssembleMessageDataNilData(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bm, _ := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	bm.Close()
 	mdm.On("GetMessageWithDataCached", mock.Anything, mock.Anything).Return(nil, nil, false, nil)
@@ -579,7 +613,10 @@ func TestGetMessageDataFail(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bm, _ := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	mdm.On("GetMessageWithDataCached", mock.Anything, mock.Anything).Return(nil, nil, false, fmt.Errorf("pop"))
 	bm.Close()
@@ -592,7 +629,10 @@ func TestGetMessageNotFound(t *testing.T) {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mim := &identitymanagermocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bm, _ := NewBatchManager(context.Background(), "ns1", mdi, mdm, mim, txHelper)
 	mdm.On("GetMessageWithDataCached", mock.Anything, mock.Anything).Return(nil, nil, false, nil)
 	bm.Close()
diff --git a/internal/batch/batch_processor_test.go b/internal/batch/batch_processor_test.go
index 75febc347..c9303ba56 100644
--- a/internal/batch/batch_processor_test.go
+++ b/internal/batch/batch_processor_test.go
@@ -25,8 +25,10 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/retry"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
@@ -40,7 +42,10 @@ func newTestBatchProcessor(t *testing.T, dispatch DispatchHandler) (func(), *dat
 	bm, cancel := newTestBatchManager(t)
 	mdi := bm.database.(*databasemocks.Plugin)
 	mdm := bm.data.(*datamocks.Manager)
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	bp := newBatchProcessor(bm, &batchProcessorConf{
 		txType:   core.TransactionTypeBatchPin,
 		signer:   core.SignerRef{Author: "did:firefly:org/abcd", Key: "0x12345"},
diff --git a/internal/blockchain/ethereum/address_resolver_test.go b/internal/blockchain/ethereum/address_resolver_test.go
index db121c7cc..627dac4ed 100644
--- a/internal/blockchain/ethereum/address_resolver_test.go
+++ b/internal/blockchain/ethereum/address_resolver_test.go
@@ -23,11 +23,15 @@ import (
 	"net/http/httptest"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 	"golang.org/x/net/context"
 )
 
@@ -57,9 +61,11 @@ func TestAddressResolverInEthereumOKCached(t *testing.T) {
 	config.Set(AddressResolverURLTemplate, fmt.Sprintf("%s/resolve/{{.Key}}", server.URL))
 
 	ctx, cancel := context.WithCancel(context.Background())
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
 	defer cancel()
 
-	ar, err := newAddressResolver(ctx, config)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	e := &Ethereum{
@@ -98,8 +104,9 @@ func TestAddressResolverPOSTOk(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	resolved, err := ar.NormalizeSigningKey(ctx, "testkeystring")
@@ -125,8 +132,9 @@ func TestAddressResolverPOSTBadKey(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	_, err = ar.NormalizeSigningKey(ctx, "testkeystring")
@@ -148,8 +156,9 @@ func TestAddressResolverPOSTResponse(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	_, err = ar.NormalizeSigningKey(ctx, "testkeystring")
@@ -169,8 +178,9 @@ func TestAddressResolverFailureResponse(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	_, err = ar.NormalizeSigningKey(ctx, "testkeystring")
@@ -190,8 +200,9 @@ func TestAddressResolverErrorResponse(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	_, err = ar.NormalizeSigningKey(ctx, "testkeystring")
@@ -207,8 +218,9 @@ func TestAddressResolverBadBodyTemplate(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	_, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	_, err := newAddressResolver(ctx, config, cmi)
 	assert.Regexp(t, "FF10337.*bodyTemplate", err)
 
 }
@@ -220,8 +232,9 @@ func TestAddressResolverErrorURLTemplate(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
 	assert.NoError(t, err)
 
 	_, err = ar.NormalizeSigningKey(ctx, "testkeystring")
@@ -237,8 +250,15 @@ func TestAddressResolverErrorBodyTemplate(t *testing.T) {
 
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
-
-	ar, err := newAddressResolver(ctx, config)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ar, err := newAddressResolver(ctx, config, cmi)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheAddressResolverLimit,
+		coreconfig.CacheAddressResolverTTL,
+		"",
+	))
 	assert.NoError(t, err)
 
 	_, err = ar.NormalizeSigningKey(ctx, "testkeystring")
diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go
index e3fd5deb6..8321c1b57 100644
--- a/internal/blockchain/ethereum/ethereum_test.go
+++ b/internal/blockchain/ethereum/ethereum_test.go
@@ -32,15 +32,16 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/wsclient"
 	"github.com/hyperledger/firefly/internal/blockchain/common"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/coremocks"
 	"github.com/hyperledger/firefly/mocks/metricsmocks"
 	"github.com/hyperledger/firefly/mocks/wsmocks"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/jarcoal/httpmock"
-	"github.com/karlseguin/ccache"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -93,7 +94,7 @@ func newTestEthereum() (*Ethereum, func()) {
 		prefixLong:  defaultPrefixLong,
 		wsconn:      wsm,
 		metrics:     mm,
-		cache:       ccache.New(ccache.Configure().MaxSize(100)),
+		cache:       cache.NewUmanagedCache(ctx, 100, 5*time.Minute),
 		callbacks:   common.NewBlockchainCallbacks(),
 		subs:        common.NewFireflySubscriptions(),
 	}
@@ -107,7 +108,7 @@ func newTestEthereum() (*Ethereum, func()) {
 }
 
 func newTestStreamManager(client *resty.Client) *streamManager {
-	return newStreamManager(client, ccache.New(ccache.Configure()), 5*time.Minute)
+	return newStreamManager(client, cache.NewUmanagedCache(context.Background(), 100, 5*time.Minute))
 }
 
 func mockNetworkVersion(t *testing.T, version int) func(req *http.Request) (*http.Response, error) {
@@ -130,7 +131,9 @@ func TestInitMissingURL(t *testing.T) {
 	e, cancel := newTestEthereum()
 	defer cancel()
 	resetConf(e)
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF10138.*url", err)
 }
 
@@ -138,8 +141,10 @@ func TestInitBadAddressResolver(t *testing.T) {
 	e, cancel := newTestEthereum()
 	defer cancel()
 	resetConf(e)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
 	utAddressResolverConf.Set(AddressResolverURLTemplate, "{{unclosed}")
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF10337.*urlTemplate", err)
 }
 
@@ -150,7 +155,9 @@ func TestInitMissingTopic(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPConfigURL, "http://localhost:12345")
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x12345")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF10138.*topic", err)
 }
 
@@ -183,7 +190,9 @@ func TestInitAndStartWithFFTM(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 	utFFTMConf.Set(ffresty.HTTPConfigURL, "http://fftm.example.com:12345")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	assert.NotNil(t, e.fftmClient)
 
@@ -224,7 +233,9 @@ func TestWSInitFail(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF00149", err)
 
 }
@@ -269,7 +280,9 @@ func TestInitOldInstancePathContracts(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/contracts/firefly")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
@@ -304,7 +317,9 @@ func TestInitOldInstancePathInstances(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
@@ -328,7 +343,9 @@ func TestInitNewConfig(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Equal(t, 2, httpmock.GetTotalCallCount())
 	assert.NoError(t, err)
 }
@@ -352,7 +369,9 @@ func TestInitNewConfigBadIndex(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 }
 
@@ -381,7 +400,9 @@ func TestInitNetworkVersionNotFound(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 }
@@ -404,7 +425,9 @@ func TestStreamQueryError(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF10111.*pop", err)
 
 }
@@ -430,7 +453,9 @@ func TestStreamCreateError(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF10111.*pop", err)
 
 }
@@ -456,7 +481,9 @@ func TestStreamUpdateError(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.Regexp(t, "FF10111.*pop", err)
 }
 
@@ -490,7 +517,9 @@ func TestInitAllExistingStreams(t *testing.T) {
 		"address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -530,7 +559,9 @@ func TestInitAllExistingStreamsV1(t *testing.T) {
 		"address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -570,7 +601,9 @@ func TestInitAllExistingStreamsOld(t *testing.T) {
 		"address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -610,7 +643,9 @@ func TestInitAllExistingStreamsInvalidName(t *testing.T) {
 		"address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -640,7 +675,9 @@ func TestSubQueryError(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 }
 
@@ -669,7 +706,9 @@ func TestSubQueryCreateError(t *testing.T) {
 	utEthconnectConf.Set(EthconnectConfigInstanceDeprecated, "/instances/0x71C7656EC7ab88b098defB751B7401B5f6d8976F")
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 }
 
@@ -3001,9 +3040,17 @@ func TestAddAndRemoveFireflySubscription(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	originalContext := e.ctx
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		originalContext,
+		coreconfig.CacheBlockchainLimit,
+		coreconfig.CacheBlockchainTTL,
+		"",
+	))
 	assert.NoError(t, err)
-
 	location := fftypes.JSONAnyPtr(fftypes.JSONObject{
 		"address": "0x123",
 	}.String())
@@ -3042,7 +3089,9 @@ func TestAddFireflySubscriptionV1(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 
 	location := fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -3078,7 +3127,9 @@ func TestAddFireflySubscriptionQuerySubsFail(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 
 	location := fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -3114,7 +3165,9 @@ func TestAddFireflySubscriptionCreateError(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 
 	location := fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -3149,7 +3202,9 @@ func TestAddFireflySubscriptionGetVersionError(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 
 	location := fftypes.JSONAnyPtr(fftypes.JSONObject{
@@ -3194,7 +3249,9 @@ func TestGetContractListenerStatus(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 
 	status, err := e.GetContractListenerStatus(context.Background(), "sub1")
@@ -3224,7 +3281,9 @@ func TestGetContractListenerStatusGetSubFail(t *testing.T) {
 	utEthconnectConf.Set(ffresty.HTTPCustomClient, mockedClient)
 	utEthconnectConf.Set(EthconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, e.metrics, cmi)
 	assert.NoError(t, err)
 
 	status, err := e.GetContractListenerStatus(context.Background(), "sub1")
diff --git a/internal/blockchain/fabric/fabric_test.go b/internal/blockchain/fabric/fabric_test.go
index 453cd452e..e04e22e50 100644
--- a/internal/blockchain/fabric/fabric_test.go
+++ b/internal/blockchain/fabric/fabric_test.go
@@ -33,15 +33,16 @@ import (
 	"github.com/hyperledger/firefly-common/pkg/log"
 	"github.com/hyperledger/firefly-common/pkg/wsclient"
 	"github.com/hyperledger/firefly/internal/blockchain/common"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/coremocks"
 	"github.com/hyperledger/firefly/mocks/metricsmocks"
 	"github.com/hyperledger/firefly/mocks/wsmocks"
 	"github.com/hyperledger/firefly/pkg/blockchain"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/jarcoal/httpmock"
-	"github.com/karlseguin/ccache"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -67,7 +68,7 @@ func newTestFabric() (*Fabric, func()) {
 		prefixShort:    defaultPrefixShort,
 		prefixLong:     defaultPrefixLong,
 		wsconn:         wsm,
-		cache:          ccache.New(ccache.Configure().MaxSize(100)),
+		cache:          cache.NewUmanagedCache(ctx, 100, 5*time.Minute),
 		callbacks:      common.NewBlockchainCallbacks(),
 		subs:           common.NewFireflySubscriptions(),
 	}
@@ -81,7 +82,7 @@ func newTestFabric() (*Fabric, func()) {
 }
 
 func newTestStreamManager(client *resty.Client, signer string) *streamManager {
-	return newStreamManager(client, signer, ccache.New(ccache.Configure()), 5*time.Minute)
+	return newStreamManager(client, signer, cache.NewUmanagedCache(context.Background(), 100, 5*time.Minute))
 }
 
 func testFFIMethod() *fftypes.FFIMethod {
@@ -127,7 +128,9 @@ func TestInitMissingURL(t *testing.T) {
 	e, cancel := newTestFabric()
 	defer cancel()
 	resetConf(e)
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+
+	cmi := &cachemocks.Manager{}
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.Regexp(t, "FF10138.*url", err)
 }
 
@@ -139,7 +142,9 @@ func TestInitMissingTopic(t *testing.T) {
 	utFabconnectConf.Set(FabconnectConfigChaincodeDeprecated, "Firefly")
 	utFabconnectConf.Set(FabconnectConfigSigner, "signer001")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.Regexp(t, "FF10138.*topic", err)
 }
 
@@ -172,7 +177,16 @@ func TestInitAllNewStreamsAndWSEvent(t *testing.T) {
 	utFabconnectConf.Set(FabconnectConfigSigner, "signer001")
 	utFabconnectConf.Set(FabconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	originalContext := e.ctx
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		originalContext,
+		coreconfig.CacheBlockchainLimit,
+		coreconfig.CacheBlockchainTTL,
+		"",
+	))
 	assert.NoError(t, err)
 
 	assert.Equal(t, "fabric", e.Name())
@@ -212,7 +226,9 @@ func TestWSInitFail(t *testing.T) {
 	utFabconnectConf.Set(FabconnectConfigSigner, "signer001")
 	utFabconnectConf.Set(FabconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.Regexp(t, "FF00149", err)
 
 }
@@ -246,7 +262,9 @@ func TestInitAllExistingStreams(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -285,7 +303,9 @@ func TestInitAllExistingStreamsV1(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -322,7 +342,9 @@ func TestAddFireflySubscriptionQuerySubsFail(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "newest")
@@ -358,7 +380,9 @@ func TestAddFireflySubscriptionGetVersionError(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "newest")
@@ -394,7 +418,9 @@ func TestAddAndRemoveFireflySubscriptionDeprecatedSubName(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	subID, err := e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -437,7 +463,9 @@ func TestAddFireflySubscriptionInvalidSubName(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -471,7 +499,9 @@ func TestInitNewConfig(t *testing.T) {
 	utFabconnectConf.Set(FabconnectConfigSigner, "signer001")
 	utFabconnectConf.Set(FabconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 
 	assert.Equal(t, 1, httpmock.GetTotalCallCount())
@@ -498,7 +528,9 @@ func TestStreamQueryError(t *testing.T) {
 	utFabconnectConf.Set(FabconnectConfigSigner, "signer001")
 	utFabconnectConf.Set(FabconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.Regexp(t, "FF10284.*pop", err)
 
 }
@@ -525,7 +557,9 @@ func TestStreamCreateError(t *testing.T) {
 	utFabconnectConf.Set(FabconnectConfigSigner, "signer001")
 	utFabconnectConf.Set(FabconnectConfigTopic, "topic1")
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.Regexp(t, "FF10284.*pop", err)
 
 }
@@ -563,7 +597,9 @@ func TestSubQueryCreateError(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
@@ -604,7 +640,9 @@ func TestSubQueryCreate(t *testing.T) {
 		"chaincode": "simplestorage",
 	}.String())
 
-	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{})
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(e.ctx, 100, 5*time.Minute), nil)
+	err := e.Init(e.ctx, e.cancelCtx, utConfig, &metricsmocks.Manager{}, cmi)
 	assert.NoError(t, err)
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
 	_, err = e.AddFireflySubscription(e.ctx, ns, location, "oldest")
diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go
new file mode 100644
index 000000000..ef0730039
--- /dev/null
+++ b/internal/cache/cache_test.go
@@ -0,0 +1,92 @@
+// Copyright © 2022 Kaleido, Inc.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 cache
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/hyperledger/firefly-common/pkg/config"
+	"github.com/hyperledger/firefly/internal/coreconfig"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewCacheCreationFail(t *testing.T) {
+	coreconfig.Reset()
+	ctx := context.Background()
+	cacheManager := NewCacheManager(ctx)
+	_, err := cacheManager.GetCache(NewCacheConfig(ctx, "", "", ""))
+	assert.Equal(t, "FF10424: could not initialize cache - size limit config key is not provided", err.Error())
+	_, err = cacheManager.GetCache(NewCacheConfig(ctx, "test.limit", "", ""))
+	assert.Equal(t, "FF10425: could not initialize cache - ttl config key is not provided", err.Error())
+	_, err = cacheManager.GetCache(NewCacheConfig(ctx, "inconsistent1.limit", "inconsistent2.ttl", ""))
+	assert.Equal(t, "FF10426: could not initialize cache - 'inconsistent1.limit' and 'inconsistent2.ttl' do not have identical prefix, mismatching prefixes are: 'inconsistent1','inconsistent2'", err.Error())
+	_, err = cacheManager.GetCache(NewCacheConfig(ctx, "test.max", "test.ttl", ""))
+	assert.Equal(t, "FF10427: could not initialize cache - 'max' is not an expected size configuration key suffix. Expected values are: 'size', 'limit'", err.Error())
+}
+
+func TestGetCacheReturnsSameCacheForSameConfig(t *testing.T) {
+	coreconfig.Reset()
+	ctx := context.Background()
+	cacheManager := NewCacheManager(ctx)
+	cache0, _ := cacheManager.GetCache(NewCacheConfig(ctx, "cache.batch.limit", "cache.batch.ttl", "testnamespace"))
+	cache1, _ := cacheManager.GetCache(NewCacheConfig(ctx, "cache.batch.limit", "cache.batch.ttl", "testnamespace"))
+
+	assert.Equal(t, cache0, cache1)
+	assert.Equal(t, []string{"testnamespace::cache.batch"}, cacheManager.ListKeys())
+
+	cache2, _ := cacheManager.GetCache(NewCacheConfig(ctx, "cache.batch.limit", "cache.batch.ttl", ""))
+	assert.NotEqual(t, cache0, cache2)
+	assert.Equal(t, 2, len(cacheManager.ListKeys()))
+}
+
+func TestTwoSeparateCacheWorksIndependently(t *testing.T) {
+	coreconfig.Reset()
+	ctx := context.Background()
+	cacheManager := NewCacheManager(ctx)
+	cache0, _ := cacheManager.GetCache(NewCacheConfig(ctx, "cache.batch.limit", "cache.batch.ttl", ""))
+	cache1, _ := cacheManager.GetCache(NewCacheConfig(ctx, "cache.message.size", "cache.message.ttl", ""))
+
+	cache0.SetInt("int0", 100)
+	assert.Equal(t, 100, cache0.GetInt("int0"))
+	assert.Equal(t, 100, cache0.Get("int0").(int))
+	assert.Equal(t, 0, cache1.GetInt("int0"))
+	assert.Equal(t, nil, cache1.Get("int0"))
+
+	cache1.SetString("string1", "val1")
+	assert.Equal(t, "", cache0.GetString("string1"))
+	assert.Equal(t, nil, cache0.Get("string1"))
+	assert.Equal(t, "val1", cache1.GetString("string1"))
+	assert.Equal(t, "val1", cache1.Get("string1").(string))
+}
+
+func TestReturnsDummyCacheWhenCacheDisabled(t *testing.T) {
+	coreconfig.Reset()
+	config.Set(coreconfig.CacheEnabled, false)
+	ctx := context.Background()
+	cacheManager := NewCacheManager(ctx)
+	cache0, _ := cacheManager.GetCache(NewCacheConfig(ctx, "cache.batch.limit", "cache.batch.ttl", ""))
+	cache0.SetInt("int0", 100)
+	assert.Equal(t, nil, cache0.Get("int0"))
+}
+
+func TestUmmanagedCacheInstance(t *testing.T) {
+	uc0 := NewUmanagedCache(context.Background(), 100, 5*time.Minute)
+	uc1 := NewUmanagedCache(context.Background(), 100, 5*time.Minute)
+	assert.NotEqual(t, uc0, uc1)
+}
diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go
index 884d76d0d..eaf229e19 100644
--- a/internal/contracts/manager_test.go
+++ b/internal/contracts/manager_test.go
@@ -20,13 +20,16 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-signer/pkg/ffi2abi"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/identity"
 	"github.com/hyperledger/firefly/internal/syncasync"
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/identitymanagermocks"
@@ -46,7 +49,10 @@ func newTestContractManager() *contractManager {
 	mim := &identitymanagermocks.Manager{}
 	mbi := &blockchainmocks.Plugin{}
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	msa := &syncasyncmocks.Bridge{}
 	mbi.On("GetFFIParamValidator", mock.Anything).Return(nil, nil)
 	mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything)
@@ -80,7 +86,10 @@ func TestNewContractManagerFFISchemaLoaderFail(t *testing.T) {
 	mim := &identitymanagermocks.Manager{}
 	mbi := &blockchainmocks.Plugin{}
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	msa := &syncasyncmocks.Bridge{}
 	mbi.On("GetFFIParamValidator", mock.Anything).Return(nil, fmt.Errorf("pop"))
 	_, err := NewContractManager(context.Background(), "ns1", mdi, mbi, mim, mom, txHelper, msa)
@@ -93,7 +102,10 @@ func TestNewContractManagerFFISchemaLoader(t *testing.T) {
 	mim := &identitymanagermocks.Manager{}
 	mbi := &blockchainmocks.Plugin{}
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	msa := &syncasyncmocks.Bridge{}
 	mbi.On("GetFFIParamValidator", mock.Anything).Return(&ffi2abi.ParamValidator{}, nil)
 	mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything)
diff --git a/internal/data/data_manager_test.go b/internal/data/data_manager_test.go
index ec3a4a787..2038e6ca6 100644
--- a/internal/data/data_manager_test.go
+++ b/internal/data/data_manager_test.go
@@ -20,10 +20,13 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/dataexchangemocks"
 	"github.com/hyperledger/firefly/pkg/core"
@@ -42,7 +45,22 @@ func newTestDataManager(t *testing.T) (*dataManager, context.Context, func()) {
 	})
 	mdx := &dataexchangemocks.Plugin{}
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
-	dm, err := NewDataManager(ctx, ns, mdi, mdx)
+
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	dm, err := NewDataManager(ctx, ns, mdi, mdx, cmi)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheMessageSize,
+		coreconfig.CacheMessageTTL,
+		ns.Name,
+	))
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheValidatorSize,
+		coreconfig.CacheValidatorTTL,
+		ns.Name,
+	))
 	assert.NoError(t, err)
 	assert.True(t, dm.BlobsEnabled())
 	dm.Start()
@@ -214,12 +232,11 @@ func TestWriteNewMessageE2E(t *testing.T) {
 }
 
 func TestInitBadDeps(t *testing.T) {
-	_, err := NewDataManager(context.Background(), &core.Namespace{}, nil, nil)
+	_, err := NewDataManager(context.Background(), &core.Namespace{}, nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 }
 
 func TestValidatorLookupCached(t *testing.T) {
-
 	coreconfig.Reset()
 	dm, ctx, cancel := newTestDataManager(t)
 	defer cancel()
diff --git a/internal/events/aggregator_test.go b/internal/events/aggregator_test.go
index 011934a1b..81ccdd1c4 100644
--- a/internal/events/aggregator_test.go
+++ b/internal/events/aggregator_test.go
@@ -21,14 +21,17 @@ import (
 	"encoding/json"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/data"
 	"github.com/hyperledger/firefly/internal/definitions"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/definitionsmocks"
@@ -68,6 +71,7 @@ func (tag *testAggregator) cleanup(t *testing.T) {
 
 func newTestAggregatorCommon(metrics bool) *testAggregator {
 	coreconfig.Reset()
+	ctx, ctxCancel := context.WithCancel(context.Background())
 	logrus.SetLevel(logrus.DebugLevel)
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
@@ -75,18 +79,18 @@ func newTestAggregatorCommon(metrics bool) *testAggregator {
 	mdh := &definitionsmocks.Handler{}
 	mim := &identitymanagermocks.Manager{}
 	mmi := &metricsmocks.Manager{}
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
 	mbi := &blockchainmocks.Plugin{}
 	if metrics {
 		mmi.On("MessageConfirmed", mock.Anything, core.EventTypeMessageConfirmed).Return()
 	}
 	mmi.On("IsMetricsEnabled").Return(metrics).Maybe()
 	mbi.On("VerifierType").Return(core.VerifierTypeEthAddress)
-	ctx, ctxCancel := context.WithCancel(context.Background())
-	ag := newAggregator(ctx, "ns1", mdi, mbi, mpm, mdh, mim, mdm, newEventNotifier(ctx, "ut"), mmi)
+	ag, _ := newAggregator(ctx, "ns1", mdi, mbi, mpm, mdh, mim, mdm, newEventNotifier(ctx, "ut"), mmi, cmi)
 	cancel := func() {
 		ctxCancel()
 		if ag.batchCache != nil {
-			ag.batchCache.Stop()
 			ag.batchCache = nil
 		}
 	}
@@ -167,6 +171,31 @@ func newTestManifest(mType core.MessageType, groupID *fftypes.Bytes32) (*core.Me
 	}
 }
 
+func TestNewAggregator(t *testing.T) {
+	coreconfig.Reset()
+	ctx := context.Background()
+	logrus.SetLevel(logrus.DebugLevel)
+	mdi := &databasemocks.Plugin{}
+	mdm := &datamocks.Manager{}
+	mpm := &privatemessagingmocks.Manager{}
+	mdh := &definitionsmocks.Handler{}
+	mim := &identitymanagermocks.Manager{}
+	mmi := &metricsmocks.Manager{}
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	mbi := &blockchainmocks.Plugin{}
+	mbi.On("VerifierType").Return(core.VerifierTypeEthAddress)
+	ns := "ns1"
+	_, err := newAggregator(ctx, ns, mdi, mbi, mpm, mdh, mim, mdm, newEventNotifier(ctx, "ut"), mmi, cmi)
+	assert.NoError(t, err)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheBatchLimit,
+		coreconfig.CacheBatchTTL,
+		ns,
+	))
+}
+
 func TestAggregationMaskedZeroNonceMatch(t *testing.T) {
 
 	ag := newTestAggregatorWithMetrics()
diff --git a/internal/events/event_dispatcher_test.go b/internal/events/event_dispatcher_test.go
index 65b309f65..a00179ee3 100644
--- a/internal/events/event_dispatcher_test.go
+++ b/internal/events/event_dispatcher_test.go
@@ -21,13 +21,16 @@ import (
 	"fmt"
 	"regexp"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly-common/pkg/log"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/eventsmocks"
@@ -50,7 +53,10 @@ func newTestEventDispatcher(sub *subscription) (*eventDispatcher, func()) {
 	mbm := &broadcastmocks.Manager{}
 	mpm := &privatemessagingmocks.Manager{}
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	enricher := newEventEnricher("ns1", mdi, mdm, mom, txHelper)
 	ctx, cancel := context.WithCancel(context.Background())
 	return newEventDispatcher(ctx, enricher, mei, mdi, mdm, mbm, mpm, fftypes.NewUUID().String(), sub, newEventNotifier(ctx, "ut"), txHelper), func() {
diff --git a/internal/events/event_enrich_test.go b/internal/events/event_enrich_test.go
index 3d84a0a07..4c708cf7a 100644
--- a/internal/events/event_enrich_test.go
+++ b/internal/events/event_enrich_test.go
@@ -20,9 +20,12 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/txcommon"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/operationmocks"
@@ -35,7 +38,10 @@ func newTestEventEnricher() *eventEnricher {
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	return newEventEnricher("ns1", mdi, mdm, mom, txHelper)
 }
 
diff --git a/internal/events/event_manager_test.go b/internal/events/event_manager_test.go
index b5f0d2b5f..f3b978510 100644
--- a/internal/events/event_manager_test.go
+++ b/internal/events/event_manager_test.go
@@ -20,15 +20,18 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/events/system"
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/mocks/assetmocks"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/definitionsmocks"
@@ -127,6 +130,8 @@ func newTestEventManagerCommon(t *testing.T, metrics, dbconcurrency bool) *testE
 	mam := &assetmocks.Manager{}
 	msd := &shareddownloadmocks.Manager{}
 	mmi := &metricsmocks.Manager{}
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
 	mom := &operationmocks.Manager{}
 	mev := &eventsmocks.Plugin{}
 	events := map[string]events.Plugin{"websockets": mev}
@@ -142,10 +147,16 @@ func newTestEventManagerCommon(t *testing.T, metrics, dbconcurrency bool) *testE
 	mev.On("SetHandler", "ns1", mock.Anything).Return(nil).Maybe()
 	mev.On("ValidateOptions", mock.Anything).Return(nil).Maybe()
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
-	emi, err := NewEventManager(ctx, ns, mdi, mbi, mim, msh, mdm, mds, mbm, mpm, mam, msd, mmi, mom, txHelper, events, mmp)
+	emi, err := NewEventManager(ctx, ns, mdi, mbi, mim, msh, mdm, mds, mbm, mpm, mam, msd, mmi, mom, txHelper, events, mmp, cmi)
 	em := emi.(*eventManager)
 	mockRunAsGroupPassthrough(mdi)
 	assert.NoError(t, err)
+	cmi.AssertCalled(t, "GetCache", 		cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheEventListenerTopicLimit,
+		coreconfig.CacheEventListenerTopicTTL,
+		ns.Name,
+	),)
 	return &testEventManager{
 		eventManager: *em,
 		cancel:       cancel,
@@ -195,7 +206,7 @@ func TestStartStop(t *testing.T) {
 }
 
 func TestStartStopBadDependencies(t *testing.T) {
-	_, err := NewEventManager(context.Background(), &core.Namespace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
+	_, err := NewEventManager(context.Background(), &core.Namespace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 
 }
@@ -218,12 +229,15 @@ func TestStartStopEventListenerFail(t *testing.T) {
 	mev := &eventsmocks.Plugin{}
 	events := map[string]events.Plugin{"websockets": mev}
 	mmp := &multipartymocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	mdi.On("Capabilities").Return(&database.Capabilities{Concurrency: false})
 	mbi.On("VerifierType").Return(core.VerifierTypeEthAddress)
 	mev.On("SetHandler", "ns1", mock.Anything).Return(fmt.Errorf("pop"))
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
-	_, err := NewEventManager(context.Background(), ns, mdi, mbi, mim, msh, mdm, mds, mbm, mpm, mam, msd, mm, mom, txHelper, events, mmp)
+	_, err := NewEventManager(context.Background(), ns, mdi, mbi, mim, msh, mdm, mds, mbm, mpm, mam, msd, mm, mom, txHelper, events, mmp, cmi)
 	assert.EqualError(t, err, "pop")
 }
 
diff --git a/internal/events/subscription_manager_test.go b/internal/events/subscription_manager_test.go
index ad52886a7..99ca3efc0 100644
--- a/internal/events/subscription_manager_test.go
+++ b/internal/events/subscription_manager_test.go
@@ -20,12 +20,15 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/mocks/eventsmocks"
@@ -46,7 +49,10 @@ func newTestSubManager(t *testing.T, mei *eventsmocks.Plugin) (*subscriptionMana
 	mbm := &broadcastmocks.Manager{}
 	mpm := &privatemessagingmocks.Manager{}
 	mom := &operationmocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	enricher := newEventEnricher("ns1", mdi, mdm, mom, txHelper)
 
 	ctx, cancel := context.WithCancel(context.Background())
diff --git a/internal/identity/identitymanager_test.go b/internal/identity/identitymanager_test.go
index ac21b68bd..9af0c70a4 100644
--- a/internal/identity/identitymanager_test.go
+++ b/internal/identity/identitymanager_test.go
@@ -20,11 +20,14 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/multiparty"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/multipartymocks"
 	"github.com/hyperledger/firefly/pkg/core"
@@ -38,17 +41,31 @@ func newTestIdentityManager(t *testing.T) (context.Context, *identityManager) {
 	mdi := &databasemocks.Plugin{}
 	mbi := &blockchainmocks.Plugin{}
 	mmp := &multipartymocks.Manager{}
-
-	mbi.On("VerifierType").Return(core.VerifierTypeEthAddress).Maybe()
-
 	ctx := context.Background()
-	im, err := NewIdentityManager(ctx, "ns1", "", mdi, mbi, mmp)
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	mbi.On("VerifierType").Return(core.VerifierTypeEthAddress).Maybe()
+	ns := "ns1"
+	im, err := NewIdentityManager(ctx, ns, "", mdi, mbi, mmp, cmi)
 	assert.NoError(t, err)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheIdentityLimit,
+		coreconfig.CacheIdentityTTL,
+		ns,
+	))
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheSigningKeyLimit,
+		coreconfig.CacheSigningKeyTTL,
+		ns,
+	))
+
 	return ctx, im.(*identityManager)
 }
 
 func TestNewIdentityManagerMissingDeps(t *testing.T) {
-	_, err := NewIdentityManager(context.Background(), "", "", nil, nil, nil)
+	_, err := NewIdentityManager(context.Background(), "", "", nil, nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 }
 
diff --git a/internal/namespace/manager_test.go b/internal/namespace/manager_test.go
index adb0bbc4f..51de29ff7 100644
--- a/internal/namespace/manager_test.go
+++ b/internal/namespace/manager_test.go
@@ -34,6 +34,7 @@ import (
 	"github.com/hyperledger/firefly/internal/sharedstorage/ssfactory"
 	"github.com/hyperledger/firefly/internal/tokens/tifactory"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/dataexchangemocks"
 	"github.com/hyperledger/firefly/mocks/eventsmocks"
@@ -57,6 +58,7 @@ type testNamespaceManager struct {
 	mmi  *metricsmocks.Manager
 	mae  *spieventsmocks.Manager
 	mbi  *blockchainmocks.Plugin
+	cmi  *cachemocks.Manager
 	mdi  *databasemocks.Plugin
 	mdx  *dataexchangemocks.Plugin
 	mps  *sharedstoragemocks.Plugin
@@ -69,6 +71,7 @@ func (nm *testNamespaceManager) cleanup(t *testing.T) {
 	nm.mmi.AssertExpectations(t)
 	nm.mae.AssertExpectations(t)
 	nm.mbi.AssertExpectations(t)
+	nm.cmi.AssertExpectations(t)
 	nm.mdi.AssertExpectations(t)
 	nm.mdx.AssertExpectations(t)
 	nm.mps.AssertExpectations(t)
@@ -86,6 +89,7 @@ func newTestNamespaceManager(resetConfig bool) *testNamespaceManager {
 		mmi:  &metricsmocks.Manager{},
 		mae:  &spieventsmocks.Manager{},
 		mbi:  &blockchainmocks.Plugin{},
+		cmi:  &cachemocks.Manager{},
 		mdi:  &databasemocks.Plugin{},
 		mdx:  &dataexchangemocks.Plugin{},
 		mps:  &sharedstoragemocks.Plugin{},
@@ -153,7 +157,7 @@ func TestInit(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -195,7 +199,7 @@ func TestInitBlockchainFail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(fmt.Errorf("pop"))
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(fmt.Errorf("pop"))
 
 	ctx, cancelCtx := context.WithCancel(context.Background())
 	err := nm.Init(ctx, cancelCtx, nm.reset)
@@ -210,7 +214,7 @@ func TestInitDataExchangeFail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
 
 	ctx, cancelCtx := context.WithCancel(context.Background())
@@ -226,7 +230,7 @@ func TestInitSharedStorageFail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
 
@@ -243,7 +247,7 @@ func TestInitTokensFail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, mock.Anything, nil).Return(fmt.Errorf("pop"))
@@ -261,7 +265,7 @@ func TestInitEventsFail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -281,7 +285,7 @@ func TestInitAuthFail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -303,7 +307,7 @@ func TestInitOrchestratorFail(t *testing.T) {
 	})
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mbi.On("GetAndConvertDeprecatedContractConfig", mock.Anything).Return(nil, "", fmt.Errorf("pop"))
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
@@ -339,7 +343,7 @@ func TestInitVersion1(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -389,7 +393,7 @@ func TestInitFFSystemWithTerminatedV1Contract(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -457,7 +461,7 @@ func TestLegacyNamespaceConflictingPlugins(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -524,7 +528,7 @@ func TestLegacyNamespaceConflictingPluginsTooManyPlugins(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -591,7 +595,7 @@ func TestLegacyNamespaceMatchingPlugins(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -633,7 +637,7 @@ func TestInitVersion1Fail(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1168,7 +1172,7 @@ func TestInitBadNamespace(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1320,7 +1324,7 @@ func TestLoadNamespacesNonMultipartyNoDatabase(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1351,7 +1355,7 @@ func TestLoadNamespacesMultipartyUnknownPlugin(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1382,7 +1386,7 @@ func TestLoadNamespacesMultipartyMultipleBlockchains(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1413,7 +1417,7 @@ func TestLoadNamespacesMultipartyMultipleDX(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1444,7 +1448,7 @@ func TestLoadNamespacesMultipartyMultipleSS(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1475,7 +1479,7 @@ func TestLoadNamespacesMultipartyMultipleDB(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1578,7 +1582,7 @@ func TestLoadNamespacesNonMultipartyMultipleDB(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1607,7 +1611,7 @@ func TestLoadNamespacesNonMultipartyMultipleBlockchains(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1638,7 +1642,7 @@ func TestLoadNamespacesMultipartyMissingPlugins(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
@@ -1667,7 +1671,7 @@ func TestLoadNamespacesNonMultipartyUnknownPlugin(t *testing.T) {
 
 	nm.mdi.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mdi.On("SetHandler", database.GlobalHandler, mock.Anything).Return()
-	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi).Return(nil)
+	nm.mbi.On("Init", mock.Anything, mock.Anything, mock.Anything, nm.mmi, mock.Anything).Return(nil)
 	nm.mdx.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil)
 	nm.mps.On("Init", mock.Anything, mock.Anything).Return(nil)
 	nm.mti.On("Init", mock.Anything, mock.Anything, "erc721", nil).Return(nil)
diff --git a/internal/operations/manager_test.go b/internal/operations/manager_test.go
index af050f0d4..a26bcf946 100644
--- a/internal/operations/manager_test.go
+++ b/internal/operations/manager_test.go
@@ -23,13 +23,14 @@ import (
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -66,7 +67,11 @@ func newTestOperations(t *testing.T) (*operationsManager, func()) {
 		Concurrency: true,
 	})
 	mdm := &datamocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything).Maybe()
 	rag.RunFn = func(a mock.Arguments) {
@@ -76,13 +81,20 @@ func newTestOperations(t *testing.T) (*operationsManager, func()) {
 	}
 
 	ctx, cancel := context.WithCancel(context.Background())
-	om, err := NewOperationsManager(ctx, "ns1", mdi, txHelper)
+	ns := "ns1"
+	om, err := NewOperationsManager(ctx, ns, mdi, txHelper, cmi)
 	assert.NoError(t, err)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheOperationsLimit,
+		coreconfig.CacheOperationsTTL,
+		ns,
+	))
 	return om.(*operationsManager), cancel
 }
 
 func TestInitFail(t *testing.T) {
-	_, err := NewOperationsManager(context.Background(), "ns1", nil, nil)
+	_, err := NewOperationsManager(context.Background(), "ns1", nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 }
 
@@ -221,8 +233,7 @@ func TestRetryOperationSuccess(t *testing.T) {
 		Type: op.Type,
 	}
 
-	om.cache = ccache.New(ccache.Configure().MaxSize(100))
-	om.cacheTTL = time.Minute * 10
+	om.cache = cache.NewUmanagedCache(ctx, 100, 10*time.Minute)
 	om.cacheOperation(op)
 
 	mdi := om.database.(*databasemocks.Plugin)
diff --git a/internal/operations/operation_updater_test.go b/internal/operations/operation_updater_test.go
index e8ee74fe6..1d69745db 100644
--- a/internal/operations/operation_updater_test.go
+++ b/internal/operations/operation_updater_test.go
@@ -24,13 +24,14 @@ import (
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/txcommon"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -57,11 +58,14 @@ func newTestOperationUpdaterCommon(t *testing.T, dbCapabilities *database.Capabi
 	mom := &operationsManager{
 		namespace: "ns1",
 		handlers:  make(map[fftypes.FFEnum]OperationHandler),
-		cache:     ccache.New(ccache.Configure().MaxSize(100)),
+		cache:     cache.NewUmanagedCache(context.Background(), 100, 5*time.Minute),
 		database:  mdi,
 	}
 	mdm := &datamocks.Manager{}
-	txHelper := txcommon.NewTransactionHelper("ns1", mdi, mdm)
+	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := txcommon.NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 	return newOperationUpdater(context.Background(), mom, mdi, txHelper)
 }
 
@@ -181,8 +185,7 @@ func TestSubmitUpdateWorkerE2ESuccess(t *testing.T) {
 	opID3 := fftypes.NewUUID()
 	tx1 := &core.Transaction{ID: fftypes.NewUUID()}
 
-	om.cache = ccache.New(ccache.Configure().MaxSize(100))
-	om.cacheTTL = time.Minute * 10
+	om.cache = cache.NewUmanagedCache(context.Background(), 100, 10*time.Minute)
 	om.cacheOperation(
 		&core.Operation{ID: opID1, Namespace: "ns1", Type: core.OpTypeBlockchainInvoke, Transaction: tx1.ID},
 	)
diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go
index e683ee95b..563b52638 100644
--- a/internal/orchestrator/orchestrator_test.go
+++ b/internal/orchestrator/orchestrator_test.go
@@ -20,15 +20,18 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/mocks/authmocks"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/identity"
 	"github.com/hyperledger/firefly/mocks/assetmocks"
 	"github.com/hyperledger/firefly/mocks/batchmocks"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
 	"github.com/hyperledger/firefly/mocks/broadcastmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/contractmocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/dataexchangemocks"
@@ -73,6 +76,7 @@ type testOrchestrator struct {
 	mti *tokenmocks.Plugin
 	mcm *contractmocks.Manager
 	mmi *metricsmocks.Manager
+	cmi *cachemocks.Manager
 	mom *operationmocks.Manager
 	mth *txcommonmocks.Helper
 	msd *shareddownloadmocks.Manager
@@ -99,6 +103,7 @@ func (tor *testOrchestrator) cleanup(t *testing.T) {
 	tor.mti.AssertExpectations(t)
 	tor.mcm.AssertExpectations(t)
 	tor.mmi.AssertExpectations(t)
+	tor.cmi.AssertExpectations(t)
 	tor.mom.AssertExpectations(t)
 	tor.mth.AssertExpectations(t)
 	tor.msd.AssertExpectations(t)
@@ -132,6 +137,7 @@ func newTestOrchestrator() *testOrchestrator {
 		mti: &tokenmocks.Plugin{},
 		mcm: &contractmocks.Manager{},
 		mmi: &metricsmocks.Manager{},
+		cmi: &cachemocks.Manager{},
 		mom: &operationmocks.Manager{},
 		mth: &txcommonmocks.Helper{},
 		msd: &shareddownloadmocks.Manager{},
@@ -151,6 +157,7 @@ func newTestOrchestrator() *testOrchestrator {
 	tor.orchestrator.assets = tor.mam
 	tor.orchestrator.contracts = tor.mcm
 	tor.orchestrator.metrics = tor.mmi
+	tor.orchestrator.cacheManager = tor.cmi
 	tor.orchestrator.operations = tor.mom
 	tor.orchestrator.sharedDownload = tor.msd
 	tor.orchestrator.txHelper = tor.mth
@@ -187,6 +194,7 @@ func newTestOrchestrator() *testOrchestrator {
 	tor.mmi.On("Name").Return("mock-mm").Maybe()
 	tor.mmp.On("Name").Return("mock-mp").Maybe()
 	tor.mds.On("Init", mock.Anything).Maybe()
+	tor.cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(tor.ctx, 100, 5*time.Minute), nil).Maybe()
 	return tor
 }
 
@@ -196,6 +204,7 @@ func TestNewOrchestrator(t *testing.T) {
 		Config{},
 		&Plugins{},
 		&metricsmocks.Manager{},
+		&cachemocks.Manager{},
 	)
 	assert.NotNil(t, or)
 }
diff --git a/internal/privatemessaging/privatemessaging_test.go b/internal/privatemessaging/privatemessaging_test.go
index 623acd697..dc216d143 100644
--- a/internal/privatemessaging/privatemessaging_test.go
+++ b/internal/privatemessaging/privatemessaging_test.go
@@ -20,13 +20,16 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
 	"github.com/hyperledger/firefly/internal/batch"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/mocks/batchmocks"
 	"github.com/hyperledger/firefly/mocks/blockchainmocks"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/dataexchangemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
@@ -42,9 +45,10 @@ import (
 
 func newTestPrivateMessagingCommon(t *testing.T, metricsEnabled bool) (*privateMessaging, func()) {
 	coreconfig.Reset()
-	config.Set(coreconfig.GroupCacheTTL, "1m")
-	config.Set(coreconfig.GroupCacheLimit, 10)
+	config.Set(coreconfig.CacheGroupLimit, "1m")
+	config.Set(coreconfig.CacheGroupTTL, 10)
 
+	ctx, cancel := context.WithCancel(context.Background())
 	mdi := &databasemocks.Plugin{}
 	mim := &identitymanagermocks.Manager{}
 	mdx := &dataexchangemocks.Plugin{}
@@ -55,6 +59,8 @@ func newTestPrivateMessagingCommon(t *testing.T, metricsEnabled bool) (*privateM
 	mmp := &multipartymocks.Manager{}
 	mmi := &metricsmocks.Manager{}
 	mom := &operationmocks.Manager{}
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
 	mockRunAsGroupPassthrough(mdi)
 
 	mba.On("RegisterDispatcher",
@@ -75,11 +81,15 @@ func newTestPrivateMessagingCommon(t *testing.T, metricsEnabled bool) (*privateM
 	mmi.On("IsMetricsEnabled").Return(metricsEnabled)
 	mom.On("RegisterHandler", mock.Anything, mock.Anything, mock.Anything)
 
-	ctx, cancel := context.WithCancel(context.Background())
 	ns := &core.Namespace{Name: "ns1", NetworkName: "ns1"}
-	pm, err := NewPrivateMessaging(ctx, ns, mdi, mdx, mbi, mim, mba, mdm, msa, mmp, mmi, mom)
+	pm, err := NewPrivateMessaging(ctx, ns, mdi, mdx, mbi, mim, mba, mdm, msa, mmp, mmi, mom, cmi)
 	assert.NoError(t, err)
-
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheGroupLimit,
+		coreconfig.CacheGroupTTL,
+		ns.Name,
+	))
 	// Default mocks to save boilerplate in the tests
 	mdx.On("Name").Return("utdx").Maybe()
 	mbi.On("Name").Return("utblk").Maybe()
@@ -212,7 +222,7 @@ func TestDispatchBatchWithBlobs(t *testing.T) {
 }
 
 func TestNewPrivateMessagingMissingDeps(t *testing.T) {
-	_, err := NewPrivateMessaging(context.Background(), &core.Namespace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
+	_, err := NewPrivateMessaging(context.Background(), &core.Namespace{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
 	assert.Regexp(t, "FF10128", err)
 }
 
diff --git a/internal/txcommon/txcommon_test.go b/internal/txcommon/txcommon_test.go
index c60a5f7ef..c6374a245 100644
--- a/internal/txcommon/txcommon_test.go
+++ b/internal/txcommon/txcommon_test.go
@@ -20,36 +20,30 @@ import (
 	"context"
 	"fmt"
 	"testing"
+	"time"
 
 	"github.com/hyperledger/firefly-common/pkg/config"
 	"github.com/hyperledger/firefly-common/pkg/fftypes"
+	"github.com/hyperledger/firefly/internal/cache"
 	"github.com/hyperledger/firefly/internal/coreconfig"
 	"github.com/hyperledger/firefly/internal/data"
+	"github.com/hyperledger/firefly/mocks/cachemocks"
 	"github.com/hyperledger/firefly/mocks/databasemocks"
 	"github.com/hyperledger/firefly/mocks/datamocks"
 	"github.com/hyperledger/firefly/pkg/core"
 	"github.com/hyperledger/firefly/pkg/database"
-	"github.com/karlseguin/ccache"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
 
-func NewTestTransactionHelper(di database.Plugin, dm data.Manager) (Helper, *ccache.Cache, *ccache.Cache) {
+func NewTestTransactionHelper(di database.Plugin, dm data.Manager) (Helper, cache.CInterface, cache.CInterface) {
 	t := &transactionHelper{
 		namespace: "ns1",
 		database:  di,
 		data:      dm,
 	}
-	t.transactionCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetByteSize(coreconfig.TransactionCacheSize)),
-	)
-	t.blockchainEventCache = ccache.New(
-		// We use a LRU cache with a size-aware max
-		ccache.Configure().
-			MaxSize(config.GetByteSize(coreconfig.BlockchainEventCacheLimit)),
-	)
+	t.transactionCache = cache.NewUmanagedCache(context.Background(), config.GetByteSize(coreconfig.CacheTransactionSize), config.GetDuration(coreconfig.CacheTransactionTTL))
+	t.blockchainEventCache = cache.NewUmanagedCache(context.Background(), config.GetByteSize(coreconfig.CacheBlockchainEventLimit), config.GetDuration(coreconfig.CacheBlockchainEventTTL))
 	return t, t.transactionCache, t.blockchainEventCache
 }
 
@@ -57,8 +51,10 @@ func TestSubmitNewTransactionOK(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, err := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	var txidInserted *fftypes.UUID
 	mdi.On("InsertTransaction", ctx, mock.MatchedBy(func(transaction *core.Transaction) bool {
@@ -85,8 +81,10 @@ func TestSubmitNewTransactionFail(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	mdi.On("InsertTransaction", ctx, mock.Anything).Return(fmt.Errorf("pop"))
 
@@ -101,8 +99,10 @@ func TestSubmitNewTransactionEventFail(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	mdi.On("InsertTransaction", ctx, mock.Anything).Return(nil)
 	mdi.On("InsertEvent", ctx, mock.Anything).Return(fmt.Errorf("pop"))
@@ -118,8 +118,10 @@ func TestPersistTransactionNew(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(nil, nil)
@@ -143,8 +145,10 @@ func TestPersistTransactionNewInserTFail(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(nil, nil)
@@ -162,8 +166,10 @@ func TestPersistTransactionExistingAddBlockchainID(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(&core.Transaction{
@@ -187,8 +193,10 @@ func TestPersistTransactionExistingUpdateFail(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(&core.Transaction{
@@ -212,8 +220,23 @@ func TestPersistTransactionExistingNoChange(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	ns := "ns1"
+	txHelper, _ := NewTransactionHelper(ctx, ns, mdi, mdm, cmi)
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheTransactionSize,
+		coreconfig.CacheTransactionTTL,
+		ns,
+	))
+	cmi.AssertCalled(t, "GetCache", cache.NewCacheConfig(
+		ctx,
+		coreconfig.CacheBlockchainEventLimit,
+		coreconfig.CacheBlockchainEventTTL,
+		ns,
+	))
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(&core.Transaction{
@@ -236,8 +259,10 @@ func TestPersistTransactionExistingNoBlockchainID(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(&core.Transaction{
@@ -260,8 +285,10 @@ func TestPersistTransactionExistingLookupFail(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(nil, fmt.Errorf("pop"))
@@ -278,8 +305,10 @@ func TestPersistTransactionExistingMismatchType(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	txid := fftypes.NewUUID()
 	mdi.On("GetTransactionByID", ctx, "ns1", txid).Return(&core.Transaction{
@@ -302,8 +331,10 @@ func TestAddBlockchainTX(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	tx := &core.Transaction{
 		ID:            fftypes.NewUUID(),
@@ -332,8 +363,10 @@ func TestAddBlockchainTXUpdateFail(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	tx := &core.Transaction{
 		ID:            fftypes.NewUUID(),
@@ -355,8 +388,10 @@ func TestAddBlockchainTXUnchanged(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	tx := &core.Transaction{
 		ID:            fftypes.NewUUID(),
@@ -418,11 +453,13 @@ func TestGetTransactionByIDCachedFail(t *testing.T) {
 }
 
 func TestGetBlockchainEventByIDCached(t *testing.T) {
-
+	config.Set(coreconfig.CacheEnabled, true)
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	evID := fftypes.NewUUID()
 	mdi.On("GetBlockchainEventByID", ctx, "ns1", evID).Return(&core.BlockchainEvent{
@@ -446,8 +483,10 @@ func TestGetBlockchainEventByIDNil(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	evID := fftypes.NewUUID()
 	mdi.On("GetBlockchainEventByID", ctx, "ns1", evID).Return(nil, nil)
@@ -464,8 +503,10 @@ func TestGetBlockchainEventByIDErr(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	evID := fftypes.NewUUID()
 	mdi.On("GetBlockchainEventByID", ctx, "ns1", evID).Return(nil, fmt.Errorf("pop"))
@@ -481,8 +522,10 @@ func TestInsertGetBlockchainEventCached(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	evID := fftypes.NewUUID()
 	chainEvent := &core.BlockchainEvent{
@@ -506,8 +549,10 @@ func TestInsertBlockchainEventDuplicate(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	evID := fftypes.NewUUID()
 	chainEvent := &core.BlockchainEvent{
@@ -529,8 +574,10 @@ func TestInsertBlockchainEventErr(t *testing.T) {
 
 	mdi := &databasemocks.Plugin{}
 	mdm := &datamocks.Manager{}
-	txHelper := NewTransactionHelper("ns1", mdi, mdm)
 	ctx := context.Background()
+	cmi := &cachemocks.Manager{}
+	cmi.On("GetCache", mock.Anything).Return(cache.NewUmanagedCache(ctx, 100, 5*time.Minute), nil)
+	txHelper, _ := NewTransactionHelper(ctx, "ns1", mdi, mdm, cmi)
 
 	evID := fftypes.NewUUID()
 	chainEvent := &core.BlockchainEvent{