Proposal: Denex Localnet#318
Conversation
| # Username == password convention (so 'bob-trader' logs in with | ||
| # password 'bob-trader'). The Keycloak realm for this validator is | ||
| # 'Bob' (title-cased from validator name). | ||
| users: |
There was a problem hiding this comment.
From an implementation standpoint, have you seen the declarative API for users/parties? (https://docs.digitalasset.com/operate/3.4/howtos/configure/general/declarative_conf.html)
It would be nice if, as part of this, changes were implemented in lower levels of the stack so that components understand declarative APIs more natively.
There was a problem hiding this comment.
I hadn't seen that before. It looks interesting.
denex-localnet also provisions the users in keycloak from this config, and it's network-wide rather than localized to a single participant, so a 1-1 correspondence with the dynamic participant config feature probably isn't possible. It's worth thinking about using it in the implementation, though.
There was a problem hiding this comment.
Note that mid term we wanted to decouple the user database from the validator instance in order to use it across the different node types and support direct integration with existing user management systems rather than to hand-roll our own. This is more of a heads-up, though.
|
Based on our experience with Canton dev environments, a “Hardhat-like” tool could be very useful for Canton, and could significantly improve the developer experience for both new and experienced Canton builders. The proposal also directly addresses one of the clearest pieces of feedback from the DevEx survey, i.e. the need for better local development frameworks and simpler environment setup. In addition, features described in the proposal, such as the single-file declarative configuration, programmatic API for lifecycle, or the runtime state inspection/discovery could potentially offer capabilities beyond those of existing frameworks. Personally, I am supportive of this proposal. |
| # Validators | ||
| # --------------------------------------------------------------------------- | ||
| # The Super Validator (SV) is IMPLICIT — always exactly one, created | ||
| # automatically. You only configure the *regular* validators here. |
There was a problem hiding this comment.
What exactly will constitute a validator here? Participant node & validator app?
There was a problem hiding this comment.
Yes - participant node, validator, wallet ui, the full splice stack.
| # transformation, because Splice enforces a 30-char limit on the derived | ||
| # participant node name (`{participantName}-validator_backend`, where | ||
| # `participantName = name.replace(/-/g, '_')`). | ||
| # |
There was a problem hiding this comment.
Note that multi-synchronizer support is coming (and is already usable for alpha development in 3.5). Does the grant here include running multiple synchronizers as well?
Also please note that recently PQS was approved as OSS, which means that it is likely going to become a standard piece of infrastructure for app provider backends: #67
There was a problem hiding this comment.
Not at this point. I don't have a good enough sense of the use cases and shape of the requirements there to have a point of view on what/how to build.
There was a problem hiding this comment.
Supporting PQS is a requirement. A large percentage of dApps use PQS.
There was a problem hiding this comment.
In the short term, supporting a Wallet Gateway will also be needed. See Proposal: Wallet Gateway Reference Implementation #109.
I would expect the pattern of running sidecar, Docker images to a validator will be a recurring pattern.
There was a problem hiding this comment.
PQS, Wallet Gateway, and any other tools that a user would like to use with their app are supported the same way that apps themselves are supported: denex-localnet provides infrastructure-level configuration and primitives, and a rich API/CLI surface with which users can configure and run those higher-level components in whatever way they want. Whether to run PQS, Wallet Gateway, or any of a slew of other current or future Canton tools and applications, and how they are configured, is a matter of user choice.
I do think it would be a good idea to include some rich recipes in the documentation for how to configure and run apps and app tools on a denex-localnet.
| Idempotency: re-calling with the same args is safe — each side (ledger, Keycloak, wallet) converges. Partial failures are recoverable; just call again. | ||
|
|
||
| --- | ||
|
|
There was a problem hiding this comment.
Wouldn't it make more sense to base the CLI here on a normal Typescript Client that can also be used in applications and mostly provide a factory to automatically discover and connect to the node? This would allow devs to only learn one syntax API and avoid creating a small subset that only works in a few common cases.
There was a problem hiding this comment.
Not sure I quite follow what you're saying here.
denex-localnet provides two interaction surfaces, which have essential feature parity between them:
- A TypeScript API (documented in this section) with which a user can create, configure, lifecycle, query status and metadata, and to an extent modify localnets from that user's own code
- A CLI tool that can be used to do the same things
With those tools you can both create a new localnet or interact with a running localnet - and there could be more than one running. This section is showing how the TypeScript API can get a handle on a running localnet. The handle returned from this is the same type of object you get when creating a new localnet from a config.
There was a problem hiding this comment.
Yes. I do see that one part of the Typescript API is interacting with the node lifecycle. However, the second part is ultimately interacting with the node state via Ledger API.
In your other proposal, you are also proposing a new Typescript Ledger interface: https://github.com/canton-foundation/canton-dev-fund/pull/238/changes#diff-4f2822a1b669831f32b246ebe4d0c799c9407081c67470b41c55ec781b794b5cR17-R18
So my question is how much overlap there is and how these two will be used in conjunction.
There was a problem hiding this comment.
There's some limited overlap, in particular for the mutations that localnet can do. Right now the beta version does not use the interface from our SDK for the things it needs to do via the ledger, mostly for some logistical/release coordination reasons. I would guess that once there's a finalized release of our SDK and npm distribution it's likely that'll get integrated into localnet, but don't know that for sure just yet. This is more in the realm of implementation detail.
Here is the vision for usage. denex-localnet is meant to stand up the Canton infrastructure-layer pieces that the application layer needs, providing a flexible way for users, applications or app test/run harnesses to configure and run that infrastructure, and supplying the information those applications would need to configure and bootstrap themselves (parties, users, network endpoints) - the sorts of things that often get plugged into say an app .env file. Beyond this initialization, consumers should generally handle all subsequent ledger interactions and state mutations on their own - perhaps by using the tools in our separate SDK, but that kind of decision is app- and usecase- specific and outside of denex-localnet's bounds.
| subject to change. These are intended to help illustrate the design | ||
| thinking and capabilities of `denex-localnet`. | ||
|
|
||
| ### Configuration Files |
There was a problem hiding this comment.
How are you going to support passing custom configuration arguments to the individual nodes?
There was a problem hiding this comment.
What kind of configuration do you have in mind?
There was a problem hiding this comment.
New features and capabilities are often introduced via feature flags so people can try them out (either via canton.parameters.xyz or canton..node.parameters). Also, the resource consumption of the nodes can be tweaked there etc. Or the protocol version to be used etc: https://docs.canton.network/sdks-tools/development-tools/sandbox#dev-protocol
There was a problem hiding this comment.
Those are interesting ideas and I'll have to give that some thought.
| experience of navigating 80-120+ files across Splice LocalNet and CN Quickstart | ||
| into a single, readable configuration. Developers can bring up a customized | ||
| Canton network in minutes rather than hours, and integrate it programmatically | ||
| into their test suites and CI/CD pipelines. |
There was a problem hiding this comment.
How are you going to solve this, actually, technically. Do you plan to map this to docker? Do you plan to integrate this tightly with the splice releases?
How do you minimize resource usage? This has been an issue with localnet, so it's worth bringing it up here so you don't run into the same problem. Right now, a full localnet creates too many resource hungry JVM processes.
canton.jar itself does support running many nodes in a single jvm process (and in theory wouldn't be hard to support dynamically adding new nodes to a running process), but the validator app is a separate jar.
There was a problem hiding this comment.
Yes, the working beta version of denex-localnet uses the Docker socket directly to create and manage the containers, using configs that it generates from the single denex-localnet config.
I think there is an effective minimum resources needed to run a localnet. We're running a single canton container, and single validator app container, each hosting all of the nodes. All told, between canton, all the splice apps, postgres, nginx, keycloak, right now a denex-localnet with 2 validators configured stands up 10 docker containers consuming around 5 GB of memory.
There was a problem hiding this comment.
I have some additional questions:
- Does that configuration include the synchronizer containers (e..g, mediator, sequencer)?
- How do you plan on keeping your configurations consistent with DevNet/TestNet/MainNet which may each have different config values?
- Regarding memory, I think you need to have a requirement that the memory footprint is equal to or less than the current LocalNet for the same app. Can you add this?
- If a dApp needs to include other services (e.g., LAPI client, Nginx running a UI), how easily will this framework incorporate this? For example, the dApp user needs to run a LAPI client, how will it know the port number for the LAPI for the target validator? How will the Docker vpn accommodate these non-LocalNet images?
There was a problem hiding this comment.
- Yes, it includes the synchronizer and the full Splice single-node Super Validator setup.
- I'm not sure which configurations you mean. Talking about, eg, the Amulet config and associated Splice configs that are part of network governance, or some other config?
- So I think this might be somewhat difficult, as splice and cn-quickstart localnet have a fixed number of participants, whereas
denex-localnethas a configurable number of participants. Additionally, some of the other configuration options requested by @daravep in a separate comment, if really supported properly, would probably need to contemplate different low-level configuration options per-participant, which could in turn requiredenex-localnetto run those participants in a less memory-efficient way (separate JVM/containers per participant, potentially, rather than single multi-hosting). denex-localnetis designed to support flexible integration with apps. It binds host ports in a configurable range for the ledger API, admin API, and the various Splice UIs/apps. The TypeScript API and the CLI are designed to support both manual and programmatic querying for what those endpoints are against a running localnet, and integrating flows to generate app .env files that apps can hook into. It allows developers to choose whatever mechanism they want to run their own app level components locally, and multiple options for how they want to bootstrap and integrate them with the underlying Canton layer.
| Idempotency: re-calling with the same args is safe — each side (ledger, Keycloak, wallet) converges. Partial failures are recoverable; just call again. | ||
|
|
||
| --- | ||
|
|
There was a problem hiding this comment.
Yes. I do see that one part of the Typescript API is interacting with the node lifecycle. However, the second part is ultimately interacting with the node state via Ledger API.
In your other proposal, you are also proposing a new Typescript Ledger interface: https://github.com/canton-foundation/canton-dev-fund/pull/238/changes#diff-4f2822a1b669831f32b246ebe4d0c799c9407081c67470b41c55ec781b794b5cR17-R18
So my question is how much overlap there is and how these two will be used in conjunction.
|
|
||
| **Phase 2 Funding Requested: 1,250,000 CC** | ||
|
|
||
| ## Phase 3: Hardening |
There was a problem hiding this comment.
I would suggest that you add another work item to continuously align localnet with the splice release cadence. The artifacts you are coding against are not static (e.g. multi-sync ...). I would recommend putting an initial but low estimate in there, but refer to a possible extension grant if the effort exceeds the budget.
There was a problem hiding this comment.
So I had intended here to convey that idea with the "regular updates to incorporate Splice releases". Are you suggesting to go further and specify a release cadence that explicitly matches Splice's (currently weekly)?
There was a problem hiding this comment.
Apologies, looks like I've overlooked this. In that sense it seems all good.
| primaryParty: ops | ||
| rights: [ParticipantAdmin] | ||
| # --------------------------------------------------------------------------- | ||
| # DAR packages to upload after startup |
There was a problem hiding this comment.
A feature that we've found extremely helpful is to automatically forward log files to a single directory so that a tool like "lnav" can be used. Can you include this?
There was a problem hiding this comment.
I'll have to give that some thought, on the right way to handle this.
| } | ||
|
|
||
| // One snapshot of everything | ||
| const snapshot = await net.getSnapshot(); |
There was a problem hiding this comment.
Please explain what getSnapthot does and what its boundaries are (e.g., include PQS or not)?
There was a problem hiding this comment.
This returns an object that shows the full configuration/metadata from the localnet: parties, users, endpoints, participants, DSO, uploaded Dars, etc. It's not talking about database or runtime state. Perhaps not the best named method.
|
|
||
| ```ts | ||
| // Allocate an additional party (createUser does this automatically for referenced hints) | ||
| const party = await net.allocateParty('alice', 'validator-1', 'Alice'); |
There was a problem hiding this comment.
What kind of party is this: internal or external? External parties are the focus because they support wallets.
Can you reuse the TypeScript signatures of the Wallet SDK and dApp SDK so that users don't need to learn several libraries that do the same thing? For example, see https://docs.canton.network/integrations/wallet/guidance#create-an-external-party-wallet.
There was a problem hiding this comment.
These are internal parties.
I will say that I have mixed feelings about including support for Mutations at all in the API. As you point out, that really starts to get into the domain of application logic rather than the infrastructure layer. There is a good chance of dropping this feature entirely, and limiting denex-localnet to only creating users and parties from config as part of the localnet initialization process.
|
|
||
| --- | ||
|
|
||
| ## 4. Comparison with Existing Solutions: Denex Localnet vs. CN Quickstart / Splice LocalNet |
There was a problem hiding this comment.
Yes, you have highlighted gaps. Will you be replacing LocalNet in Splice and CN QuickStart with denex-localnet? If not, why? I think that would make sense. After all, how many types of localnet do we need?
There was a problem hiding this comment.
Good tools are opinionated. While it's not for me to say whether denex-localnet is a good tool, it is an opinionated one. Although I have made an effort to focus the design such that the "blast radius" of its opinions are kept smaller, and as much useful choice is left up to the user, nonetheless people are free to accept them or not. Given that, I was not planning to try to promote this into Splice or QuickStart.
|
@mgaare, your proposal is missing a Special Interest Group (SIG) label. Adding the right SIG label ensures the relevant domain experts can find and review your proposal, Check more about SIGs. Please add one of the following labels to this PR:
Not sure which one fits? Pick the closest match to your proposal's domain. You can add a label from the right sidebar under "Labels". |
Development Fund Proposal Submission
Proposal file:
./proposals/denex-localnet.md
Summary
denex-localnetis a Testcontainers-style SDK and CLI for running Canton Network LocalNets, collapsing what would currently require dozens of scattered config files into a single declarative YAML file. It orchestrates the full Canton stack — Super Validator, regular validators, Splice, Keycloak auth, and all associated web UIs — directly via the Docker API (no Docker Compose), with sensible defaults for ports, party hints, realm naming, and OAuth2 wiring.It lowers the barrier to local development and integration testing: developers can spin up a fully-wired multi-validator network programmatically (via the cross-runtime SDK on Deno/Node/Bun) or interactively (via the Deno CLI), then query state, allocate parties, and provision users through clean high-level APIs. This enables programmatic and flexible integration for app developers, and makes Canton LocalNets viable as ephemeral fixtures in CI pipelines and SDK-driven test suites — a workflow that's currently very challenging with existing tooling.
Checklist
/proposals/Notes for Reviewers
(Add anything the Tech & Ops Committee should pay attention to.)