Static Configuration

Canton differentiates between static and dynamic configuration. Static configuration is immutable and therefore has to be known from the beginning of the process start. An example for a static configuration are the connectivity parameters to the local persistence store or the port the admin-apis should bind to. On the other hand, connecting to a domain or adding parties however is not a static configuration and therefore is not set via the config file but through the adminstration APIs or the console.

initialization of Canton nodes.

The configuration files themselves are written in HOCON format with some extensions:

Canton does not run one node, but any number of nodes, be it domain or participant nodes in the same process. Therefore, the root configuration allows to define several instances of domain and participant nodes together with a set of general process parameters.

A sample configuration file for two participant nodes and a single domain can be seen below.

canton {
  parameters {
    manual-start = yes
  }
  participants {

    participant1 {
      storage {
        type = memory
      }

      admin-api {
        port= 5012
      }

      ledger-api {
        port = 5011
      }
    }

    participant2 {
      storage {
        type = memory
      }

      admin-api {
        port= 5022
      }

      ledger-api {
        port = 5021
      }
    }
  }

  domains {
    mydomain {
      storage {
        type = memory
      }

      public-api.port = 5018
      admin-api.port = 5019

    }
  }

}

HOCON supports substituting environment variables for config values using the syntax key = ${ENV_VAR_NAME}.

Advanced Configurations

Configuration files can be nested and combined together. First, using the include directive (with relative paths), a configuration file can include other configuration files.

canton {
    domains {
        include "domain1.conf"
    }
}

Second, by providing several configuration files, we can override configuration settings using explicit configuration option paths:

canton.participants.myparticipant.admin-api.port = 11234

Those defined first will have precedence if the same key is included in multiple configurations.

Even more than multiple configuration files, we can leverage pureconfig to create shared configuration items that refer to environment variables. A handy example is the following, which allows to share database configuration settings in a setup involving several participant or domain nodes:

# shared configuration elements are placed in a key outside of canton so they won't be validated
# by pureconfig until they are substituted into the main config below
_shared {
# shared persistent storage definition
# storage.db-name must be specified at the substitution site as this cannot be shared
  storage {
    type = postgres
    config {
      dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
      properties = {
        serverName = ${CANTON_DB_HOST}
        portNumber = ${CANTON_DB_PORT}
        user = ${CANTON_DB_USER}
        password = ${CANTON_DB_PASSWORD}
      }
      connectionPool = HikariCP
      maxConnections = 8
      maxThreads = 8
      numThreads = 8
    }
  }
}

Such a definition can subsequently be referenced in the actual node definition:

canton {
    domains {
        mydomain {
            storage = ${_shared.storage}
            storage.config.properties.databaseName = ${CANTON_DB_NAME_DOMAIN}
        }
    }
}

Monitoring

The canton process can optionally expose an HTTP endpoint indicating if the process believes it is healthy. This is intended for use in uptime checks and liveness probes. If enabled, the /health endpoint will respond to a GET http request with a 200 HTTP status code if healthy or 500 if unhealthy (with a plain text description of why it is unhealthy).

To enable this health endpoint add a monitoring section to the canton configuration. As this health check is for the whole process, it is added directly to the canton configuration rather than for a specific node.

