Synchronizer connections
Synchronizer connections
A Participant Node connects to the Synchronizer by connecting to one or many Sequencers of this Synchronizer.
A Participant Node can connect to multiple Synchronizers at once. The synchronizer connectivity commands allow the administrator of a Participant Node to manage connectivity to Synchronizers.
The following sections explain how to manage such Sequencer connections for a Participant Node.
Connect
Connect to a Sequencer by Reference
To connect a Participant Node to a Sequencer by reference (local or remote reference), follow the steps below.
Define a Synchronizer alias. An alias is a name chosen by the Participant’s operator to manage the connection. For example:
@ val synchronizerAlias = "mysynchronizer"
synchronizerAlias : String = "mysynchronizer"
2. Set Optional Sequencer Connection Validation. This parameter determines how thoroughly the provided Sequencer connections are validated before being persisted.
@ val sequencerConnectionValidation = com.digitalasset.canton.sequencing.SequencerConnectionValidation.Active
sequencerConnectionValidation : Active.type = com.digitalasset.canton.sequencing.SequencerConnectionValidation$Active$@2bd1394
Execute the
connect_localcommand:
@ participantReference.synchronizers.connect_local(sequencerReference, synchronizerAlias, validation = sequencerConnectionValidation)
List the connected Synchronizers to verify that the connection is listed. See inspect connections.
Connect to a Sequencer by URL
To connect to a remote Sequencer:
Obtain the Sequencer address and port from the Synchronizer Operator.
@ val sequencerUrl = s"https://${sequencerAddress}:${port}"
sequencerUrl : String = "https://127.0.0.1:30133"
2. Provide custom certificates (if necessary). If the Sequencer uses TLS certificates that cannot be automatically validated using your JVM’s trust store (for example, self-signed certificates), you have to provide a custom certificate such that the client can verify the Sequencer’s public API TLS certificate. The operate must trust the custom root CA. Let’s assume that this root certificate is given by:
@ val certificatesPath = "tls/root-ca.crt"
certificatesPath : String = "tls/root-ca.crt"
Connect the Participant to the Sequencer using the
connectconsole command.
@ participantReference.synchronizers.connect("mysynchronizer", connection = sequencerUrl, certificatesPath = certificatesPath)
res6: SynchronizerConnectionConfig = SynchronizerConnectionConfig(
synchronizer = Synchronizer 'mysynchronizer',
sequencerConnections = SequencerConnections(
connections = Sequencer 'DefaultSequencer' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'DefaultSequencer',
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
),
sequencer trust threshold = 1,
sequencer liveness margin = 0,
submission request amplification = SubmissionRequestAmplification(factor = 1, patience = 0s),
sequencer connection pool delays = SequencerConnectionPoolDelays(
minRestartDelay = 0.01s,
maxRestartDelay = 10s,
warnValidationDelay = 20s,
subscriptionRequestDelay = 1s
)
),
manualConnect = false
)
List the connected Synchronizers to verify that the connection is listed. See inspect connections.
Connect to decentralized Sequencers
To enhance reliability and security, you can connect to multiple Sequencers of the same Synchronizer using the connect_bft command.
Create the URL for each Sequencer by referencing its public API address and port.
@ val sequencer1Url = s"http://${sequencer1Address}:${sequencer1Port}"
sequencer1Url : String = "http://0.0.0.0:30129"
@ val sequencer2Url = s"http://${sequencer2Address}:${sequencer2Port}"
sequencer2Url : String = "http://127.0.0.1:30131"
@ val sequencer3Url = s"https://${sequencer3Address}:${sequencer3Port}"
sequencer3Url : String = "https://127.0.0.1:30133"
@ val sequencer3Certificate = com.digitalasset.canton.util.BinaryFileUtil.tryReadByteStringFromFile(certificatesPath)
sequencer3Certificate : com.google.protobuf.ByteString = <ByteString@15626f98 size=1960 contents="-----BEGIN CERTIFICATE-----\nMIIFeTCCA2GgAwIBAgI...">
Create the Sequencer connections.
@ val connections = Seq(
GrpcSequencerConnection.tryCreate(sequencer1Url, sequencerAlias = "sequencer1"),
GrpcSequencerConnection.tryCreate(sequencer2Url, sequencerAlias = "sequencer2"),
GrpcSequencerConnection.tryCreate(sequencer3Url, sequencerAlias = "sequencer3", customTrustCertificates = Some(sequencer3Certificate)),
)
connections : Seq[GrpcSequencerConnection] = List(
GrpcSequencerConnection(sequencerAlias = Sequencer 'sequencer1', endpoints = http://0.0.0.0:30129),
GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer2',
endpoints = http://127.0.0.1:30131
),
GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer3',
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
)
)
Configure the Sequencer trust threshold by setting the minimum number of Sequencers that must agree before a message is considered valid.
Default: 1
Maximum: Number of connections
For example, setting the threshold to 2:
@ val sequencerTrustThreshold = 2
sequencerTrustThreshold : Int = 2
[Specific to the connection pool] Configure the Sequencer liveness margin; this sets the number of Sequencer connections, in addition to
sequencerTrustThreshold-many, from which we attempt to read messages.
Default: 0
Higher values increase the resilience to Sequencer delays or failures, but having (
sequencerTrustThreshold+sequencerLivenessMargin> number of connections) provides no benefit.
For example, setting the liveness margin to 1:
@ val sequencerLivenessMargin = 1
sequencerLivenessMargin : Int = 1
Configure submission request amplification: define how often the client should try to send a submission request that is eligible for deduplication.
Higher values increase the chance of a submission request being accepted by the system, but also increase the load on the Sequencers.
Default:
SubmissionRequestAmplification.NoAmplification
In this example, I want to ensure that submission requests are sent to two Sequencers. Therefore, I set the amplification factor to 2 and the patience to 0 seconds.
@ val submissionRequestAmplification = SubmissionRequestAmplification(factor = 2, patience = 0.seconds)
submissionRequestAmplification : SubmissionRequestAmplification = SubmissionRequestAmplification(factor = 2, patience = 0s)
Connect using the
connect_bftcommand.
@ participantReference.synchronizers.connect_bft(
connections,
synchronizerAlias,
sequencerTrustThreshold = sequencerTrustThreshold,
sequencerLivenessMargin = sequencerLivenessMargin,
submissionRequestAmplification = submissionRequestAmplification,
)
@ participantReference.synchronizers.list_registered()
res16: Seq[(SynchronizerConnectionConfig, ConfiguredPhysicalSynchronizerId, Boolean)] = Vector(
(
SynchronizerConnectionConfig(
synchronizer = Synchronizer 'mysynchronizer',
sequencerConnections = SequencerConnections(
connections = Map(
Sequencer 'sequencer1' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer1',
sequencerId = SEQ::sequencer1::1220cb0a22fb...,
endpoints = http://0.0.0.0:30129
),
Sequencer 'sequencer2' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer2',
sequencerId = SEQ::sequencer2::12203a55a279...,
endpoints = http://127.0.0.1:30131
),
Sequencer 'sequencer3' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer3',
sequencerId = SEQ::sequencer3::122076e8bfb8...,
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
)
),
sequencer trust threshold = 2,
sequencer liveness margin = 1,
submission request amplification = SubmissionRequestAmplification(factor = 2, patience = 0s),
sequencer connection pool delays = SequencerConnectionPoolDelays(
minRestartDelay = 0.01s,
maxRestartDelay = 10s,
warnValidationDelay = 20s,
subscriptionRequestDelay = 1s
)
),
manualConnect = false
),
da::1220222f081c6c7d7dd4cba1612b1c80e12e0a7c1eef2139be2d928d903fccf9f090::34-0,
true
)
)
List the connected Synchronizers to verify that the connection is listed. See inspect connections.
Inspect connections
You can inspect the connected Synchronizer connections, as well as the configuration of a specific connection.
List connected Synchronizer connections. You can get the Synchronizer aliases and Synchronizer IDs of all connected Synchronizers:
@ participantReference.synchronizers.list_connected()
res17: Seq[ListConnectedSynchronizersResult] = Vector(
ListConnectedSynchronizersResult(
synchronizerAlias = Synchronizer 'mysynchronizer',
physicalSynchronizerId = da::1220222f081c...::34-0,
healthy = true
)
)
And you can inspect the configuration of a specific Synchronizer connection using:
Inspect the configuration of a specific connection:
@ participantReference.synchronizers.config("mysynchronizer")
res18: Option[SynchronizerConnectionConfig] = Some(
value = SynchronizerConnectionConfig(
synchronizer = Synchronizer 'mysynchronizer',
sequencerConnections = SequencerConnections(
connections = Map(
Sequencer 'sequencer1' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer1',
sequencerId = SEQ::sequencer1::1220cb0a22fb...,
endpoints = http://0.0.0.0:30129
),
Sequencer 'sequencer2' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer2',
sequencerId = SEQ::sequencer2::12203a55a279...,
endpoints = http://127.0.0.1:30131
),
Sequencer 'sequencer3' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer3',
sequencerId = SEQ::sequencer3::122076e8bfb8...,
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
)
),
sequencer trust threshold = 2,
sequencer liveness margin = 1,
submission request amplification = SubmissionRequestAmplification(factor = 2, patience = 0s),
sequencer connection pool delays = SequencerConnectionPoolDelays(
minRestartDelay = 0.01s,
maxRestartDelay = 10s,
warnValidationDelay = 20s,
subscriptionRequestDelay = 1s
)
),
manualConnect = false
)
)
Modify
Update Sequencer trust threshold
You can modify the Sequencer trust threshold to control how many Sequencers must agree before a message is considered valid.
Define a valid threshold. Set the threshold value between 1 and the number of connected Sequencers.
@ participantReference.synchronizers.modify("mysynchronizer", _.tryWithSequencerTrustThreshold(1))
Verify the updated configuration using:
@ participantReference.synchronizers.config("mysynchronizer")
res20: Option[SynchronizerConnectionConfig] = Some(
value = SynchronizerConnectionConfig(
synchronizer = Synchronizer 'mysynchronizer',
sequencerConnections = SequencerConnections(
connections = Map(
Sequencer 'sequencer1' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer1',
sequencerId = SEQ::sequencer1::1220cb0a22fb...,
endpoints = http://0.0.0.0:30129
),
Sequencer 'sequencer2' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer2',
sequencerId = SEQ::sequencer2::12203a55a279...,
endpoints = http://127.0.0.1:30131
),
Sequencer 'sequencer3' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer3',
sequencerId = SEQ::sequencer3::122076e8bfb8...,
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
)
),
sequencer trust threshold = 1,
sequencer liveness margin = 1,
submission request amplification = SubmissionRequestAmplification(factor = 2, patience = 0s),
sequencer connection pool delays = SequencerConnectionPoolDelays(
minRestartDelay = 0.01s,
maxRestartDelay = 10s,
warnValidationDelay = 20s,
subscriptionRequestDelay = 1s
)
),
manualConnect = false
)
)
[Specific to the connection pool] Update Sequencer liveness margin
You can modify the Sequencer liveness margin to control the resilience to faulty Sequencers.
Update the configuration with the new liveness margin:
@ participantReference.synchronizers.modify("mysynchronizer", _.withSequencerLivenessMargin(0))
Verify the updated configuration using:
@ participantReference.synchronizers.config("mysynchronizer")
res22: Option[SynchronizerConnectionConfig] = Some(
value = SynchronizerConnectionConfig(
synchronizer = Synchronizer 'mysynchronizer',
sequencerConnections = SequencerConnections(
connections = Map(
Sequencer 'sequencer1' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer1',
sequencerId = SEQ::sequencer1::1220cb0a22fb...,
endpoints = http://0.0.0.0:30129
),
Sequencer 'sequencer2' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer2',
sequencerId = SEQ::sequencer2::12203a55a279...,
endpoints = http://127.0.0.1:30131
),
Sequencer 'sequencer3' -> GrpcSequencerConnection(
sequencerAlias = Sequencer 'sequencer3',
sequencerId = SEQ::sequencer3::122076e8bfb8...,
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
)
),
sequencer trust threshold = 1,
sequencer liveness margin = 0,
submission request amplification = SubmissionRequestAmplification(factor = 2, patience = 0s),
sequencer connection pool delays = SequencerConnectionPoolDelays(
minRestartDelay = 0.01s,
maxRestartDelay = 10s,
warnValidationDelay = 20s,
subscriptionRequestDelay = 1s
)
),
manualConnect = false
)
)
Update request amplification
1. Configure submission request amplification. Amplification makes Sequencer clients send eligible submission requests to multiple Sequencers to overcome message loss in faulty Sequencers.
@ participantReference.synchronizers.modify("mysynchronizer", _.withSubmissionRequestAmplification(SubmissionRequestAmplification.NoAmplification))
Same as above, verify the updated configuration using
configcommand.
Synchronizer priority
When connected to multiple Synchronizers, transaction routing will select the Synchronizer with the highest priority for submitting transactions. You can adjust the priority of a Synchronizer to control which one is preferred for submissions. For more details, refer to the multiple synchronizer documentation
Update the priority of the Synchronizer connection.
@ participantReference.synchronizers.modify("mysynchronizer", _.withPriority(0))
Same as above, verify the updated configuration using
configcommand.
Update a custom TLS trust certificate
Whenever the root of trust changes, the clients need to update the custom root certificate accordingly. To update a custom TLS trust certificate, particularly when using self-signed certificates as the root of trust for a TLS Sequencer connection, follow these steps:
Load the root certificate from a file:
@ val certificate = com.digitalasset.canton.util.BinaryFileUtil.tryReadByteStringFromFile("tls/root-ca.crt")
certificate : com.google.protobuf.ByteString = <ByteString@59bf919e size=1960 contents="-----BEGIN CERTIFICATE-----\nMIIFeTCCA2GgAwIBAgI...">
Create a new connection instance and pass the certificate into this new connection.
@ val connection = com.digitalasset.canton.sequencing.GrpcSequencerConnection.tryCreate(sequencerUrl, customTrustCertificates = Some(certificate))
connection : GrpcSequencerConnection = GrpcSequencerConnection(
sequencerAlias = Sequencer 'DefaultSequencer',
endpoints = https://127.0.0.1:30133,
transportSecurity,
customTrustCertificates
)
Update the Sequencer connection settings on the Participant Node:
@ participantReference.synchronizers.modify("mysynchronizer", _.copy(sequencerConnections=SequencerConnections.single(connection)))
For Mediators, update the certificate settings using a similar approach.
Disconnect and reconnect
Disconnect from the Synchronizer by using the
disconnectcommand with the Synchronizer alias:
@ participantReference.synchronizers.disconnect("mysynchronizer")
Verify the disconnection:
@ participantReference.synchronizers.list_connected().isEmpty
res29: Boolean = true
Reconnect to a specific Synchronizer using the
reconnectcommand with the Synchronizer alias:
@ participantReference.synchronizers.reconnect("mysynchronizer")
res30: Boolean = true
4. To reconnect all Synchronizers that are not configured to require a manual connection, use the following command:
@ participantReference.synchronizers.reconnect_all()