5.3 Observability and Auditing Framework
Effective institutional operation requires a comprehensive observability framework that bridges the private and public execution contexts.
Unified Dashboard
A comprehensive dashboard combining Canton ledger state with public chain execution status.
Key Metrics
┌────────────────────────────────────────────────────┐
│ Canton DeFi Vault Dashboard │
├────────────────────────────────────────────────────┤
│ Intents Generated: 1,234 │ Success Rate: 99.2% │
│ Total AUM: $10.5M │ Active Vaults: 12 │
│ Avg. Latency: 2.3s │ Failed TX: 10 │
└────────────────────────────────────────────────────┘Implementation
typescript
interface DashboardMetrics {
// Canton metrics
canton: {
activePolicies: number;
pendingApprovals: number;
intentsGenerated24h: number;
averageApprovalTime: number;
};
// Relayer metrics
relayer: {
queueDepth: number;
processingRate: number;
verificationFailures: number;
currentLeader: string;
};
// Execution metrics
execution: {
successRate: number;
averageGas: BigNumber;
averageLatency: number;
totalVolume24h: BigNumber;
};
// Per-chain metrics
chains: Map<string, ChainMetrics>;
}
class ObservabilityService {
async getMetrics(): Promise<DashboardMetrics> {
const [canton, relayer, execution] = await Promise.all([
this.getCantonMetrics(),
this.getRelayerMetrics(),
this.getExecutionMetrics()
]);
return { canton, relayer, execution, chains: await this.getChainMetrics() };
}
}Audit Trail Correlation
Tracing a Transaction
typescript
async function traceTransaction(txHash: string): Promise<AuditTrail> {
// 1. Find on-chain transaction
const tx = await ethProvider.getTransaction(txHash);
const receipt = await ethProvider.getTransactionReceipt(txHash);
// 2. Extract intent data from transaction calldata
const intentData = decodeExecuteIntent(tx.data);
// 3. Query Canton for the ExecutionIntent
const cantonIntent = await canton.query({
templateId: 'Vault.Policy:ExecutionIntent',
filter: { nonce: intentData.nonce }
});
// 4. Find the source proposal (if Flow B)
let proposal = null;
if (cantonIntent.sourceProposalId) {
proposal = await canton.query({
templateId: 'Vault.Policy:RebalanceProposal',
contractId: cantonIntent.sourceProposalId
});
}
// 5. Get the policy in effect
const policy = await canton.query({
templateId: 'Vault.Policy:VaultPolicy',
contractId: cantonIntent.policyId
});
return {
publicChain: {
txHash,
block: receipt.blockNumber,
timestamp: (await ethProvider.getBlock(receipt.blockNumber)).timestamp,
gasUsed: receipt.gasUsed,
success: receipt.status === 1
},
canton: {
intentId: cantonIntent.contractId,
issuer: cantonIntent.issuer,
createdAt: cantonIntent.createdAt,
policy: policy,
proposal: proposal
}
};
}Example Audit Query
typescript
// Find all high-value transactions in the last month
async function auditHighValueTransactions(): Promise<AuditReport> {
const oneMonthAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
const intents = await canton.query({
templateId: 'Vault.Policy:ExecutionIntent',
filter: {
createdAt: { $gte: oneMonthAgo },
estimatedValue: { $gte: 100000 } // > $100k
}
});
const report = await Promise.all(
intents.map(async (intent) => {
const execution = await findExecution(intent.id);
return {
intentId: intent.id,
createdAt: intent.createdAt,
estimatedValue: intent.estimatedValue,
executed: execution !== null,
success: execution?.success,
actualValue: execution?.actualValue,
approvals: await getApprovals(intent.sourceProposalId)
};
})
);
return { period: '30d', highValueTx: report };
}Real-Time Alerts
Alert Configuration
yaml
# alerts.yml
alerts:
- name: policy_violation_attempted
severity: high
condition: canton.policy_check_failed > 0
action:
- notify: risk-officer
- create_ticket: jira
- name: execution_failure
severity: medium
condition: execution.failed_tx_rate > 0.05
action:
- notify: ops-team
- page_on_call: true
- name: high_slippage
severity: medium
condition: execution.slippage > 2.0 # > 2%
action:
- notify: trader
- pause_vault: true # Auto-pause
- name: intent_expiry
severity: low
condition: relayer.expired_intents > 5
action:
- notify: ops-team
- name: relayer_equivocation
severity: critical
condition: relayer.duplicate_submissions > 0
action:
- emergency_pause: all
- page_on_call: true
- notify: security-teamAlert Implementation
typescript
class AlertManager {
private rules: AlertRule[];
async evaluateAlerts(): Promise<void> {
const metrics = await this.getMetrics();
for (const rule of this.rules) {
if (this.evaluateCondition(rule.condition, metrics)) {
await this.triggerAlert(rule);
}
}
}
private async triggerAlert(rule: AlertRule): Promise<void> {
// Record alert
await db.alerts.create({
name: rule.name,
severity: rule.severity,
timestamp: Date.now(),
metrics: this.currentMetrics
});
// Execute actions
for (const action of rule.actions) {
switch (action.type) {
case 'notify':
await this.notify(action.recipient, rule);
break;
case 'pause_vault':
await this.pauseVault(action.vaultId);
break;
case 'page_on_call':
await this.pageOnCall(rule);
break;
case 'emergency_pause':
await this.emergencyPauseAll();
break;
}
}
}
}Off-Chain Canton State Snapshots
Snapshot Service
typescript
class CantonSnapshotService {
async createSnapshot(): Promise<Snapshot> {
// 1. Query all active contracts
const [policies, proposals, intents] = await Promise.all([
canton.queryAll('Vault.Policy:VaultPolicy'),
canton.queryAll('Vault.Policy:RebalanceProposal'),
canton.queryAll('Vault.Policy:ExecutionIntent')
]);
// 2. Serialize state
const state = {
timestamp: Date.now(),
ledgerHeight: await canton.getLedgerHeight(),
policies,
proposals,
intents
};
// 3. Sign snapshot with Canton key
const stateHash = hash(JSON.stringify(state));
const signature = await canton.sign(stateHash);
// 4. Store snapshot
const snapshot = {
...state,
hash: stateHash,
signature
};
await storage.put(`snapshots/${state.timestamp}.json`, snapshot);
return snapshot;
}
async verifySnapshot(snapshot: Snapshot): Promise<boolean> {
// 1. Recompute hash
const { signature, hash, ...state } = snapshot;
const computedHash = hash(JSON.stringify(state));
if (computedHash !== hash) {
return false;
}
// 2. Verify signature
return canton.verify(hash, signature);
}
}
// Schedule regular snapshots
setInterval(async () => {
await snapshotService.createSnapshot();
}, 1000 * 60 * 60); // Every hourForensic Analysis Tools
Transaction Investigation
typescript
async function investigateIncident(
incidentId: string
): Promise<Investigation> {
const incident = await db.incidents.findOne(incidentId);
// Collect all related data
const investigation = {
incident,
timeline: await buildTimeline(incident),
cantonState: await getCantonStateAtTime(incident.timestamp),
relayerLogs: await getRelayerLogs(incident.timestamp),
chainTransactions: await getChainTransactions(incident),
policyHistory: await getPolicyHistory(incident),
approvals: await getApprovals(incident)
};
// Analyze
investigation.analysis = {
rootCause: await analyzeRootCause(investigation),
impact: await assessImpact(investigation),
recommendations: await generateRecommendations(investigation)
};
return investigation;
}Compliance Report Generation
typescript
async function generateComplianceReport(
startDate: Date,
endDate: Date
): Promise<ComplianceReport> {
// Query all intents in period
const intents = await canton.query({
templateId: 'Vault.Policy:ExecutionIntent',
filter: {
createdAt: { $gte: startDate, $lte: endDate }
}
});
// Analyze compliance
const report = {
period: { start: startDate, end: endDate },
totalIntents: intents.length,
policyCompliance: {
total: intents.length,
compliant: intents.filter(i => i.policyValidated).length,
violations: intents.filter(i => !i.policyValidated).length
},
approvalWorkflow: {
requiresApproval: intents.filter(i => i.sourceProposalId).length,
averageApprovalTime: calculateAverageApprovalTime(intents),
quorumBreaches: findQuorumBreaches(intents)
},
execution: {
successful: await countSuccessful(intents),
failed: await countFailed(intents),
averageGas: await calculateAverageGas(intents)
},
securityEvents: await getSecurityEvents(startDate, endDate)
};
// Generate PDF
const pdf = await generatePDF(report);
// Sign report
const signature = await canton.sign(hash(pdf));
return { report, pdf, signature };
}Performance Monitoring
Key Performance Indicators
typescript
interface KPIs {
// Latency metrics
cantonToRelayer: number; // Intent generation → Relayer receipt
relayerToChain: number; // Relayer → Chain submission
chainConfirmation: number; // Submission → Confirmation
endToEnd: number; // Intent generation → Confirmed
// Throughput metrics
intentsPerHour: number;
transactionsPerChain: Map<string, number>;
// Reliability metrics
intentSuccessRate: number;
relayerUptime: number;
cantonUptime: number;
// Cost metrics
averageGasPerIntent: BigNumber;
totalGasCost24h: BigNumber;
}
async function calculateKPIs(): Promise<KPIs> {
const metrics = await metricsService.query({
range: '24h',
aggregation: 'avg'
});
return {
endToEnd: metrics.latency.p95,
intentSuccessRate: metrics.success.rate,
averageGasPerIntent: metrics.gas.avg,
// ... other KPIs
};
}Best Practices
1. Structured Logging
typescript
// Good: Structured, queryable logs
logger.info('intent_executed', {
intentId: intent.id,
vaultId: intent.vaultId,
nonce: intent.nonce,
chain: 'ethereum',
txHash: receipt.transactionHash,
gasUsed: receipt.gasUsed.toString(),
duration_ms: executionTime
});
// Bad: Unstructured logs
console.log(`Executed intent ${intent.id} with tx ${receipt.transactionHash}`);2. Correlation IDs
typescript
// Track requests across components
const correlationId = uuid();
logger.info('intent_received', { correlationId, intentId });
// → Relayer
logger.info('transaction_prepared', { correlationId, txHash });
// → Chain
logger.info('transaction_confirmed', { correlationId, blockNumber });
// → Canton
logger.info('execution_reported', { correlationId, success: true });3. Regular Audits
Schedule regular audit reviews:
typescript
// Weekly audit review
cron.schedule('0 9 * * 1', async () => { // Every Monday 9 AM
const report = await generateComplianceReport(
new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
new Date()
);
await notifyAuditTeam(report);
});