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 sync domain 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 sync domains or participant nodes in the same process. Therefore, the root configuration allows to define several instances of sync domains and participant nodes together with a set of general process parameters.

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

canton {
  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 {
      init.domain-parameters.protocol-version = 5
      storage.type = memory
      public-api.port = 5018
      admin-api.port = 5019
    }
  }
  // enable ledger_api commands for our getting started guide
  features.enable-testing-commands = yes
}

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. domainParameters in the scaladocs becomes domain-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 {
    domains {
        include required(file("domain1.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 sync domains 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 equired
                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 {
    domains {
        mydomain {
            storage = ${_shared.storage}
            storage.config.properties.databaseName = ${CANTON_DB_NAME_DOMAIN}
        }
    }
}

Multiple Synchronization Domains

A Canton configuration allows you to define multiple sync domains. Also, a Canton participant can connect to multiple sync domains. 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 sync domains. In this situation, we need to turn contract key uniqueness off by setting

canton {
  domains {
    alpha {
      // subsequent changes have no effect and the mode of a node can never be changed
      init.domain-parameters {
        unique-contract-keys = false
        protocol-version = 5
      }
    }
  }

  participants {
    participant1 {
      // subsequent changes have no effect and the mode of a node can never be changed
      init.parameters.unique-contract-keys = false
    }
  }
}

Please note that the setting is final and cannot be changed subsequently. We will provide a migration path once multi-sync-domain 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
    parameters.unique-contract-keys = false
    identity.node-identifier.type = random
  }
}

The option ledger-api.max-deduplication-duration sets the maximum deduplication duration that the participant’s ledger configuration service reports and uses for command deduplication.