5.2 Deployment and DevOps Checklist
Successful deployment requires distinct focus on securing and ensuring high availability for all three architectural layers.
A. Canton Infrastructure
1. Production Canton Domain Setup
bash
# Initialize Canton domain
canton {
domains {
production {
storage {
type = postgres
config {
url = "jdbc:postgresql://canton-db:5432/canton"
user = "canton"
password = ${?CANTON_DB_PASSWORD}
}
}
public-api {
port = 5021
tls {
cert-chain-file = "/etc/canton/certs/domain.crt"
private-key-file = "/etc/canton/keys/domain.key"
}
}
monitoring {
metrics {
enabled = true
reporters = ["prometheus"]
}
}
}
}
participants {
vault-operator {
storage = ${canton.domains.production.storage}
admin-api.port = 5022
ledger-api.port = 5011
}
}
}2. Access Control Configuration
daml
-- Configure Canton topology
namespace Canton.Topology
-- Add policy issuer party
topology.transactions.add_party(
party_id = "PolicyIssuer::1234567890abcdef",
participant = "vault-operator"
)
-- Grant signing key
topology.transactions.add_signing_key(
party = "PolicyIssuer::1234567890abcdef",
public_key = "0x...", -- HSM-backed key
key_purpose = SigningKeyPurpose.Policy
)
-- Add regulatory observer
topology.transactions.add_party(
party_id = "Regulator::fedcba9876543210",
participant = "regulator-node"
)3. HSM/KMS Integration
typescript
// AWS KMS integration for Canton
import { KMS } from 'aws-sdk';
class CantonKMSIntegration {
private kms: KMS;
private keyId: string;
constructor(keyId: string) {
this.kms = new KMS({ region: 'us-east-1' });
this.keyId = keyId;
}
async signIntent(intentHash: Buffer): Promise<Buffer> {
const result = await this.kms.sign({
KeyId: this.keyId,
Message: intentHash,
MessageType: 'DIGEST',
SigningAlgorithm: 'ECDSA_SHA_256'
}).promise();
return Buffer.from(result.Signature!);
}
async rotateKey(): Promise<string> {
// Generate new key
const newKey = await this.kms.createKey({
KeyUsage: 'SIGN_VERIFY',
KeySpec: 'ECC_NIST_P256',
Description: 'Canton Policy Issuer v2'
}).promise();
// Schedule old key deletion (30 days)
await this.kms.scheduleKeyDeletion({
KeyId: this.keyId,
PendingWindowInDays: 30
}).promise();
return newKey.KeyMetadata!.KeyId!;
}
}B. Relayer Layer
1. High Availability Deployment
yaml
# docker-compose.yml
version: '3.8'
services:
relayer-1:
image: canton-relayer:latest
environment:
- RELAYER_ID=relayer-1
- CANTON_API_URL=https://canton.example.com
- LEADER_ELECTION=true
- ETCD_ENDPOINTS=etcd-1:2379,etcd-2:2379,etcd-3:2379
deploy:
replicas: 1
placement:
constraints:
- node.labels.zone==us-east-1a
relayer-2:
image: canton-relayer:latest
environment:
- RELAYER_ID=relayer-2
- CANTON_API_URL=https://canton.example.com
- LEADER_ELECTION=true
- ETCD_ENDPOINTS=etcd-1:2379,etcd-2:2379,etcd-3:2379
deploy:
replicas: 1
placement:
constraints:
- node.labels.zone==us-west-2a
etcd-1:
image: quay.io/coreos/etcd:latest
command:
- etcd
- --name=etcd-1
- --initial-advertise-peer-urls=http://etcd-1:23802. Leader Election
typescript
import { Etcd3 } from 'etcd3';
class RelayerCluster {
private etcd: Etcd3;
private election: Election;
async startLeaderElection(): Promise<void> {
this.etcd = new Etcd3({
hosts: process.env.ETCD_ENDPOINTS!.split(',')
});
this.election = this.etcd.election('relayer-leader');
await this.election.campaign(process.env.RELAYER_ID!);
console.log(`${process.env.RELAYER_ID} became leader`);
// Start processing intents only if leader
if (await this.election.isLeader()) {
this.startIntentProcessing();
}
// Watch for leadership changes
this.election.observe().on('change', (leader) => {
if (leader === process.env.RELAYER_ID) {
this.startIntentProcessing();
} else {
this.stopIntentProcessing();
}
});
}
}3. Operational Logging
typescript
import winston from 'winston';
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
// Immutable log storage for auditing
new winston.transports.File({
filename: '/var/log/relayer/intents.log',
maxsize: 100 * 1024 * 1024, // 100MB
tailable: true
}),
// Ship to centralized logging
new winston.transports.Http({
host: 'logs.example.com',
port: 443,
path: '/api/logs',
ssl: true
})
]
});
// Log all operations
logger.info('intent_received', {
intentId: intent.id,
nonce: intent.nonce,
vaultId: intent.vaultId
});
logger.info('transaction_submitted', {
intentId: intent.id,
txHash: receipt.transactionHash,
chain: 'ethereum',
gasUsed: receipt.gasUsed
});C. On-Chain Vaults
1. Deployment Script
typescript
// scripts/deploy-vaults.ts
import { ethers } from 'hardhat';
async function deployVault(chain: string) {
console.log(`Deploying vault on ${chain}...`);
const [deployer] = await ethers.getSigners();
// Deploy vault
const Vault = await ethers.getContractFactory('CantonDeFiVault');
const vault = await Vault.deploy(
process.env.CANTON_SIGNER_ADDRESS!,
process.env.AUTHORIZED_PARTY_ID!
);
await vault.deployed();
console.log(`Vault deployed to: ${vault.address}`);
// Deploy adapters
const adapters = {
aave: await deployAaveAdapter(vault.address),
uniswap: await deployUniswapAdapter(vault.address),
compound: await deployCompoundAdapter(vault.address)
};
// Register adapters
for (const [protocol, adapter] of Object.entries(adapters)) {
const protocolId = ethers.utils.id(protocol);
await vault.registerAdapter(protocolId, adapter.address);
console.log(`Registered ${protocol} adapter: ${adapter.address}`);
}
// Verify on Etherscan
await hre.run('verify:verify', {
address: vault.address,
constructorArguments: [
process.env.CANTON_SIGNER_ADDRESS!,
process.env.AUTHORIZED_PARTY_ID!
]
});
return vault;
}
// Deploy to all chains
async function main() {
const chains = ['ethereum', 'polygon', 'arbitrum', 'optimism'];
for (const chain of chains) {
await deployVault(chain);
}
}
main().catch(console.error);2. Canton Key Registry (Pattern 2)
solidity
// contracts/CantonKeyRegistry.sol
contract CantonKeyRegistry is Ownable {
mapping(bytes32 => address) public cantonPartyToKey;
event KeyRegistered(bytes32 indexed partyId, address publicKey);
event KeyRevoked(bytes32 indexed partyId, address publicKey);
function registerKey(
bytes32 partyId,
address publicKey
) external onlyOwner {
require(publicKey != address(0), "Invalid key");
cantonPartyToKey[partyId] = publicKey;
emit KeyRegistered(partyId, publicKey);
}
function revokeKey(bytes32 partyId) external onlyOwner {
address oldKey = cantonPartyToKey[partyId];
delete cantonPartyToKey[partyId];
emit KeyRevoked(partyId, oldKey);
}
}bash
# Deploy key registry
forge create CantonKeyRegistry \
--rpc-url $ETH_RPC_URL \
--private-key $DEPLOYER_KEY
# Register Canton public key
cast send $REGISTRY_ADDRESS \
"registerKey(bytes32,address)" \
$AUTHORIZED_PARTY_ID \
$CANTON_PUBKEY_ADDRESS \
--rpc-url $ETH_RPC_URL \
--private-key $ADMIN_KEYInfrastructure as Code
Terraform Configuration
hcl
# infrastructure/main.tf
# Canton Domain
resource "aws_ecs_task_definition" "canton_domain" {
family = "canton-domain"
container_definitions = jsonencode([
{
name = "canton"
image = "digitalasset/canton:latest"
memory = 4096
cpu = 2048
environment = [
{
name = "CANTON_CONFIG"
value = "/etc/canton/production.conf"
}
]
mountPoints = [
{
sourceVolume = "canton-config"
containerPath = "/etc/canton"
}
]
}
])
}
# Relayer Service
resource "aws_ecs_service" "relayer" {
name = "canton-relayer"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.relayer.arn
desired_count = 3 # High availability
load_balancer {
target_group_arn = aws_lb_target_group.relayer.arn
container_name = "relayer"
container_port = 8080
}
}
# PostgreSQL for Canton
resource "aws_db_instance" "canton" {
identifier = "canton-db"
engine = "postgres"
engine_version = "14"
instance_class = "db.r5.2xlarge"
allocated_storage = 500
storage_encrypted = true
multi_az = true
backup_retention_period = 30
vpc_security_group_ids = [aws_security_group.canton_db.id]
}Monitoring and Alerting
Prometheus Metrics
yaml
# prometheus/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'canton-domain'
static_configs:
- targets: ['canton:5021']
- job_name: 'relayer'
static_configs:
- targets: ['relayer-1:9090', 'relayer-2:9090']
- job_name: 'ethereum-node'
static_configs:
- targets: ['geth:6060']Grafana Dashboards
json
{
"dashboard": {
"title": "Canton DeFi Vault Operations",
"panels": [
{
"title": "Intent Processing Rate",
"targets": [
{
"expr": "rate(relayer_intents_processed_total[5m])"
}
]
},
{
"title": "Execution Success Rate",
"targets": [
{
"expr": "rate(relayer_chain_execution_success_rate[5m])"
}
]
},
{
"title": "Canton Ledger Height",
"targets": [
{
"expr": "canton_ledger_height"
}
]
}
]
}
}AlertManager Rules
yaml
# alertmanager/rules.yml
groups:
- name: CantonVault
interval: 30s
rules:
- alert: IntentProcessingDelay
expr: relayer_intent_queue_depth > 10
for: 5m
annotations:
summary: "High intent queue depth"
- alert: ExecutionFailureRate
expr: rate(relayer_execution_failures_total[5m]) > 0.05
for: 2m
annotations:
summary: "High execution failure rate"
- alert: CantonDomainDown
expr: up{job="canton-domain"} == 0
for: 1m
annotations:
summary: "Canton domain is down"
severity: "critical"Deployment Checklist
Pre-Production
- [ ] Canton domain deployed and synced
- [ ] HSM/KMS integration tested
- [ ] Participant nodes configured
- [ ] Topology properly set up (parties, keys)
- [ ] Relayer cluster deployed (3+ instances)
- [ ] Leader election working
- [ ] Vault contracts deployed to testnets
- [ ] Adapters registered
- [ ] Key registry synchronized (if Pattern 2)
- [ ] End-to-end tests passing
- [ ] Security audit completed
- [ ] Load testing performed
- [ ] Monitoring configured
- [ ] Alerting rules tested
- [ ] Runbooks documented
- [ ] On-call rotation established
Production Launch
- [ ] Deploy to mainnet chains
- [ ] Verify all contracts on explorers
- [ ] Transfer ownership to multisig
- [ ] Set emergency contacts
- [ ] Enable monitoring
- [ ] Start with limited AUM
- [ ] Gradual rollout (1 chain → multi-chain)
- [ ] Monitor for 48 hours before scaling
- [ ] Regular health checks
- [ ] Incident response ready
