Skip to content

Conversation

@PHOL-DA
Copy link
Contributor

@PHOL-DA PHOL-DA commented Dec 8, 2025

Motivation:
This PR introduces background runners and caching to the wallet gateway that allows us to (in memory) continuously maintain the status of ledger endpoints before authentication. This will become a lot handier further down the road when we split idp and network completely.

considerations:
as @mjuchli-da have mentioned in comments for persistence and scaling reason this should be in cache / memory only.
for now this is a proof of concept that delivers also a short term value (in the form of the status icon), that creates a basis for further similar works.

With nothing running locally:
Screencast from 2025-12-11 08-48-10.webm

With start:canton & start:localnet
Screencast from 2025-12-11 08-51-15.webm

@mjuchli-da
Copy link
Contributor

@PHOL-DA I had a think about persistence/scalability. I agree, but this should not go into our domain models, but instead in a separate 'cache'.

Signed-off-by: phillip olesen <[email protected]>
Signed-off-by: phillip olesen <[email protected]>
Signed-off-by: phillip olesen <[email protected]>
@PHOL-DA PHOL-DA marked this pull request as ready for review December 11, 2025 16:02
@PHOL-DA PHOL-DA requested review from a team as code owners December 11, 2025 16:02
Comment on lines +556 to +577
"VerifiedNetwork": {
"title": "VerifiedNetwork",
"description": "Structure representing the Networks with connectivity verification",
"allOf": [
{
"$ref": "#/components/schemas/Network"
},
{
"title": "VerifiedNetworkProperties",
"description": "holder for boolean verified property",
"type": "object",
"properties": {
"verified": {
"title": "verified",
"description": "true if we could succesfully call the ledger version endpoint",
"type": "boolean"
}
},
"required": ["verified"]
}
]
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this need a separate schema, rather than adding an optional "verified" to Network itself?

Comment on lines +146 to +149
cron.schedule('* * * * *', () => {
startLedgerConnectivityTester(networkCacheStore, logger)
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this runs every minute? is that too frequent?

Comment on lines +20 to +24
export class NetworkCacheStore {
constructor(
private readonly store: Store,
private readonly logger: Logger
) {}
Copy link
Contributor

@alexmatson-da alexmatson-da Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need all this? I'm wondering why not decouple the cache from the underlying store?

The network cache can just store { networkId: string, verified: boolean }, and only needs a getter and setter.

In the controller you retrieve the network from the store directly, and merge based on ID to include the verified status

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we wanted to go all out, we might even feel inclined to design a Cache interface analogous to Store. Then down the road we could have an in-memory cache implementation, and then something like Redis implementation for a HA/scalable production setup

Copy link
Contributor

@mjuchli-da mjuchli-da left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the comments, I believe it would be better to hold only the cached properties (status in this case) in the LRU. Then use this cache here needed: which is only when listing networks. This way, we have much better decoupling between the domain model (Network) and a (single) cached property (Status) that is not part of the domain model.

networks: z.array(networkSchema),
})

export const verifiedNetworkSchema = networkSchema.extend({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't think this has anything to do with the wallet-store. Can we define it directly in the wallet-gateway?

"type": "string",
"description": "The unique identifier of the command associated with the transaction."
},
"VerifiedNetwork": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term Verified is a bit ambiguous since we use it to indicate a "verified" wallet in the SDK.
What this really indicates is the "availability", no?

Copy link
Contributor

@alexmatson-da alexmatson-da Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hahaha, I just had the same comment. Didn't think about availability though, I like that the most!

EDIT: or plain available

export const DEFAULT_MAX_CACHE_SIZE = 100
export const DEFAULT_ENTRY_EXPIRATION_TIME = 10 * 60 * 1000

const NetworkCache = new LRUCache<string, VerifiedNetwork>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply cache the status for each network NetworkStatusCache = LRU<NetworkId, NetworkStatus>?

With

NetworkStatus = { connected: boolean, reason?: string, lastChecked: Date }

This way, when listing existing networks, nothing needs to change, except for an additional cache lookup to get the status.

entryExpirationTimeInMS: DEFAULT_ENTRY_EXPIRATION_TIME,
})

export class NetworkCacheStore {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the benefit of this. I'd separate concerns and only cache the NetworkStatus (or an alternative name: NetworkAvailability).


// TODO: Add an explicit updateNetwork method to the User API spec and controller
const existingNetworks = await store.listNetworks()
const existingNetworks = await cache.listNetworks()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not have to touch the cache here. See my suggestion for separating things above.

},
removeNetwork: async (params: RemoveNetworkParams) => {
await store.removeNetwork(params.networkName)
await cache.removeNetwork(params.networkName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

listNetworks: async () =>
Promise.resolve({ networks: await store.listNetworks() }),
listNetworks: async () => {
return { networks: await cache.listNetworks() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can list the networks, then in addition, get the status via the cache.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants