Note

This page is a work in progress. It may contain incomplete or incorrect information.

Set Configuration Options

Canton differentiates between static and dynamic configuration. Static configuration is immutable and therefore has to be known from the beginning of the process. For a static configuration, examples would be the connectivity parameters to the local persistence store or the port the admin-apis should bind to. On the other hand, connecting to a synchronizer or adding parties are not matters of static configuration and therefore are not set via the config file but through console commands (or the administration APIs).

initialization of Canton nodes.

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

  • Durations are specified scala durations using a <length><unit> format. Valid units are defined by scala directly, but behave as expected using ms, s, m, h, d to refer to milliseconds, seconds, minutes, hours and days. Durations have to be non-negative in our context.

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

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

canton {

  features.enable-testing-commands = yes
  features.enable-preview-commands = yes

  // user-manual-entry-begin: SimpleSequencerNodeConfig
  sequencers {
    sequencer1 {
      storage.type = memory
      public-api.port = 5001
      admin-api.port = 5002
      sequencer.type = BFT
    }
  }
  // user-manual-entry-end: SimpleSequencerNodeConfig

  // user-manual-entry-begin: SimpleMediatorNodeConfig
  mediators {
    mediator1 {
      storage.type = memory
      admin-api.port = 5202
    }
  }
  // user-manual-entry-end: SimpleMediatorNodeConfig

  participants {
    // user-manual-entry-begin: port configuration
    participant1 {
      storage.type = memory
      admin-api.port = 5012
      ledger-api.port = 5011
    }
    // user-manual-entry-end: port configuration
    participant2 {
      storage.type = memory
      admin-api.port = 5022
      ledger-api.port = 5021
    }
  }
}

Configuration reference

The Canton configuration file for static properties is based on PureConfig. PureConfig maps Scala case classes and their class structure into analogue configuration options (see e.g. the PureConfig quick start for an example). Therefore, the ultimate source of truth for all available configuration options and the configuration file syntax is given by the appropriate scaladocs of the CantonConfig classes.

When understanding the mapping from scaladocs to configuration, please keep in mind that:

  • CamelCase Scala names are mapped to lowercase-with-dashes names in configuration files, e.g. synchronizerParameters in the scaladocs becomes synchronizer-parameters in a configuration file (dash, not underscore).

  • Option[<scala-class>] means that the configuration can be specified but doesn’t need to be, e.g. you can specify a JWT token via token=token in a remote participant configuration, but not specifying token is also valid.

Configuration Compatibility

The enterprise edition configuration files extend the community configuration. As such, any community configuration can run with an enterprise binary, whereas not every enterprise configuration file will also work with community versions.

Advanced Configurations

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

canton {
    synchronizers {
        include required(file("synchronizer1.conf"))
    }
}

The required keyword will trigger an error, if the included file does not exist; without the required keyword, any missing files will be silently ignored. The file keyword instructs the configuration parser to interpret its argument as a file name; without this keyword, the parser may interpret the given name as a URL or classpath resource. By using the file keyword, you will also get the most intuitive semantics and most stable semantics of include. The precise rules for resolving relative paths can be found here.

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

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

If the same key is included in multiple configurations, then the last definition has highest precedence.

Furthermore, HOCON supports substituting environment variables for config values using the syntax key = ${ENV_VAR_NAME} or optional substitution key = ${?ENV_VAR_NAME}, where the key will only be set if the environment variable exists.

Configuration Mixin

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 for sharing database configuration settings in a setup involving several synchronizers or participant nodes:

# Postgres persistence configuration mixin
#
# This file defines a shared configuration resources. You can mix it into your configuration by
# refer to the shared storage resource and add the database name.
#
# Example:
#   participant1 {
#     storage = ${_shared.storage}
#     storage.config.properties.databaseName = "participant1"
#   }
#
# The user and password is not set. You want to either change this configuration file or pass
# the settings in via environment variables POSTGRES_USER and POSTGRES_PASSWORD.
#
_shared {
    storage {
        type = postgres
        config {
            dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
            properties = {
                serverName = "localhost"
                # the next line will override above "serverName" in case the environment variable POSTGRES_HOST exists
                # which makes it optional
                serverName = ${?POSTGRES_HOST}
                portNumber = "5432"
                portNumber = ${?POSTGRES_PORT}
                # user and password are required
                user = ${POSTGRES_USER}
                password = ${POSTGRES_PASSWORD}
            }
        }
        parameters {
            # If defined, will configure the number of database connections per node.
            # Please note that the number of connections can be fine tuned for participant nodes (see participant.conf)
            max-connections = ${?POSTGRES_NUM_CONNECTIONS}
            # If true, then database migrations will be applied on startup automatically
            # Otherwise, you will have to run the migration manually using participant.db.migrate()
            migrate-and-start = false
            # If true (default), then the node will fail to start if it can not connect to the database.
            # The setting is useful during initial deployment to get immediate feedback when the
            # database is not available.
            # In a production setup, you might want to set this to false to allow uncoordinated startups between
            # the database and the node.
            fail-fast-on-startup = true
        }
    }
}

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

canton {
    synchronizers {
        mysynchronizer {
            storage = ${_shared.storage}
            storage.config.properties.databaseName = ${CANTON_DB_NAME_SYNCHRONIZER}
        }
    }
}

Multiple Synchronizers

A Canton configuration allows you to define multiple synchronizers. Also, a Canton participant can connect to multiple synchronizers. This is however only supported as a preview feature and not yet suitable for production use.

In particular, contract key uniqueness cannot be enforced over multiple synchronizers. In this situation, we need to turn contract key uniqueness off by setting

Please note that the setting is final and cannot be changed subsequently. We will provide a migration path once multi-synchronizer functionality is fully implemented.

Fail Fast Mode

By default, Canton will fail to start if it cannot access some external dependency such as the database. This is preferable during initial deployment and development, as it provides instantaneous feedback, but can cause problems in production. As an example, if Canton is started with a database in parallel, the Canton process would fail if the database is not ready before the Canton process attempts to access it. To avoid this problem, you can configure a node to wait indefinitely for an external dependency such as a database to start. The config option below will disable the “fail fast” behavior for participant1.

canton.participants.participant1.storage.parameters.fail-fast-on-startup = "no"

This option should be used with care as, by design, it can cause infinite, noisy waits.

Init Configuration

Some configuration values are only used during the first initialization of a node and cannot be changed afterwards. These values are located under the init section of the relevant configuration of the node. Below is an example with some init values for a participant config

participant1 {
  init {
    // example settings
    ledger-api.max-deduplication-duration = 1 minute
    identity.node-identifier.type = random
  }
}

The option ledger-api.max-deduplication-duration sets the maximum deduplication duration that the participant uses for command deduplication.