Rendering and transformation caching
Caching is an opt-in core capability. The core artifact owns the cache keys, bounded in-process storage, single-flight loading, and the rendering and transformation facades. The web module only resolves HTTP parameters, binds configuration, creates the core facades, and serves their results.
This distinction matters for consuming applications:
| Integration | Cache behavior |
|---|---|
Call representable-contract-state-web over HTTP |
Requests use that web-service instance's cache. |
Embed net.finmath:representable-contract-state |
Construct and share the core caching facades in the consuming process. |
An embedded application should not add representable-contract-state-web as a
library dependency merely to obtain caching. The web artifact is an executable
Spring Boot application.
Core dependency and setup
Depend on the core artifact:
<dependency>
<groupId>net.finmath</groupId>
<artifactId>representable-contract-state</artifactId>
<version>${representable-contract-state.version}</version>
</dependency>
Create one shared cached facade for each renderer configuration:
import net.finmath.smartcontracts.representablestate.cache.CachedRenderingService;
import net.finmath.smartcontracts.representablestate.cache.CachedXsltTransformationService;
import net.finmath.smartcontracts.representablestate.cache.EvmSnapshot;
import net.finmath.smartcontracts.representablestate.cache.EvmSnapshotVerifier;
import net.finmath.smartcontracts.representablestate.cache.RendererCacheOptions;
import net.finmath.smartcontracts.representablestate.postprocessing.ExternalXsltResourceLoader;
import net.finmath.smartcontracts.representablestate.xml.EvmXmlRenderer;
import net.finmath.smartcontracts.representablestate.xml.XsltPostProcessor;
import net.finmath.smartcontracts.representablestate.xml.XsltPostProcessor.TransformationResult;
import java.time.Duration;
RendererCacheOptions options = RendererCacheOptions.defaults();
EvmXmlRenderer rawRenderer = new EvmXmlRenderer(rpcUrl);
CachedRenderingService renderings = new CachedRenderingService(
rawRenderer,
options
);
EvmSnapshot snapshot = new EvmSnapshot(chainId, blockNumber, blockHash);
String stateXml = renderings.render(snapshot, contractAddress);
String partXml = renderings.renderPart(snapshot, contractAddress, partId);
String template = renderings.renderTemplate(
snapshot,
contractAddress,
true
);
The raw EvmXmlRenderer remains available when caching is not wanted. Creating
a new cached facade for each request defeats reuse; expose a singleton or
equivalent application-scoped instance instead.
To cache a complete render-plus-XSLT operation, use a second facade:
XsltPostProcessor postProcessor = new XsltPostProcessor();
ExternalXsltResourceLoader resourceLoader = new ExternalXsltResourceLoader(
web3j,
ExternalXsltResourceLoader.Options.defaults()
);
CachedXsltTransformationService transformations =
new CachedXsltTransformationService(
renderings,
postProcessor,
resourceLoader,
options
);
TransformationResult document = transformations.transform(
snapshot,
contractAddress,
xsltSpecification
);
The rendering and transformation caches are separate. The configured maximum weight applies to each cache, so two caches configured for 64 MiB may retain approximately 128 MiB plus implementation overhead.
Snapshot and key semantics
Resolve latest exactly once before calling a cached facade. EvmSnapshot
requires:
- the RPC provider's chain ID;
- a non-negative block number; and
- that block's 32-byte hash.
The block hash prevents a document rendered before a chain reorganization from
being reused for a different block at the same height. Address matching is
case-insensitive. Rendering keys also distinguish complete state, partId, and
template XInclude resolution mode. Transformation keys add the normalized XSLT
specification.
EvmXmlRenderer currently executes eth_call by block number. Applications
that require strong canonicality guarantees should therefore verify the block
hash after a newly computed rendering, as a reorganization can occur between
snapshot resolution and the RPC calls. Supply that check directly to the core
facades:
EvmSnapshotVerifier snapshotVerifier = snapshot -> {
// Re-read snapshot.blockNumber() and reject a different block hash.
};
CachedRenderingService verifiedRenderings = new CachedRenderingService(
rawRenderer,
options,
snapshotVerifier
);
The verifier runs after a successful computation and before its result is
returned or cached. CachedXsltTransformationService has an equivalent
five-argument constructor and verifies after the complete transformation,
including external stylesheet retrieval. The web application supplies its
Web3j-backed verifier automatically and returns 409 Conflict when the block
changes during generation.
A cache hit is identified by the full snapshot hash and does not run the post-computation verifier again. Web requests still resolve the current block hash before every lookup. Embedded callers using the two-argument constructor must retain their own canonicality checks. Post-verification narrows the reorg window but does not replace the caller's finality policy or hash-pinned EIP-1898 RPC support.
Expiry and external stylesheets
When caching is enabled, successful values are cached. Exceptions and null
results are not retained, and concurrent calls for the same key share one
computation. Entries are local to the process and disappear on restart or
upgrade. clear() explicitly removes the entries owned by a facade. Disabled
facades execute and verify every call directly without retaining or coalescing
work.
The default options are:
enabled: true
maximum weight per cache: 64 MiB
expire after write: 10 minutes
Use explicit options when the host application needs different bounds:
RendererCacheOptions options = new RendererCacheOptions(
16L * 1024L * 1024L,
Duration.ofMinutes(5)
);
Inline and block-pinned web3:// stylesheets are tied to the source snapshot.
An HTTP(S) URL can return different content without changing the transformation
key. Its freshness is therefore bounded only by cache expiry. Use a suitably
short expiry, immutable content-addressed URLs, or disable that integration when
this is not acceptable.
Web-service configuration
representable-contract-state-web creates the core facades automatically. Its
defaults can be overridden with Spring configuration:
renderer.cache.enabled=true
renderer.cache.maximum-weight=64MB
renderer.cache.expire-after-write=10m
Set renderer.cache.enabled=false to bypass both caches without changing the
HTTP endpoints or their snapshot verification. Every request is then rendered,
transformed, and verified directly. This provides an operational rollback for
mutable resources or cache-related incidents.
This is server-side computation caching. The transform and text-resource
endpoints continue to send Cache-Control: no-store; browser and proxy caching
is a separate policy.
Smart Bond handover
Smart Bond currently embeds the core renderer rather than calling the web service. Its integration should therefore:
-
Create one shared
CachedRenderingServicefor each RPC/renderer configuration instead of constructingEvmXmlRendererin each service. -
Map its existing snapshot directly to core identity:
EvmSnapshot cacheSnapshot = new EvmSnapshot( snapshot.chainId(), snapshot.blockNumber(), snapshot.blockHash() ); -
Adapt Smart Bond's existing post-render canonicality check to
EvmSnapshotVerifierand pass it to the three-argument constructor. This keeps a reorganization failure from admitting the rejected XML to the cache; retain any additional pre-render and finality checks Smart Bond already performs. -
Put Smart Bond-specific validation, product-term extraction, and lifecycle rules above the generic cached renderer.
-
Use distinct facades for differently configured data-retrieval and event-listening RPC connections.
-
Test cache misses across chain ID, block number, block hash, contract, kind,
partId, and transformation input.
Caching Smart Bond's richer final RenderedState can be added at its application
boundary if profiling shows value. That domain cache is separate from the
generic XML/XSLT cache supplied here.
The optional on-chain stateVersion() extension is a different, cross-block
optimization. It is not required to reuse an exact fixed-snapshot rendering.
