Skip to content

Latest commit

 

History

History
278 lines (191 loc) · 14.5 KB

File metadata and controls

278 lines (191 loc) · 14.5 KB

Table of Contents

Contributing to the Splice repository

In order to setup your development environment, please see the Development README.

Picking up issues

Splice maintainers may use the following GitHub issue labels to highlight issues suitable for newer contributors:

  • help wanted: tasks of various sizes and difficulty levels suitable for contributors outside of the maintainers team
  • good first issue: easier tasks that are well-suited for onboarding to the code base

Note that not all good first issues are also help wanted; some may require access to infrastructure (CI, test deployments) that is not openly available.

If you are planning to work on an issue please assign yourself to it (if you are able to) or leave a comment, to avoid duplicate work across contributors. If the issue is not new, it is also a good idea to reach out to the core contributors before working on it, to check how relevant it still is, and whether it is something worth working on.

Opening PRs

  • Keep patches small, focused, and clearly motivated. One logical change per PR.
  • Reference the issue that the PR addresses.
  • Keep the text in your PR concise, write only what is useful for the reviewers to read. Specifically, avoid AI-generated text in PR descriptions. See our AI contribution guidelines.

Opening new issues

Please note that GH issues are not meant for Q&A, but rather for tracking future and current work items. Issues that are purely a question may be closed with no further comments. Please also consult our AI contribution guidelines for guidelines on AI-created issues.

Testing

Every contribution must be tested in an automated test! For further details see the Testing README.

Also note that the splice CI enforces that all commits on a pull request contain a valid Signed-off-by: Your Name <your@email.com> line, to confirm adherence to the DCO requirements.

Git Hygiene

Branch Naming

If you are a Splice Contributor and therefore have write permissions to the Splice repo directly, please prefix branch names by your name, followed by a slash and a descriptive name:

<yourname>/<descriptivename>

For example, if Bob is working on issue 4242 to "fix FooTest", he could name his branch:

bob/fix-footest/4242.

Merge Strategy

PRs are merged into main using squash and merge. This collapses all commits in the PR into a single commit on main, keeping the history clean and linear.

Squash-and-Merge Commit Messages