canton {
  monitoring.health {
   server {
      port = 7000
   }

   check {
     type = ping
     participant = participant1
     interval = 30s
   }
}

This health check will have participant1 “ledger ping” itself every 30 seconds. The process will be considered healthy if the ping is successful.

Persistence

Note

Please note that Canton is still in pre-alpha stage and support for crash-recovery is limited.

Participant and domain nodes both require storage configurations. Both use the same configuration format and therefore support the same configuration options. There are three different configurations available:

  1. Memory - Using simple, hash-map backed in-memory stores which are deleted whenever a node is stopped.
  2. H2 - Based on the H2-database engine.
  3. Postgres - To use with the open source relational database Postgres.

In order to set a certain storage type, we have to edit the storage section of the particular node, such as canton.participants.myparticipant.storage = Memory. Memory storage does not require any other setting.

For the actual database driver, Canton does not directly define how they are configured, but leverages a third party library (slick) for it, exposing all configuration methods therein. Therefore, please refer to the respective detailed documentation to learn about all configuration options.

Please note that Canton will create, manage and upgrade the database schema directly. You don’t have to create tables yourselves.

Postgres

The Slick configuration allows to either define the driver directly or use the respective dataSource objects. An example for a driver based definition for Postgres configuration would read:

canton {
  participants {
    participant2 {
      storage {
        type = postgres
        config {
          url = "jdbc:postgresql://0.0.0.0:5432/participant2"
          user = "participant2"
          password = "supersafe"
          driver = org.postgresql.Driver
        }
      }
    }
  }
}

Whereas the dataSource based configuration would be:

canton {
  participants {
    participant2 {
      storage {
        type = postgres
        config {
          dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
          properties = {
            serverName = "localhost"
            portNumber = "5432"
            databaseName = "participant2"
            user = "participant2"
            password = "supersafe"
          }
          numThreads = 20
        }
      }
    }
  }
}

Please refer to the respective Slick configuration settings for detailed setup instructions and options, including the reference guide. The above configurations are included in the examples/03-advanced-configuration folder and are sufficient to get going.

H2 Database

In some cases, the overhead of running a separate database is not desired. For these situations, the H2-database engine can be leveraged.

Canton allows a user to directly access the underlying H2 setup settings which allows the administrator to use all available H2 modes directly.

The canonical example of using H2 with a file-backed database is given by

canton {
  participants {
    participant2 {
      storage {
        type = "h2"
        config = {
          connectionPool = disabled
          url = "jdbc:h2:file:./participant2;MODE=PostgreSQL;LOCK_TIMEOUT=10000;DB_CLOSE_DELAY=-1"
          user = "participant2"
          password = "morethansafe"
          driver = org.h2.Driver
        }
      }
    }
  }
}

If you are looking for any other H2 setup, please consult the H2 manual directly.

Api Configuration

A domain node exposes two main APIs: the admin-api and the public-api, while the participant node exposes the ledger-api and the admin-api. In this section, we will explain what the APIs do and how they can be configured. All APIs are based on GRPC, which is an efficient RPC and streaming protocol with client support in almost all relevant programming languages. Native bindings can be built using the API definitions.

Administration API

The nature and scope of the admin api on participant and domain nodes has some overlap. As an example, you will find the same key management commands on the domain and the participant node API, whereas the participant has different commands to connect to several domains.

The configuration currently is simple (see the TLS example below) and just takes an address and a port. The address defaults to 127.0.0.1 which means that in most cases you just need to configure the port number.

You should not expose the admin-api publicly as it serves administrative purposes only.

TLS Configuration

In order to use TLS on the admin API, you need to provide a server certificate chain and a server private key.

admin-api {
    address = "127.0.0.1"
    port = 5042
    tls {
        server-cert-chain-file = "./tls/cert.crt"
        server-private-key-file = "./tls/private.key"
    }
}

These TLS settings allow a connecting client to ensure that it is talking to the right server. If Canton connects to the admin API of a node remotely (i.e., one Canton instance runs the node and a different instance connects to the node), then both Canton configurations need to contain a TLS configuration for the node with the same server certificate chain. The client’s configuration may omit the server’s private key.

Note

TODO(i2496): Adding client authentication or authorization via JWT to the admin-api is on the roadmap, but has not yet been added to Canton.

If you need to create a self-signed TLS certificate, you can use the following openssl commands:

#!/bin/bash

mkdir tls
cd tls
openssl req -new -newkey rsa:4096 -x509 -sha256 -days 90 -nodes -out "cert.crt" -keyout "private.key" -subj "/O=TESTING/OU=TESTING/CN=localhost/emailAddress=canton@digitalasset.com"

Participant Configuration

Ledger Api

The configuration of the ledger API is similar to the admin API configuration, except that the group starts with ledger-api instead of admin-api.

If TLS is enabled, both client and server are authenticated (2-way TLS). Therefore, a trust-collection-file needs to be specified as part of the TLS configuration. The file must contain all client certificates who are trusted to use the ledger API.

Domain Configurations

Public Api

The domain configuration requires the same configuration of the admin-api as the participant. Next to the admin-api, we need to configure the public-api, which is the api where all participants will connect to. There is a built in authentication of the restricted services on the public api, leveraging the participant signing keys. You don’t need to do anything in order to set this up. It is enforced automatically and can’t be turned off.

As with the admin-api, network traffic can (and should be) encrypted using TLS.

An example configuration section is given by

  public-api {
    port = 5028
    address = "localhost"
    tls {
      server-cert-chain-file = "./tls/cert.crt"
      server-private-key-file = "./tls/private.key"
    }
  }

If TLS is used on the server side with a self-signed certificate, we need to pass the certificate chain during the connect call. Otherwise, the default root certificates of the Java runtime will be used. An example would be:

    participant3.domains.connect(
      domainAlias = "acme",
      connection = s"https://$hostname:$port",
      certificatesPath = certs // path to certificate chain file (.pem) of server
    )

Domain Rules

Every domain has its own rules in terms of what parameters are used by the participants while running the protocol. The participants obtain these parameters before connecting to the domain. They can be configured using the specific parameter section. An example would be:

domain-parameters {
  # Left empty to use default values from class DomainParameters.
}

The full set of available parameters can be found in the scala reference documentation.

Cryptography

All crypto-graphic routines used in Canton are represented through an abstract API for asymmetric and symmetric cryptographic operations. Currently, there are two implementations available, a convenient one for development and testing and one for serious and secure use.

Tink

The real cryptographic module is based on Tink and can be activated using

canton.domains.<mydomain>.crypto.type = tink
canton.domains.<myparticipant>.crypto.type = tink

Symbolic

The symbolic cryptography module will not encrypt anything but just wrap the content in a clear-text container together with the information of what was encrypted by whom.

canton.domains.<mydomain>.crypto.type = symbolic

Reference

Canton configuration file for static properties is based on Pureconfig. Pureconfig maps Scala case classes into appropriate configuration options. Therefore, the ultimate source of truth for all available configuration options is given by the appropriate scaladocs of the CantonConfig classes.

In order to map the Scala names into the HOCON format, we need to map the names from CamelCase to lowercase-with-dashes, i.e. domainParameters becomes domain-parameters (dash, not underscore).