Because the squash commit is the only record of the change on main, its message matters. Follow these conventions:

  • Title (first line): A concise summary of the change, written in imperative mood (e.g., "Add external transaction hash to Scan API", not "Added …" or "Adds …"). GitHub defaults the title to the PR title, so make sure the PR title is descriptive.
  • Body: GitHub auto-populates the body with the list of individual commit messages. Clean this up before merging:
    • Remove noise such as "address review comments", "fixes", "format", "refactor", etc.
    • Keep a brief summary of what the change does and why. A few bullet points are fine.
    • Reference the related GitHub issue, e.g., Fixes #1234 or Part of #1234.
    • Example of a good squash commit message:
      Add external transaction hash to Scan API (#1234)
      
      - Store the external transaction hash from the ledger API in the
        update_history_transactions table.
      - Expose the hash through the v2/updates and v2/updates/{update_id}
        Scan endpoints.
      - Gate hash inclusion behind a configurable threshold record time for BFT-safety.
      
      Signed-off-by: Git-Hygienic-Dev <ghd@example.com>
      
  • CI tags: Post-merge process ignores CI tags (e.g., [ci], [static]), so always remove them.
  • Sign-off: The squash commit must include a valid Signed-off-by line. Ensure it is present in the commit message body.

TODO Comments

TBD

DB Migrations

Refer to the main README on migrations.

Daml Changes

Approval for Daml Changes

Adopting Daml changes on a prod system requires a vote from a majority of SVs. Therefore, before proposing any Daml changes, please reach out to the Splice Maintainers to discuss your proposal and the best way to secure support for your changes first.

Daml Lock Files

To prevent accidental changes to dar files, we commit their current package IDs with the repo, in daml/dars.lock. CI verifies that those package IDs are correct. If you intentionally make changes in daml code, please run sbt damlDarsLockFileUpdate and commit the updated dars.lock file along with your dar changes.

Backwards-compatible Daml changes

We require all Daml changes to be backwards-compatible. See the Upgrading and Extending Daml Applications section of the Canton docs.

In the early days of Daml 3.0 upgrading of variants and enums was not supported, which is why there are variant constructors with names like ExtFoo in our codebase. They used to be a workaround for this lack of upgradeability. You can ignore them, and just add new enum and variant constructors directly.

Care still must be taken to not accidentally change an enum into a variant: enums are data type declarations that only consist of nullary constructors. They are compiled to Daml-LF enums, which is nice as that ensures that the codegens like the Java codegen define these as Jave enums as well. Make sure to only add further nullary constructors to types that only have nullary constructors.

Daml Numerics

To represent Daml Numerics for any user facing APIs (console commands), we use scala.math.BigDecimals. We use Scala BigDecimals instead of Java BigDecimals (that are used in the Daml repo) because integers, floats etc. are automatically converted to Scala BigDecimals by the Scala compiler unlike Java BigDecimals (wallet.tap(10) vs wallet.tap(new java.math.BigDecimal(10))).

To represent Daml Numerics in Protobuf we use strings (there is no Protobuf BigDecimal type). Conversions to and from strings should occur via org.lfdecentralizedtrust.splice.util.Proto.encode/tryDecode.

When interacting with the Ledger API, we convert the Scala BigDecimals to Java BigDecimals.

Overall, please refer to the wallet.tap command implementation for the canonical handling of Daml Numerics.

Message Definitions

  • All Protobuf definitions should be using proto3
  • Avoid wrapping primitive types in a message structure unless future extensibility will likely be required
  • Use a plural name for repeated fields
  • Use string fields with a suffix contract_id to store contract ids
  • Use string fields with a suffix party_id to store party ids

OpenAPI / Scan API Changes

Modifying BFT-consensus types

Update history types served by the Scan API are serialized to JSON and compared across SVs for BFT consensus. Any schema change — adding, removing, or renaming a field — will cause JSON divergence between SVs running different code versions, breaking consensus.

For any such change you must ensure a coordination mechanism is in place (e.g., a threshold record time gate) so that the new schema is not emitted before all SVs have deployed the updated code.

Adding or removing required fields

Adding a new required field means new-code SVs emit a key that old-code SVs do not; removing one has the opposite effect. Both break JSON equality. Gate the change behind a threshold record time or equivalent mechanism so that all SVs switch over simultaneously.

Adding optional fields

Optional fields are especially easy to add without realizing the consequences: circe emits null for Option[_] fields by default instead of omitting them, so new-code SVs emit "field": null while old-code SVs omit the key entirely.

If you need to add an optional field to any type in the update history schema:

  1. Prefer making the field required with a sensible default — this avoids the problem entirely (though you still need a gate for the new key itself).
  2. If the field must be optional, use Option[OmitNullString] (via x-scala-type in the OpenAPI spec) and wire it into ScanJsonSupport using the omitWhenNone helper so the key is omitted from JSON when None.
  3. Add the new type (if it is a new leaf type) to the compliance check in UpdateHistoryOmitNullStringComplianceTest — this test verifies that no Option[_] fields exist on BFT-consensus types unless they are Option[OmitNullString].

See ScanJsonSupport.scala for the full set of custom encoders and their documentation.

Config Parameters

  • name flags as enableXXX instead of disableXXX to avoid a double negation

Code Layout

  • Place .proto files in src/main/protobuf
  • Prefer having a single .proto definition per service.
  • Refer to generated Protobuf classes with a package prefix, e.g., v0.MyMessage instead of MyMessage. This avoids name conflicts with our hand-written classes.

Domain Specific Naming

  • Use listXXX, acceptXXX, rejectXXX, withdrawXXX for managing proposals, requests etc.
  • Beware of the differences between amount, quantity and number: To keep things simple across the repository, we consistently use only the term amount
  • Between sender/receiver and payer/payee: please use sender/receiver

Frontend Code

Background

This section discusses how to contribute, or add new frontend code for an app. To understand how to run frontends locally, see Building and Running the Wallet and Splitwell Apps.

Frontend code projects are managed via npm workspaces. This gives us a way to manage multiple distinct NPM packages all co-located in the same monorepo, and confers several benefits:

  • One local monorepo package can be installed as a dependency of another, enabling "easy" code-sharing.
  • With npm install, all dependencies of all workspace projects are installed in the root node_modules folders, giving us de-deduplication.
  • If all workspace projects share common scripts, you can easily run that script across all workspaces in one command.
  • All required npm commands are issued from sbt compile, so there should not be a need to run e.g. npm install directly.

New Packages

In this section only, the term "root-level directory" will describe the workspace root, which is inside apps/ (not the repo root directory).

If you want to add a new package to the workspace, first register its directory in the root-level apps/package.json workspaces key. The directory referenced here must contain a package.json of its own defining the workspace package itself -- name, dependencies, etc.

Then add the new package to build.sbt following the examples from the existing frontend packages.

Running sbt compile (or manually npm install from the root) installs the dependencies of all registered workspace packages.

Make sure your package contains at least the scripts build, fix, check, and start. This enables the use of (e.g.) npm run build --workspaces to run the build script for all packages in the workspace at once, as well as proper integration with sbt.

Your new package will need its own tsconfig.json file that inherits from the root tsconfig. See any existing workspace package for an example.

Common libs

In apps/common/frontend we have an NPM package containing common code. This package (named common-frontend) can be installed with npm install @lfdecentralizedtrust/splice-common-frontend -w my-workspace-pkg. You can import anything from it with import { ... } from '@lfdecentralizedtrust/splice-common-frontend' in your package's source code.

You're also free to add more things in common-frontend to use across multiple frontend apps. This can really include anything: utility functions, reusable React components, shared config, etc. Just ensure whatever you add is exposed via the lib's entrypoint, index.ts (we use the barreling technique to expose all modules from the root of the library).

Conversions between Java & Scala types

Because we use the Java bindings and codegen, we need to convert between Java and Scala types, e.g., Seq and java.util.List. We try to use Scala types whereever possible so we delay the conversion to Java types until the last possible point and convert from Scala to Java as early as possible.

To convert, import scala.jdk.CollectionConverters.*. You can then use asScala and asJava methods.

Dev Docs

We publish docs from each commit from main to https://canton-network.github.io/splice/. This can often be useful to answer support requests with a docs link even if those docs are still very recent.